We see occasionally in our production environment a concurrent access issue on a Timetable object.
It seems that a reader thread (GraphQL API call) iterates over the TripTimes collection of a Timetable object while this collection is being modified concurrently by another thread.
In theory this should not happen since the TimetableSnapshot should ensure that all write operations on Timetable objects happen before the Timetables are handed over to reader threads.
Exception while fetching data (/stopPlaces[0]/estimatedCalls) : null
java.util.ConcurrentModificationException: null
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1095)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:1049)
at org.opentripplanner.routing.stoptimes.StopTimesHelper.listTripTimeShortsForPatternAtStop(StopTimesHelper.java:247)
at org.opentripplanner.routing.stoptimes.StopTimesHelper.stopTimesForStop(StopTimesHelper.java:65)
at org.opentripplanner.transit.service.DefaultTransitService.stopTimesForStop(DefaultTransitService.java:325)
at org.opentripplanner.apis.transmodel.model.stop.StopPlaceType.getTripTimesForStop(StopPlaceType.java:438)
at org.opentripplanner.apis.transmodel.model.stop.StopPlaceType.lambda$create$16(StopPlaceType.java:378)
at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:273)
at java.base/java.util.HashMap$KeySpliterator.tryAdvance(HashMap.java:1736)
at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:129)
at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:527)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
at org.opentripplanner.apis.transmodel.model.stop.StopPlaceType.lambda$create$17(StopPlaceType.java:400)
at graphql.execution.ExecutionStrategy.invokeDataFetcher(ExecutionStrategy.java:329)
at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:305)
s at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:243)
at graphql.execution.ExecutionStrategy.resolveFieldWithInfo(ExecutionStrategy.java:214)
at graphql.execution.AsyncExecutionStrategy.execute(AsyncExecutionStrategy.java:63)
at graphql.execution.ExecutionStrategy.completeValueForObject(ExecutionStrategy.java:729)
at graphql.execution.ExecutionStrategy.completeValue(ExecutionStrategy.java:502)
at graphql.execution.ExecutionStrategy.completeValueForList(ExecutionStrategy.java:613)
at graphql.execution.ExecutionStrategy.completeValueForList(ExecutionStrategy.java:561)
at graphql.execution.ExecutionStrategy.completeValue(ExecutionStrategy.java:487)
at graphql.execution.ExecutionStrategy.completeField(ExecutionStrategy.java:453)
at graphql.execution.ExecutionStrategy.lambda$resolveFieldWithInfo$1(ExecutionStrategy.java:216)
at java.base/java.util.concurrent.CompletableFuture.uniApplyNow(CompletableFuture.java:684)
at java.base/java.util.concurrent.CompletableFuture.uniApplyStage(CompletableFuture.java:662)
at java.base/java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:2200)
at graphql.execution.ExecutionStrategy.resolveFieldWithInfo(ExecutionStrategy.java:215)
at graphql.execution.AsyncExecutionStrategy.execute(AsyncExecutionStrategy.java:63)
at graphql.execution.Execution.executeOperation(Execution.java:162)
at graphql.execution.Execution.execute(Execution.java:104)
at graphql.GraphQL.execute(GraphQL.java:568)
at graphql.GraphQL.lambda$parseValidateAndExecute$13(GraphQL.java:487)
at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1187)
at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2341)
at graphql.GraphQL.parseValidateAndExecute(GraphQL.java:482)
at graphql.GraphQL.lambda$executeAsync$9(GraphQL.java:440)
at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1187)
at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2341)
at graphql.GraphQL.executeAsync(GraphQL.java:428)
at graphql.GraphQL.execute(GraphQL.java:366)
at org.opentripplanner.apis.transmodel.TransmodelGraph.executeGraphQL(TransmodelGraph.java:66)
at org.opentripplanner.apis.transmodel.TransmodelAPI.getGraphQL(TransmodelAPI.java:113)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:52)
at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:146)
at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:189)
at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:176)
at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:93)
at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:478)
at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:400)
at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:81)
at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:274)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)
at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
at org.glassfish.jersey.internal.Errors.process(Errors.java:244)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:266)
at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:253)
at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:696)
at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer.service(GrizzlyHttpContainer.java:367)
at org.glassfish.grizzly.http.server.HttpHandler$1.run(HttpHandler.java:190)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:535)
at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:515)
at java.base/java.lang.Thread.run(Thread.java:1583)
We see occasionally in our production environment a concurrent access issue on a Timetable object. It seems that a reader thread (GraphQL API call) iterates over the TripTimes collection of a Timetable object while this collection is being modified concurrently by another thread. In theory this should not happen since the TimetableSnapshot should ensure that all write operations on Timetable objects happen before the Timetables are handed over to reader threads.