Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/main/java/graphql/execution/ExecutionContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.function.Consumer;
import java.util.function.Supplier;

import static graphql.Assert.assertTrue;
import static graphql.execution.EngineRunningObserver.RunningState.CANCELLED;
import static graphql.execution.EngineRunningObserver.RunningState.NOT_RUNNING;
import static graphql.execution.EngineRunningObserver.RunningState.RUNNING;
Expand Down Expand Up @@ -378,15 +379,15 @@ public boolean isRunning() {

private void incrementRunning(Throwable throwable) {
checkIsCancelled(throwable);

assertTrue(isRunning.get() >= 0);
if (isRunning.incrementAndGet() == 1) {
changeOfState(RUNNING);
}
}

private void decrementRunning(Throwable throwable) {
checkIsCancelled(throwable);

assertTrue(isRunning.get() > 0);
if (isRunning.decrementAndGet() == 0) {
changeOfState(NOT_RUNNING);
}
Expand Down
32 changes: 20 additions & 12 deletions src/main/java/graphql/execution/ExecutionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ protected Object executeObject(ExecutionContext executionContext, ExecutionStrat
if (fieldValueInfosResult instanceof CompletableFuture) {
CompletableFuture<List<FieldValueInfo>> fieldValueInfos = (CompletableFuture<List<FieldValueInfo>>) fieldValueInfosResult;
fieldValueInfos.whenComplete((completeValueInfos, throwable) -> {
executionContext.run(throwable,() -> {
executionContext.run(throwable, () -> {
if (throwable != null) {
handleResultsConsumer.accept(null, throwable);
return;
Expand Down Expand Up @@ -280,7 +280,7 @@ protected Object executeObject(ExecutionContext executionContext, ExecutionStrat

private BiConsumer<List<Object>, Throwable> buildFieldValueMap(List<String> fieldNames, CompletableFuture<Map<String, Object>> overallResult, ExecutionContext executionContext) {
return (List<Object> results, Throwable exception) -> {
executionContext.run(exception,() -> {
executionContext.run(exception, () -> {
if (exception != null) {
handleValueException(overallResult, exception, executionContext);
return;
Expand Down Expand Up @@ -485,22 +485,32 @@ private Object fetchField(GraphQLFieldDefinition fieldDef, ExecutionContext exec
}
if (fetchedObject instanceof CompletableFuture) {
@SuppressWarnings("unchecked")
CompletableFuture<Object> fetchedValue = (CompletableFuture<Object>) fetchedObject;
executionContext.decrementRunning(fetchedValue);
CompletableFuture<FetchedValue> fetchedValueCF = fetchedValue
.handle((result, exception) -> executionContext.call(exception,() -> {
CompletableFuture<Object> originalFetchValue = (CompletableFuture<Object>) fetchedObject;
// the completion order of dependent CFs is in stack order for
// directly dependent CFs, but in reverse stack order for indirect dependent ones
// By creating one dependent CF on originalFetchValue, we make sure the order it is always
// in reverse stack order
CompletableFuture<Object> fetchedValue = originalFetchValue.thenApply(Function.identity());
executionContext.incrementRunning(fetchedValue);
CompletableFuture<Object> rawResultCF = fetchedValue
.handle((result, wrapperExceptionOrNull) -> executionContext.call(wrapperExceptionOrNull, () -> {
// because we added an artificial CF, we need to unwrap the exception
Throwable exception = wrapperExceptionOrNull != null ? wrapperExceptionOrNull.getCause() : null;
fetchCtx.onCompleted(result, exception);
if (exception != null) {
CompletableFuture<Object> handleFetchingExceptionResult = handleFetchingException(dataFetchingEnvironment.get(), parameters, exception);
return handleFetchingExceptionResult;
} else {
// we can simply return the fetched value CF and avoid a allocation
return fetchedValue;
return originalFetchValue;
}
}))
.thenCompose(Function.identity())
.thenCompose(Function.identity());
executionContext.incrementRunning(rawResultCF);
CompletableFuture<FetchedValue> fetchedValueCF = rawResultCF
.thenApply(result -> unboxPossibleDataFetcherResult(executionContext, parameters, result));
executionContext.incrementRunning(fetchedValue);
executionContext.decrementRunning(rawResultCF);
executionContext.decrementRunning(fetchedValue);
return fetchedValueCF;
} else {
fetchCtx.onCompleted(fetchedObject, null);
Expand Down Expand Up @@ -819,7 +829,7 @@ protected FieldValueInfo completeValueForList(ExecutionContext executionContext,
overallResult.whenComplete(completeListCtx::onCompleted);

resultsFuture.whenComplete((results, exception) -> {
executionContext.run(exception,() -> {
executionContext.run(exception, () -> {
if (exception != null) {
handleValueException(overallResult, exception, executionContext);
return;
Expand Down Expand Up @@ -1161,6 +1171,4 @@ private static void addErrorsToRightContext(List<GraphQLError> errors, Execution
executionContext.addErrors(errors);
}
}


}
192 changes: 192 additions & 0 deletions src/test/groovy/graphql/EngineRunningTest.groovy
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package graphql

import graphql.execution.DataFetcherExceptionHandler
import graphql.execution.DataFetcherExceptionHandlerResult
import graphql.execution.EngineRunningObserver
import graphql.execution.ExecutionId
import graphql.schema.DataFetcher
import spock.lang.Specification

import java.util.concurrent.CompletableFuture
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.locks.ReentrantLock

import static graphql.ExecutionInput.newExecutionInput
import static graphql.execution.EngineRunningObserver.ENGINE_RUNNING_OBSERVER_KEY
Expand Down Expand Up @@ -53,6 +56,55 @@ class EngineRunningTest extends Specification {
states == [RUNNING, NOT_RUNNING]
}

def "multiple async DF"() {
given:
def sdl = '''

type Query {
hello: String
hello2: String
}
'''
CompletableFuture cf1 = new CompletableFuture();
def df1 = { env ->
return cf1;
} as DataFetcher
CompletableFuture cf2 = new CompletableFuture();
def df2 = { env ->
return cf2;
} as DataFetcher

def fetchers = ["Query": ["hello": df1, "hello2": df2]]
def schema = TestUtil.schema(sdl, fetchers)
def graphQL = GraphQL.newGraphQL(schema).build()

def query = "{ hello hello2 }"
def ei = newExecutionInput(query).build()

List<RunningState> states = trackStates(ei)

when:
def er = graphQL.executeAsync(ei)
then:
states == [RUNNING, NOT_RUNNING]

when:
states.clear();
cf1.complete("world")

then:
states == [RUNNING, NOT_RUNNING]

when:
states.clear()
cf2.complete("world2")

then:
states == [RUNNING, NOT_RUNNING]
er.get().data == [hello: "world", hello2: "world2"]
}


def "engine running state is observed with one async datafetcher"() {
given:
def sdl = '''
Expand Down Expand Up @@ -88,6 +140,146 @@ class EngineRunningTest extends Specification {
er.get().data == [hello: "world"]
}

def "engine running state is observed with one dependent async datafetcher"() {
given:
def sdl = '''

type Query {
hello: String
}
'''
CompletableFuture cf = new CompletableFuture();
def df = { env ->
return cf.thenApply { it -> it }
} as DataFetcher
def fetchers = ["Query": ["hello": df]]
def schema = TestUtil.schema(sdl, fetchers)
def graphQL = GraphQL.newGraphQL(schema).build()

def query = "{ hello }"
def ei = newExecutionInput(query).build()

List<RunningState> states = trackStates(ei)

when:
def er = graphQL.executeAsync(ei)
then:
states == [RUNNING, NOT_RUNNING]

when:
states.clear();
cf.complete("world")

then:
er.get().data == [hello: "world"]
states == [RUNNING, NOT_RUNNING]
}


def "datafetcher failing with async exception handler"() {
given:
def sdl = '''

type Query {
hello: String
}
'''
def df = { env ->
throw new RuntimeException("boom")
} as DataFetcher

ReentrantLock reentrantLock = new ReentrantLock()
reentrantLock.lock();

def exceptionHandler = { param ->
def async = CompletableFuture.supplyAsync {
reentrantLock.lock();
return DataFetcherExceptionHandlerResult.newResult(GraphqlErrorBuilder
.newError(param.dataFetchingEnvironment).message("recovered").build()).build()
}
return async
} as DataFetcherExceptionHandler

def fetchers = ["Query": ["hello": df]]
def schema = TestUtil.schema(sdl, fetchers)
def graphQL = GraphQL.newGraphQL(schema).defaultDataFetcherExceptionHandler(exceptionHandler).build()

def query = "{ hello }"
def ei = newExecutionInput(query).build()

List<RunningState> states = trackStates(ei)

when:
def er = graphQL.executeAsync(ei)
states.clear()
reentrantLock.unlock()
def result = er.get()

then:
result.errors.collect { it.message } == ["recovered"]
// we expect simply going from running to finshed
states == [RUNNING, NOT_RUNNING]
}

def "async datafetcher failing with async exception handler"() {
given:
def sdl = '''

type Query {
hello: String
}
'''
def cf = new CompletableFuture();
def df = { env ->
return cf.thenApply { it -> throw new RuntimeException("boom") }
} as DataFetcher

ReentrantLock reentrantLock = new ReentrantLock()
reentrantLock.lock();

def exceptionHandler = { param ->
def async = CompletableFuture.supplyAsync {
reentrantLock.lock();
return DataFetcherExceptionHandlerResult.newResult(GraphqlErrorBuilder
.newError(param.dataFetchingEnvironment).message("recovered").build()).build()
}
return async
} as DataFetcherExceptionHandler

def fetchers = ["Query": ["hello": df]]
def schema = TestUtil.schema(sdl, fetchers)
def graphQL = GraphQL.newGraphQL(schema).defaultDataFetcherExceptionHandler(exceptionHandler).build()

def query = "{ hello }"
def ei = newExecutionInput(query).build()

List<RunningState> states = trackStates(ei)

when:
def er = graphQL.executeAsync(ei)

then:
states == [RUNNING, NOT_RUNNING]

when:
states.clear()
cf.complete("foo")

then:
states == [RUNNING, NOT_RUNNING]

when:
states.clear()
reentrantLock.unlock()
def result = er.get()

then:
result.errors.collect { it.message } == ["recovered"]
// we expect simply going from running to finshed
states == [RUNNING, NOT_RUNNING]
}


def "engine running state is observed with two async datafetcher"() {
given:
def sdl = '''
Expand Down
Loading