Concurrency bug in the Directory Layer

In the java binding, there is a local concurrency bug:

Inside a single transaction, if multiple threads are createOrOpening directories in a base directory with a common prefix, the directory might return as created, but in reality, it won’t be created. Even inside the same transation, listing it will cause an error.

7.1.x, Linux and Windows tested. Reproducer:

(“a”, “b”) is the base directory.

(“a”, “b”, “prefix”, random-uuid) are the directories being created.

 public static void main(String[] args) {

        int subDirectoryCount = 100;

        var executor = Executors.newFixedThreadPool(8);

        var db = FDB.selectAPIVersion(710)
                .open(null, Runnable::run);

        List<String> basePath = List.of("a", "b");

        // Delete root dir of it existed
        db.run(tr -> {
            DirectoryLayer.getDefault()
                    .removeIfExists(tr, basePath)
                    .join();
            return null;
        });

        // Create root dir
        var baseDirectory = db.run(tr -> {
            return DirectoryLayer.getDefault()
                    .createOrOpen(tr, basePath)
                    .join();
        });

        // Create many subdirectories in PARALLEL
        try (var tr = db.createTransaction(Runnable::run)) {
            CompletableFuture[] dirFutures = IntStream.range(0, subDirectoryCount)
                    .mapToObj(x -> CompletableFuture.supplyAsync(() -> {
                        try {

                            String subDirName = UUID.randomUUID().toString();

                            baseDirectory.createOrOpen(tr, List.of("prefix", subDirName))
                                    .join();

                            if (!baseDirectory.exists(tr, List.of("prefix", subDirName)).join()) {
                                throw new IllegalStateException("Subdir does not exist, although it was just created");
                            }

                            return null;

                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }, executor))
                    .toArray(CompletableFuture[]::new);

            CompletableFuture.allOf(dirFutures)
                    .join();

            tr.commit().join();
        }

        executor.shutdownNow();
        db.close();
        System.out.println("No bug detected");
    }

Thanks for the report! This is possibly a reproduction of this bug:

Unfortunately at this point, I don’t think there has been any attempt to make these directory operations within a single transaction thread safe.