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

Deadline exceed exception when getting projection list #287

Open
federicoorlandini opened this issue Sep 30, 2024 · 4 comments · May be fixed by #298
Open

Deadline exceed exception when getting projection list #287

federicoorlandini opened this issue Sep 30, 2024 · 4 comments · May be fixed by #298

Comments

@federicoorlandini
Copy link

federicoorlandini commented Sep 30, 2024

Hi here,

not sure if this is the right place to raise my question. Anyway, I'm using TestContainers to spin Docker containers to run integration tests using EvenStore database.
Every integration test scenario spins a new container.
This is the test class that contains the tests:

package integration;

import com.federico.LessonBookingSystem.adapters.out.persistence.EventStoreLessonProjectionsRepository;
import com.federico.LessonBookingSystem.adapters.out.persistence.EventStoreLessonRepository;
import com.federico.LessonBookingSystem.application.usecases.CreateLessonUseCaseImpl;
import com.federico.LessonBookingSystem.application.usecases.GetLessonsOverviewUseCaseImpl;
import jdk.jshell.spi.ExecutionControl;
import Lesson.Lesson;
import Lesson.DuplicatedLessonException;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.concurrent.ExecutionException;

@Testcontainers(disabledWithoutDocker = true)
public class GetLessonOverviewUseCaseTests extends TestContainerIntegrationTestBase {
    @Test
    public void GetLessonOverviewUseCase_createOneLesson_eventModellingScenario1() throws IOException, ExecutionException, InterruptedException, ExecutionControl.NotImplementedException, DuplicatedLessonException {
        // Arrange
        final var projectionClient = getProjectionClient();
        final var projectionRepository = new EventStoreLessonProjectionsRepository(projectionClient);

        // arranging the test scenario here......

        projectionRepository.EnsureProjectionExists();
        
        // other code here.....

        // Act
        var projection = projectionUseCase.GetLessonsProjection();

        // Assert
       // assertions here
    }

    @Test
    public void GetLessonOverviewUseCase_createTwoLessons_eventModellingScenario2() throws IOException, ExecutionException, InterruptedException, ExecutionControl.NotImplementedException, DuplicatedLessonException {
        // Arrange
        
        final var projectionClient = getProjectionClient();
        final var projectionRepository = new EventStoreLessonProjectionsRepository(projectionClient);
        projectionRepository.EnsureProjectionExists();

       // arrange the test scenario here.....

        // Act
        var projection = projectionUseCase.GetLessonsProjection();

        // asserting here
    }
}

This class inherits from this one:


import com.eventstore.dbclient.EventStoreDBClient;
import com.eventstore.dbclient.EventStoreDBClientSettings;
import com.eventstore.dbclient.EventStoreDBConnectionString;
import com.eventstore.dbclient.EventStoreDBProjectionManagementClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.utility.DockerImageName;

import java.util.Map;

// This is the base class for all the integration tests using the TestContainer.
// This class contains all the common code shared by all the integration tests using the TestContainer library
public abstract class TestContainerIntegrationTestBase {
    protected static final String CONNECTION_STRING_TEMPLATE = "esdb://admin:changeit@localhost:%s?tls=false&tlsVerifyCert=false&defaultDeadline=30000";
    protected static final int EVENT_STORE_PORT = 2113;

    protected final Map<String, String> ENV_CONFIGURATION = Map.ofEntries(
            Map.entry("EVENTSTORE_ALLOWUNKNOWNOPTIONS", "true"),
            Map.entry("EVENTSTORE_INSECURE", "true"),
            Map.entry("EVENTSTORE_CLUSTER_SIZE", "1"),
            Map.entry("EVENTSTORE_RUN_PROJECTIONS", "All"),
            Map.entry("EVENTSTORE_START_STANDARD_PROJECTIONS", "true"),
            Map.entry("EVENTSTORE_EXT_TCP_PORT", "1113"),
            Map.entry("EVENTSTORE_EXT_HTTP_PORT", "2113"),
            Map.entry("EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP", "true"),
            Map.entry("EVENTSTORE_ENABLE_EXTERNAL_TCP", "true")
    );

    @Container
    protected final GenericContainer<?> eventStoreDbContainer =
            new GenericContainer<>(DockerImageName.parse("eventstore/eventstore:latest"))
                    .withExposedPorts(EVENT_STORE_PORT)
                    .withEnv(ENV_CONFIGURATION);

    @BeforeEach
    public void beforeEach() {
        eventStoreDbContainer.start();
    }

    @AfterEach
    public void afterEach() {
        eventStoreDbContainer.stop();
    }

    protected EventStoreDBProjectionManagementClient getProjectionClient() {
        var settings = getSettings();
        return EventStoreDBProjectionManagementClient.create(settings);
    }

    protected EventStoreDBClient getEventStoreDbClient() {
        var settings = getSettings();
        return EventStoreDBClient.create(settings);
    }

    private EventStoreDBClientSettings getSettings() {
        var port = eventStoreDbContainer.getMappedPort(EVENT_STORE_PORT);
        var connectionString = String.format(CONNECTION_STRING_TEMPLATE, port);
        var settings = EventStoreDBConnectionString.parseOrThrow(connectionString);
        return settings;
    }
}

And this is the ProjectionRepository class that implements the EnsureProjectExists() method:


import com.eventstore.dbclient.EventStoreDBProjectionManagementClient;
import Lesson.Lesson;
import com.federico.LessonBookingSystem.application.projections.ports.out.persistence.LessonProjectionsRepository;
import jdk.jshell.spi.ExecutionControl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;

@Repository
public class EventStoreLessonProjectionsRepository implements LessonProjectionsRepository {
    // This class is only used to deserialize the result of the projection LessonProjection
    private static class LessonProjectionResult {
        public ArrayList<Lesson> lessons;
    }
    private final EventStoreDBProjectionManagementClient projectionClient;

    @Autowired
    public EventStoreLessonProjectionsRepository(EventStoreDBProjectionManagementClient projectionClient) {
        this.projectionClient = projectionClient;
    }

    @Override
    public void EnsureProjectionExists() throws ExecutionException, InterruptedException, ExecutionControl.NotImplementedException {
        // Must check if the projection needed by this repository is present in the event store
        if( ProjectionExists() ) {
            System.out.println(String.format("Projection %s already exists. Skipping the creation.", LESSONS_PROJECTION_NAME));
        }
        else {
            System.out.println(String.format("Projection %s doesn't exist. Creating it.", LESSONS_PROJECTION_NAME));
            CreateProjection();
            System.out.println("Projection created");
        }
    }

    .......

    private boolean ProjectionExists() throws ExecutionException, InterruptedException {
        System.out.println("Reading projections");
        var projections = projectionClient.list().get();

        System.out.println("Projections have been red");
        return projections
                .stream()
                .anyMatch(item -> item.getName().equalsIgnoreCase(LESSONS_PROJECTION_NAME));
    }

    private void CreateProjection() throws ExecutionException, InterruptedException {
        final String LESSON_STATUS_JS_SCRIPT = "fromAll()\n" +
                "    .when({\n" +
                "        $init: function () {\n" +
                "            return {\n" +
                "                lessons: []\n" +
                "            }\n" +
                "        },\n" +
                "        LessonCreated: function (state, event) {\n" +
                "            const bodyraw = JSON.parse(event.bodyRaw);\n" +
                "            state.lessons.push({id: bodyraw.lessonId,\n" +
                "                date: bodyraw.date,\n" +
                "                startTime: bodyraw.startTime,\n" +
                "                endTime: bodyraw.endTime,\n" +
                "                maxNumberAttenders: bodyraw.maxNumberAttenders,\n" +
                "                status: 'OPEN'});\n" +
                "        }\n" +
                "    })\n" +
                "    .outputState()";

        projectionClient.create("LessonProjection", LESSON_STATUS_JS_SCRIPT).get();
    }
}

When executing the Gradle build, the first call to the method projectionRepository.EnsureProjectionExists() works, it returns the list of the projections and the test pass, but when Gradle executes the other test case and it executed the second time the projectionRepository.EnsureProjectionExists() then the execution stucks and I after the timeout the test fails with the error:

java.util.concurrent.ExecutionException: io.grpc.StatusRuntimeException: DEADLINE_EXCEEDED: deadline exceeded after 29.999275400s. Name resolution delay 0.000000000 seconds. [closed=[], open=[[remote_addr=localhost/127.0.0.1:63215]]] at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:396) at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2073) at com.federico.LessonBookingSystem.adapters.out.persistence.EventStoreLessonProjectionsRepository.ProjectionExists(EventStoreLessonProjectionsRepository.java:61) at com.federico.LessonBookingSystem.adapters.out.persistence.EventStoreLessonProjectionsRepository.EnsureProjectionExists(EventStoreLessonProjectionsRepository.java:31) at integration.EventStoreLessonProjectionsRepositoryTests.GetLessonOverviewUseCase_createOneLesson_eventModellingScenario3(EventStoreLessonProjectionsRepositoryTests.java:124)

For a reason that I'm not getting, the second call to the projectionClient.list().get() (see ProjectionExists() method in the class EventStoreLessonProjectionsRepository) stucks.

I checked the logs and I can confirm that this is the line of code where the execution stops until the timeout.

Any suggestion?

Thank you.

@w1am
Copy link
Contributor

w1am commented Oct 2, 2024

Hey @federicoorlandini

It's hard to read the code the way it's been formatted. Is there a repository we can look at to see the code?

@federicoorlandini
Copy link
Author

Hi @w1am ,
I fixed the text formatting for the code in my initial message. I hope that it is now more readable.

Looking forward to reading your thoughts about the issue I presented.

Thank you.

@w1am
Copy link
Contributor

w1am commented Oct 7, 2024

This might be caused by lingering connections from the previous container. I would make sure to close the client before stopping the container.

Perhaps something like this:

@AfterEach
public void afterEach() {
    if (projectionClient != null) {
        projectionClient.shutdown().get();
    }
    if (eventStoreDbClient != null) {
        eventStoreDbClient.shutdown().get();
    }
    eventStoreDbContainer.stop();
}

We do the same in our tests. See ClientTracker.java

@federicoorlandini
Copy link
Author

federicoorlandini commented Oct 16, 2024 via email

marcuslyth added a commit to marcuslyth/EventStoreDB-Client-Java that referenced this issue Jan 10, 2025
GrpcClient expects Exceptions of a specific type but as it is right now it will not handle any exceptions as they are propagated as CompletionException
This will not solve any similar issues for any other class using ClientTelemetry, and a better solution would probably be to never wrap the exception, however that would require more intrusive changes to the future chain.
Fixes EventStore#287
@marcuslyth marcuslyth linked a pull request Jan 10, 2025 that will close this issue
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

Successfully merging a pull request may close this issue.

2 participants