Skip to content

Commit c6c2782

Browse files
samuelAndalonSamuel Vazquez
and
Samuel Vazquez
authored
feat: synchronize executions when attempting to remove an entry (ExpediaGroup#2012)
### 📝 Description when attempting to remove an execution from the concurrent map (executions) we need to make sure that the executions map is locked, that way we will guarantee the atomic calculation of of synchronous execution exhausted state Co-authored-by: Samuel Vazquez <[email protected]>
1 parent 62bcf34 commit c6c2782

File tree

4 files changed

+28
-30
lines changed

4 files changed

+28
-30
lines changed

executions/graphql-kotlin-dataloader-instrumentation/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ tasks {
2020
limit {
2121
counter = "INSTRUCTION"
2222
value = "COVEREDRATIO"
23-
minimum = "0.94".toBigDecimal()
23+
minimum = "0.93".toBigDecimal()
2424
}
2525
limit {
2626
counter = "BRANCH"

executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/extensions/CompletableFutureExtensions.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,16 @@ fun <V> CompletableFuture<V>.dispatchIfNeeded(
3535
val dataLoaderRegistry = environment.dataLoaderRegistry as? KotlinDataLoaderRegistry ?: throw MissingKotlinDataLoaderRegistryException()
3636

3737
if (dataLoaderRegistry.dataLoadersInvokedOnDispatch()) {
38-
val cantContinueExecution = when {
38+
when {
3939
environment.graphQlContext.hasKey(SyncExecutionExhaustedState::class) -> {
4040
environment
4141
.graphQlContext.get<SyncExecutionExhaustedState>(SyncExecutionExhaustedState::class)
42-
.allSyncExecutionsExhausted()
42+
.ifAllSyncExecutionsExhausted {
43+
dataLoaderRegistry.dispatchAll()
44+
}
4345
}
4446
else -> throw MissingInstrumentationStateException()
4547
}
46-
47-
if (cantContinueExecution) {
48-
dataLoaderRegistry.dispatchAll()
49-
}
5048
}
5149
return this
5250
}

executions/graphql-kotlin-dataloader-instrumentation/src/main/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/state/SyncExecutionExhaustedState.kt

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,12 @@ class SyncExecutionExhaustedState(
7171
override fun onCompleted(result: ExecutionResult?, t: Throwable?) {
7272
if ((result != null && result.errors.size > 0) || t != null) {
7373
if (executions.containsKey(parameters.executionInput.executionId)) {
74-
executions.remove(parameters.executionInput.executionId)
75-
totalExecutions.set(totalExecutions.get() - 1)
76-
val allSyncExecutionsExhausted = allSyncExecutionsExhausted()
77-
if (allSyncExecutionsExhausted) {
78-
onSyncExecutionExhausted(executions.keys().toList())
74+
synchronized(executions) {
75+
executions.remove(parameters.executionInput.executionId)
76+
totalExecutions.set(totalExecutions.get() - 1)
77+
}
78+
ifAllSyncExecutionsExhausted { executionIds ->
79+
onSyncExecutionExhausted(executionIds)
7980
}
8081
}
8182
}
@@ -131,9 +132,8 @@ class SyncExecutionExhaustedState(
131132
executionState
132133
}
133134

134-
val allSyncExecutionsExhausted = allSyncExecutionsExhausted()
135-
if (allSyncExecutionsExhausted) {
136-
onSyncExecutionExhausted(executions.keys().toList())
135+
ifAllSyncExecutionsExhausted { executionIds ->
136+
onSyncExecutionExhausted(executionIds)
137137
}
138138
}
139139

@@ -143,9 +143,8 @@ class SyncExecutionExhaustedState(
143143
executionState
144144
}
145145

146-
val allSyncExecutionsExhausted = allSyncExecutionsExhausted()
147-
if (allSyncExecutionsExhausted) {
148-
onSyncExecutionExhausted(executions.keys().toList())
146+
ifAllSyncExecutionsExhausted { executionIds ->
147+
onSyncExecutionExhausted(executionIds)
149148
}
150149
}
151150

@@ -155,17 +154,18 @@ class SyncExecutionExhaustedState(
155154
}
156155

157156
/**
158-
* Provide the information about when all [ExecutionInput] sharing a [GraphQLContext] exhausted their execution
157+
* execute a given [predicate] when all [ExecutionInput] sharing a [GraphQLContext] exhausted their execution.
159158
* A Synchronous Execution is considered Exhausted when all [DataFetcher]s of all paths were executed up until
160159
* a scalar leaf or a [DataFetcher] that returns a [CompletableFuture]
161160
*/
162-
fun allSyncExecutionsExhausted(): Boolean = synchronized(executions) {
163-
val operationsToExecute = totalExecutions.get()
164-
when {
165-
executions.size < operationsToExecute || !dataLoaderRegistry.onDispatchFuturesHandled() -> false
166-
else -> {
167-
executions.values.all(ExecutionBatchState::isSyncExecutionExhausted)
161+
fun ifAllSyncExecutionsExhausted(predicate: (List<ExecutionId>) -> Unit) =
162+
synchronized(executions) {
163+
val operationsToExecute = totalExecutions.get()
164+
if (executions.size < operationsToExecute || !dataLoaderRegistry.onDispatchFuturesHandled())
165+
return@synchronized
166+
167+
if (executions.values.all(ExecutionBatchState::isSyncExecutionExhausted)) {
168+
predicate(executions.keys().toList())
168169
}
169170
}
170-
}
171171
}

executions/graphql-kotlin-dataloader-instrumentation/src/test/kotlin/com/expediagroup/graphql/dataloader/instrumentation/syncexhaustion/DataLoaderSyncExecutionExhaustedInstrumentationTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -614,9 +614,9 @@ class DataLoaderSyncExecutionExhaustedInstrumentationTest {
614614
fun `Instrumentation should not consider executions that thrown exceptions`() {
615615
val executions = listOf(
616616
ExecutionInput.newExecutionInput("query test1 { astronaut(id: 1) { id name } }").operationName("test1").build(),
617-
ExecutionInput.newExecutionInput("query test2 { astronaut(id: 2) { id name } }").operationName("test2").build(),
618-
ExecutionInput.newExecutionInput("query test3 { mission(id: 3) { id designation } }").operationName("test3").build(),
619-
ExecutionInput.newExecutionInput("query test4 { mission(id: 4) { designation } }").operationName("OPERATION_NOT_IN_DOCUMENT").build()
617+
ExecutionInput.newExecutionInput("query test2 { astronaut(id: 2) { id name } }").operationName("OPERATION_NOT_IN_DOCUMENT").build(),
618+
ExecutionInput.newExecutionInput("query test3 { mission(id: 3) { id designation } }").operationName("OPERATION_NOT_IN_DOCUMENT").build(),
619+
ExecutionInput.newExecutionInput("query test4 { mission(id: 4) { designation } }").operationName("test4").build()
620620
)
621621

622622
val (results, kotlinDataLoaderRegistry) = AstronautGraphQL.execute(
@@ -631,7 +631,7 @@ class DataLoaderSyncExecutionExhaustedInstrumentationTest {
631631
val missionStatistics = kotlinDataLoaderRegistry.dataLoadersMap["MissionDataLoader"]?.statistics
632632

633633
assertEquals(1, astronautStatistics?.batchInvokeCount)
634-
assertEquals(2, astronautStatistics?.batchLoadCount)
634+
assertEquals(1, astronautStatistics?.batchLoadCount)
635635

636636
assertEquals(1, missionStatistics?.batchInvokeCount)
637637
assertEquals(1, missionStatistics?.batchLoadCount)

0 commit comments

Comments
 (0)