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

Return a CompletableFuture from API methods that checks whether… #2275

Merged
merged 7 commits into from
Dec 11, 2019

Conversation

anuraaga
Copy link
Collaborator

@anuraaga anuraaga commented Nov 20, 2019

… blocking methods are called from an event loop or a non-blocking thread.

We have heard many stories of bad experiences because of blocking the event loop in an armeria server request. While we wouldn't be able to check all cases of this (e.g., calling JDBC from an event loop), it's fairly simple to check this for futures returned by Armeria methods and I think this covers a lot of cases. This changes to return a special implementation of CompletableFuture that logs from blocking methods with a stack trace.

Let me know any general feedback and then I'll apply to other methods (though I guess aggregate() is the most common way to get a future from Armeria).

@codecov
Copy link

codecov bot commented Nov 20, 2019

Codecov Report

Merging #2275 into master will decrease coverage by 0.01%.
The diff coverage is 94.11%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master    #2275      +/-   ##
============================================
- Coverage     73.67%   73.65%   -0.02%     
- Complexity    10168    10172       +4     
============================================
  Files           882      883       +1     
  Lines         38869    38891      +22     
  Branches       4813     4815       +2     
============================================
+ Hits          28637    28646       +9     
- Misses         7777     7790      +13     
  Partials       2455     2455
Impacted Files Coverage Δ Complexity Δ
...c/main/java/com/linecorp/armeria/common/Flags.java 62.18% <100%> (+0.48%) 57 <1> (+1) ⬆️
.../armeria/client/endpoint/DynamicEndpointGroup.java 91.66% <100%> (ø) 18 <0> (ø) ⬇️
...common/stream/AbstractStreamMessageDuplicator.java 78.43% <100%> (ø) 11 <0> (ø) ⬇️
...inecorp/armeria/server/file/StreamingHttpFile.java 53.04% <100%> (ø) 14 <0> (ø) ⬇️
.../java/com/linecorp/armeria/common/HttpRequest.java 73.73% <100%> (ø) 26 <1> (ø) ⬇️
...java/com/linecorp/armeria/common/HttpResponse.java 88.77% <100%> (ø) 34 <2> (ø) ⬇️
...rp/armeria/common/stream/StreamMessageDrainer.java 78.26% <100%> (ø) 7 <1> (ø) ⬇️
...dpoint/healthcheck/HealthCheckedEndpointGroup.java 78.43% <100%> (ø) 14 <0> (ø) ⬇️
...p/armeria/common/stream/AbstractStreamMessage.java 83.84% <100%> (ø) 26 <0> (ø) ⬇️
...ria/common/stream/PublisherBasedStreamMessage.java 81.75% <100%> (ø) 24 <1> (ø) ⬇️
... and 20 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4c3dfa0...42f067e. Read the comment docs.

@trustin
Copy link
Member

trustin commented Nov 25, 2019

Maybe a user can just use BlockHound? https://github.com/reactor/BlockHound
Netty integration is coming soon: netty/netty#9687

@anuraaga
Copy link
Collaborator Author

BlockHound seems cool and could be a best practice when running an Armeria app to catch other things like JDBC. But I think registering an agent is tedious enough that it could still be worth having something out of the box. The cost / performance of manually instrumenting these three common methods seems pretty good to me. What do you think?

@anuraaga
Copy link
Collaborator Author

anuraaga commented Dec 2, 2019

@trustin Any more thoughts on this? I notice there could be JDK version issues in the future with BlockHound - reactor/BlockHound#33. It's a very cool tool, but agents are so complex I think this is could be a small but effective improvement.

@trustin
Copy link
Member

trustin commented Dec 2, 2019

Sorry. Let me check soon. Didn't have a chance to check the code yet.

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.

Looks good all in all.

@anuraaga anuraaga marked this pull request as ready for review December 7, 2019 09:18
@anuraaga
Copy link
Collaborator Author

anuraaga commented Dec 7, 2019

I have switched to EventLoopCheckingCompletableFuture where we return a future to users. Though it might be simpler to just use it everywhere, let me know.

@anuraaga
Copy link
Collaborator Author

Hmm test doesn't seem to work well on Windows. Happen to have any ideas why that could be?

@trustin
Copy link
Member

trustin commented Dec 10, 2019

Maybe REPORTED_THREADS.add() returned false? How about using a different event loop for each test method?

@anuraaga
Copy link
Collaborator Author

Doh duh... thanks for finding it!

@trustin
Copy link
Member

trustin commented Dec 10, 2019

maybeLogIfOnEventLoop could be static by the way :-)

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.

Thanks!

@trustin trustin added this to the 0.98.0 milestone Dec 11, 2019
Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

This looks nice. Thanks!

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

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

I have left a humble opinion but mostly LGTM. Thanks a lot!

return;
}
final Thread thread = Thread.currentThread();
if (thread instanceof NonBlocking && REPORTED_THREADS.add(thread)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

IIUC this NonBlocking interface is created in the project reactor.

https://github.com/reactor/reactor-core/blob/628258f644bdddd095e72d1fc81c5d6ac0e3fa12/reactor-core/src/main/java/reactor/core/scheduler/ReactorThreadFactory.java#L92-L96

It seems an edge case but a non-eventloop thread might pass this condition and log this message. I am wondering if it is acceptable. Unless I suggest changing EventLoopCheckingCompletableFuture to NonBlockCheckingCompletableFuture and related message. 😀

Copy link
Member

Choose a reason for hiding this comment

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

Armeria EventLoopThread implements Nonblocking. 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I originally checked instanceof EventLoopThread but changed to NonBlocking I think to avoid moving a package-local EventLoopThread to public in internal package. @trustin Any thoughts? I lean towards it being small enough not to worry about

Copy link
Contributor

Choose a reason for hiding this comment

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

I know that :-) Just worried other non-event loop thread could implement NonBlocking.
But it seems too many worries. 😜

Copy link
Member

Choose a reason for hiding this comment

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

I think we can warn that as well because a thread which implements NonBlocking is doing blocking operation. 😄

Copy link
Member

Choose a reason for hiding this comment

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

Ah then it makes sense. 😄

Copy link
Member

Choose a reason for hiding this comment

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

So do we need to update the WARN log message?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not completely sure it is worth to update the message.
If we do, then leave the original sentence and put extra information just in case. 😀

- Calling a blocking method on CompletableFuture from an event loop thread.
+ Calling a blocking method on CompletableFuture from an event loop thread or non-blocking thread.

Copy link
Member

Choose a reason for hiding this comment

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

@anuraaga Could you update the log message?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sure

@trustin trustin changed the title Return a CompletableFuture from API methods that checks whether block… Return a CompletableFuture from API methods that checks whether… Dec 11, 2019
@trustin trustin merged commit 3b52ab6 into line:master Dec 11, 2019
fmguerreiro pushed a commit to fmguerreiro/armeria that referenced this pull request Sep 19, 2020
…e#2275)

… blocking methods are called from an event loop or a non-blocking thread.

We have heard many stories of bad experiences because of blocking the event loop in an armeria server request. While we wouldn't be able to check all cases of this (e.g., calling JDBC from an event loop), it's fairly simple to check this for futures returned by Armeria methods and I think this covers a lot of cases.  This changes to return a special implementation of `CompletableFuture` that logs from blocking methods with a stack trace.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants