Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SIGSEGV with Concurrent Write #175

Open
2 tasks done
basanthjenuhb opened this issue Mar 23, 2025 · 6 comments
Open
2 tasks done

SIGSEGV with Concurrent Write #175

basanthjenuhb opened this issue Mar 23, 2025 · 6 comments

Comments

@basanthjenuhb
Copy link

basanthjenuhb commented Mar 23, 2025

What happens?

Seeing SIGSEGV with concurrent Write

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x000000013dec82fc, pid=49078, tid=35331
#
# JRE version: OpenJDK Runtime Environment Corretto-21.0.4.7.1 (21.0.4+7) (build 21.0.4+7-LTS)
# Java VM: OpenJDK 64-Bit Server VM Corretto-21.0.4.7.1 (21.0.4+7-LTS, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, bsd-aarch64)
# Problematic frame:
# C  [libduckdb_java8757326263049628978.so+0xdc82fc]  duckdb::StandardBufferManager::Pin(duckdb::shared_ptr<duckdb::BlockHandle, true>&)+0x4c
#

To Reproduce

package org.bjenuhb.learn.postgresduckdb;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

public class ConcurrencyTestV2 {

    AtomicLong writeCount = new AtomicLong(0);

    @Test
    @SneakyThrows
    void concurrentReadWrite() {
        HikariDataSource dataSource = createDataSource();

        int numShards = 3;
        int numRows = 10000000;
        int numTreads = 10;

        setupShards(dataSource, numShards, numRows);
        concurrentWrite(dataSource, numShards, numTreads, numRows);
        monitor();
        TimeUnit.SECONDS.sleep(1000);
    }

    void monitor() {
        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                    System.out.println("Write count: " + writeCount.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    void concurrentWrite(HikariDataSource dataSource,
                         int numShards,
                         int numThreads,
                         int numRows) {
        AtomicInteger atomicInteger = new AtomicInteger(0);
        var executorService = Executors.newFixedThreadPool(numThreads);
        Random random = new Random();
        for (int i = 0; i < numThreads; i++) {
            executorService.submit(() -> {
                while (true) {
                    try (Connection connection = dataSource.getConnection()) {
                        int shardId = random.nextInt(numShards) % numShards;
                        executeQuery(connection, "use shard" + shardId);
                        int rowId = getNext(atomicInteger, numRows);
                        executeQuery(connection, "update test set amount = amount + 1 where id = " + rowId);
                        connection.commit();
                        writeCount.incrementAndGet();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    int getNext(AtomicInteger integer, int max) {
        if (integer.get() >= max) {
            synchronized (integer) {
                if (integer.get() >= max) {
                    integer.set(0);
                }
                return integer.incrementAndGet();
            }
        }
        return integer.incrementAndGet();
    }

    HikariDataSource createDataSource() {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setJdbcUrl("jdbc:duckdb:test.db");
        hikariConfig.setMaximumPoolSize(10);
        hikariConfig.setMaximumPoolSize(100);
        hikariConfig.setAutoCommit(false);
        return new HikariDataSource(hikariConfig);
    }

    @SneakyThrows
    void setupShards(HikariDataSource dataSource,
                     int numShards,
                     int numRows) {
        Connection connection = dataSource.getConnection();
        for (int i = 0; i < numShards; i++) {
            executeQuery(connection, "attach database 'shard" + i + ".db' as shard" + i);
            executeQuery(connection, "use shard" + i);
            executeQuery(connection, "create or replace table test (id bigint primary key, amount int, description varchar)");
            executeQuery(connection, "insert into test SELECT range id, cast(random() * 100000 as bigint) as amount, repeat('x', 10) as description FROM range(" + numRows + ");");
            connection.commit();
        }
        connection.close();
    }

    @SneakyThrows
    void executeQuery(Connection connection, String query) {
        try (PreparedStatement statement = connection.prepareStatement(query)) {
            statement.execute();
        }
    }
}

  • After this runs for 1-2 mins, the SIGSEGV appears
  • Doesn't happen with 1.1.3, happens with 1.2.1.

OS:

OSX

DuckDB Version:

1.2.1

DuckDB Client:

Java

Hardware:

Mac M1

Full Name:

Basanth Jenu H B

Affiliation:

Intuit

What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.

I have tested with a stable release

Did you include all relevant data sets for reproducing the issue?

Yes

Did you include all code required to reproduce the issue?

  • Yes, I have

Did you include all relevant configuration (e.g., CPU architecture, Python version, Linux distribution) to reproduce the issue?

  • Yes, I have
@staticlibs
Copy link
Contributor

Hi! Thanks for the report, I will take a look.

May I also ask someone with appropriate permissions to move this issue to duckdb-java repo?

@szarnyasg szarnyasg transferred this issue from duckdb/duckdb Mar 23, 2025
@szarnyasg
Copy link
Contributor

@staticlibs sure!

@staticlibs
Copy link
Contributor

I cannot reproduce this on x86_64 Linux, the whole test completes successfully in 17:35 min with "Write count: 316243". Overall this does not look like a crash in JDBC, there are a few of them there, but this one does not do anything concurrently with connections or statements.

Hardware:
Mac M1

@basanthjenuhb I wonder if you can reproduce this on x86_64 in your environment?

The current guess is that the problem is specific to ARM's less strict memory model. I intend to try this on ARM (and, if reproduced, try to get the same without Java), but don't have access to M1 Mac right now, so it is going to take some time.

@basanthjenuhb
Copy link
Author

got it, I'll try with both x86_64 and non-java

@basanthjenuhb
Copy link
Author

We were not able to reproduce it on x86_64
so yes, might be specific to ARM

@staticlibs
Copy link
Contributor

@basanthjenuhb

Thanks for the details! This really narrows the scope of the problem. Though ARM Macs are definitely a tier-1 platform for DuckDB due to their high performance. I will try this on ARM, perhaps on RPi while waiting for an M1 Mac, and will report back here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants