-
-
Notifications
You must be signed in to change notification settings - Fork 102
[4.2] Prevent context switching during id generation #2793
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
base: main
Are you sure you want to change the base?
Conversation
For now we use it only when the connection is used
Now the test will throw an exception if context and connection aren't handled correctly.
This commit: * Stop connection leaks in BlockingIdentifierGenerator * Assert that the connection is used in the expected context
yrodiere
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @DavideD @tsegismont!
| } | ||
| } | ||
|
|
||
| public static void assertCurrentContextMatches(Object object, ContextInternal expectedContext, Exception creationTrace) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this new assertion, do we still need assertCurrentThreadMatches? If not, fixing #2736 would be trivial...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know :)
Seems like it, but I feel that this PR is complicated enough. I will look into it as soon as we merge this.
hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientPool.java
Outdated
Show resolved
Hide resolved
| static { | ||
| try { | ||
| MethodHandles.Lookup lookup = MethodHandles.lookup(); | ||
| OPENED_HANDLE = lookup.findVarHandle( ProxyConnection.class, "opened", boolean.class ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is new to me, but it looks like a common pattern in CompletableFuture. I guess the idea is to get access to compareAndSet without the overhead of AtomicBoolean. Whether this overhead is significant in this specific case is quite questionable if you ask me, but I assume Thomas knows better than I do.
In any case, back to your question @DavideD: this kind of "reflection" in a static block should generally be fine in Quarkus even with native compilation, because it's executed during static init, before native compilation. If I turn out to be wrong, we'll improvise :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea was that, since the application will create a lot of these objects during its lifetime, it would be better to store a simple boolean field in ProxyConnection instead of a complete object (own headers + field).
@franz1981 would you recommend against it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In any case, back to your question @DavideD: this kind of "reflection" in a
staticblock should generally be fine in Quarkus even with native compilation, because it's executed during static init, before native compilation. If I turn out to be wrong, we'll improvise :)
Thanks, that's all I needed in writing :D
| public ProxyConnection withBatchSize(int batchSize) { | ||
| connectionFuture.thenApply( reactiveConnection -> reactiveConnection.withBatchSize( batchSize ) ); | ||
| return this; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems wrong?
The method is supposed to return a batched connection, and from what I can see we rely on the return value:
Lines 1753 to 1757 in 066917f
| @Override | |
| public void setBatchSize(Integer batchSize) { | |
| setJdbcBatchSize( batchSize ); | |
| reactiveConnection = reactiveConnection.withBatchSize( batchSize ); | |
| } |
Returning this will not work because we don't update the delegate, so if the caller uses this, they'll still be using a non-batching connection.
I'll assume the contract is that this can be ignored after this method is called, and only the return value will be used by the caller. Which seems consistent with what I'm seeing in BatchedConnection#withBatchSize(), where we brutally change the batch size of an existing instance.
If that assumption is correct, I'd suggest replacing this code with something like:
| public ProxyConnection withBatchSize(int batchSize) { | |
| connectionFuture.thenApply( reactiveConnection -> reactiveConnection.withBatchSize( batchSize ) ); | |
| return this; | |
| } | |
| public ProxyConnection withBatchSize(int batchSize) { | |
| ReactiveConnection reactiveConnection = connectionFuture.getNow( null ); | |
| if (reactiveConnection != null) { | |
| // Common case (hopefully): connection was already opened, | |
| // we can let callers use the delegate going forward and forget about the proxy. | |
| return reactiveConnection.withBatchSize(batchSize); | |
| } | |
| else { | |
| // Uncommon case (hopefully): connection wasn't opened yet, | |
| // we will ask callers to use a new proxy that wraps the connection. | |
| return new ProxyConnection( () -> connectionSupplier.get().withBatchSize( batchSize ) ); | |
| } | |
| } |
NOTE: If you add something to mark the proxy as closed (see my comment on close), it would be a good idea mark the current proxy as closed in the else block here, just in case someone (incorrectly) goes against the assumption that I stated above.
| return opened | ||
| ? connectionFuture.getNow( null ).close() | ||
| : voidFuture(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we remember that close was called in the proxy, so that if another method is called on the proxy, we don't mistakenly create the delegate?
I'm talking about the second case in particular:
- Create connection proxy
- Call close(): nothing happens
- Call
execute(...): delegate gets created! Instead of erroring out.
| // We are not using transactions on purpose here, because this approach will cause a context switch | ||
| // and an assertion error if things aren't handled correctly. See Hibernate Reactive issue #2768: | ||
| // https://github.com/hibernate/hibernate-reactive/issues/2768 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to duplicate the test, to also test the "transaction" case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it makes sense.
| nextHiValue( connectionSupplier ) | ||
| .whenComplete( (newlyGeneratedHi, throwable) -> { | ||
| if ( throwable != null ) { | ||
| result.completeExceptionally( throwable ); | ||
| } | ||
| else { | ||
| //We ignore the state argument as we actually use the field directly | ||
| //for convenience, but they are the same object. | ||
| executor.submit( stateIgnored -> { | ||
| result.complete( next( newlyGeneratedHi ) ); | ||
| return null; | ||
| } ); | ||
| } | ||
| } ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure nextHiValue will never throw an exception? I mean the method, not the future it returns.
Because if it does throw an exception, the whenComplete call here will be skipped and the operation will hang forever from the user's point of view.
e4fdfd4 to
9dce31a
Compare
Fix #2768
Follows #2690 and it's a refactoring of @tsegismont suggestion.