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

Add exception when tryWrite returns false #5557

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open

Conversation

pushrsp
Copy link

@pushrsp pushrsp commented Apr 2, 2024

Motivation:

tryWrite in GraphqlWSSubProtocol.writeNext(...) can return false when WebSocketWriter is closed which makes not to cancel the publisher and cleanup the resource.

Modifications:

  • When tryWrite returns false, it throws ClosedStreamException.
  • If ClosedStreamException is occurred, the exception handler in ExecutionResultSubscriber.onNext(...) can lead to cancel the publisher through subscription.cancel().

Result:

@CLAassistant
Copy link

CLAassistant commented Apr 2, 2024

CLA assistant check
All committers have signed the CLA.

Copy link

codecov bot commented Apr 2, 2024

Codecov Report

Attention: Patch coverage is 20.00000% with 4 lines in your changes are missing coverage. Please review.

Project coverage is 74.09%. Comparing base (b1a1044) to head (12451c9).
Report is 82 commits behind head on main.

Current head 12451c9 differs from pull request most recent head 73f4765

Please upload reports for the commit 73f4765 to get more accurate results.

Files Patch % Lines
...p/armeria/server/graphql/GraphqlWSSubProtocol.java 25.00% 3 Missing ⚠️
...eria/server/graphql/ExecutionResultSubscriber.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #5557      +/-   ##
============================================
- Coverage     74.10%   74.09%   -0.02%     
+ Complexity    21012    21007       -5     
============================================
  Files          1819     1819              
  Lines         77394    77399       +5     
  Branches       9889     9889              
============================================
- Hits          57353    57347       -6     
- Misses        15386    15396      +10     
- Partials       4655     4656       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ikhoon ikhoon added the defect label Apr 4, 2024
@@ -67,7 +68,7 @@ public void onNext(ExecutionResult executionResult) {
protocol.sendGraphqlErrors(executionResult.getErrors());
subscription.cancel();
}
} catch (JsonProcessingException e) {
} catch (JsonProcessingException | ClosedStreamException e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regardless of the exception type, it would be safe to cancel.

Suggested change
} catch (JsonProcessingException | ClosedStreamException e) {
} catch (Throwable e) {

Comment on lines 356 to 357
if (!out.tryWrite(event)) {
throw ClosedStreamException.get();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.write() will raise ClosedStreamException if it is closed.

Suggested change
if (!out.tryWrite(event)) {
throw ClosedStreamException.get();
out.write(event);

Should we also use write() in other places to cancel the publisher?
A considering point is that writeError() in completeWithError() may cause an error again because out was closed already. If writeNext() fails, we may skip writeError().

Copy link
Author

@pushrsp pushrsp Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also use write() in other places to cancel the publisher?

I think writeError should be considered to use write instead of tryWrite.

private static void writeError(WebSocketWriter out, String operationId, List<GraphQLError> errors)

A considering point is that writeError() in completeWithError() may cause an error again because out was closed already. If writeNext() fails, we may skip writeError().

I think we can fix like this below

private static void writeError(WebSocketWriter out, String operationId, Throwable t) {
        ...
        try {
            final String event = serializeToJson(errorResponse);
            logger.trace("ERROR: {}", event);
            out.write(event);
        } catch (Throwable e) {
            if (out.isOpen()) {
                logger.warn("Error serializing error event", e);
                out.close(e);
            }
        }
    }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may simply return in the begging of the method if t is an instance of ClosedStreamException

@pushrsp pushrsp requested a review from ikhoon April 5, 2024 06:48
Copy link
Member

@trustin trustin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind adding a test case for the problem you're trying to solve?

@pushrsp
Copy link
Author

pushrsp commented Apr 9, 2024

I'm trying to solve the problem to clean up the resources and cancel the publisher when WebSocketWriter was closed for some reasons. But I'm not sure how to test this problem. To test it, I need to close the WebSocketWriter and check if the publisher has been canceled.

@trustin
Copy link
Member

trustin commented Apr 10, 2024

@minwoox Any suggestions on testing strategy?
@pushrsp Could you fix build errors meanwhile? (checkstyle violations)

@github-actions github-actions bot added the Stale label May 13, 2024
@@ -352,7 +353,7 @@ private static void writeNext(WebSocketWriter out, String operationId, Execution
"payload", executionResult.toSpecification());
final String event = serializeToJson(response);
logger.trace("NEXT: {}", event);
out.tryWrite(event);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several tryWrite are still used in this class. Could you also fix it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, do i have to handle for ClosedStreamException in other place?

@github-actions github-actions bot removed the Stale label May 17, 2024
@minwoox
Copy link
Member

minwoox commented May 20, 2024

What do you think of merging ExecutionResultSubscriber and GraphqlSubProtocol to make it more readable? They don't need to be separate in the first place.

new ExecutionResultSubscriber(id, new GraphqlSubProtocol() {

@minwoox
Copy link
Member

minwoox commented May 20, 2024

Any suggestions on testing strategy?

I think we have to

  • Create the GraphqlService using builder
  • Create a mock request and call WebSocketHandler.handle()
  • Close the returned WebSocket
  • See if the StreamMessage is cancelled or not.
final StreamWriter<Object> streaming = StreamMessage.streaming();
final GraphqlService graphqlService =
        GraphqlService.builder()
                      .enableWebSocket(true)
                      .runtimeWiring(c -> {
                          c.type("Subscription",
                                 typeWiring -> typeWiring.dataFetcher("hello", e -> streaming));
                      })
                      .build();
assert graphqlService instanceof WebSocketHandler;
final WebSocket out = ((WebSocketHandler) graphqlService).handle(ctx, in);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Do not ignore the result of StreamMessage.tryWrite() in GraphqlWSSubProtocol
5 participants