Очерёдность выполнения запросов с MongoDB ReactiveStreams

Впервые взялся за синхронизацию потоков и столкнулся с проблемой, что данные пытаются загрузиться быстрее, чем создаются (поток загрузки стартует быстрее).

Мне необходимо, чтобы loadData() вызывался только после того, как отработают все запросы из regenerate().

За день перепробовал всё, что смог найти и довести до +- рабочего состояния:

  • ExecutorService.
  • synchronized (на объект, db и OperationSubscriber.class) на методах loadData() и insertIfNotFound(...).

Подскажите, как без говнокода реализовать необходимый мне функционал, заранее благодарю.

public class DatabaseManager {
    ...

    public DatabaseManager() {
        reload();
    }

    /**
     * Переподключается к базе данных.
     */
    @SneakyThrows
    public void reload() {
        ...
        db = client.getDatabase(database);

        regenerate();
        loadData();
    }

    /**
     * Генерирует стандартные коллекции и значения, если их нет.
     */
    private void regenerate() {
        insertIfNotFound(
            "settings",
            Filters.eq("_id", "core"),
            "core",
            settingManager.getCoreSettings()
        );

        insertIfNotFound(
            "settings",
            Filters.eq("_id", "docker"),
            "docker",
            settingManager.getDockerSettings()
        );

        ...
    }

    /**
     * Загружает необходимые данные для старта ядра из базы данных.
     */
    private void loadData() {
            db.getCollection("settings")
                .find(Filters.or(
                    Filters.eq("_id", "core"),
                    Filters.eq("_id", "docker")
                ))
                .subscribe(new OperationSubscriber<>(documents -> {
                    documents.forEach(document -> {
                        ...
                    });

                    socketServer.bind();
                }));
        }
    }

    /**
     * Вставляет документ в коллекцию, если соответствующий фильтрам документ не найден.
     * Если коллекция не найдена - создают пустую коллекцию.
     * @param collectionName Название коллекции, в которой необходимо осуществлять поиск.
     * @param filter Фильтры искомого документа.
     * @param insert Объект, который будет вставлен, если документ не найден.
     */
    private void insertIfNotFound(String collectionName, Bson filter, Object insert) {
        insertIfNotFound(collectionName, filter, "", insert);
    }

    /**
     * Вставляет документ в коллекцию, если соответствующий фильтрам документ не найден.
     * Если коллекция не найдена - создают пустую коллекцию.
     * @param collectionName Название коллекции, в которой необходимо осуществлять поиск.
     * @param filter Фильтры искомого документа.
     * @param id ID, который будет иметь документ.
     * @param insert Объект, который будет вставлен, если документ не найден.
     */
    private void insertIfNotFound(String collectionName, Bson filter, String id, Object insert) {
        MongoCollection<Document> collection = db.getCollection(collectionName);
        collection.find(filter)
            .subscribe(new OperationSubscriber<>(documents -> {
                if(documents.size() > 0) return;

                Document document = Document.parse(gson.toJson(insert));
                if(!id.isEmpty()) document.append("_id", id);

                collection.insertOne(document)
                    .subscribe(new VoidSubscriber<>());
            }));
    }
}
public class OperationSubscriber<T> implements Subscriber<T> {
    List<T> result = new ArrayList<>();
    Consumer<List<T>> callback;

    @Override
    public void onNext(T elem) {
        result.add(elem);
    }

    @Override
    public void onComplete() {
        callback.accept(result);
    }

    @Override
    public void onError(Throwable e) {
        e.printStackTrace();
    }

    @Override
    public void onSubscribe(Subscription s) {
        s.request(999);
    }
}

Ответы (1 шт):

Автор решения: Alexander Pavlov

Мой ответ просто из категории "многопоточное" программирование, т.е. возможно, библиотека Монго предоставляет более удобные инструменты.

    public void reload() {
        CompletableFuture.allOf(
            regenerate()
        ).thenRun(() -> loadData());
    }

    private CompletableFuture<Void> [] regenerate() {
        return new CompletableFuture<Void> []{
                insertIfNotFound(
                        "settings",
                        Filters.eq("_id", "core"),
                        "core",
                        settingManager.getCoreSettings()
                ),
                insertIfNotFound(
                        "settings",
                        Filters.eq("_id", "docker"),
                        "docker",
                        settingManager.getDockerSettings()
                )
        };
    }
    
    private CompletableFuture<Void> insertIfNotFound(String collectionName, Bson filter, Object insert) {
        return insertIfNotFound(collectionName, filter, "", insert);
    }
    
    private CompletableFuture<Void> insertIfNotFound(String collectionName, Bson filter, String id, Object insert) {
        final var done = new CompletableFuture<Void>();
        MongoCollection<Document> collection = db.getCollection(collectionName);
        collection.find(filter)
                .subscribe(new OperationSubscriber<>(documents -> {
                    if(documents.size() > 0) {
                        done.complete(null);
                        return;
                    }

                    Document document = Document.parse(gson.toJson(insert));
                    if(!id.isEmpty()) document.append("_id", id);

                    collection.insertOne(document)
                            .subscribe(new VoidSubscriber<>(() -> done.complete(null)));
                }));

        return done;
    }

→ Ссылка