-
Notifications
You must be signed in to change notification settings - Fork 3k
CredentialsProvider API should be asynchronous #50047
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
01ba773
to
54e8cad
Compare
This comment has been minimized.
This comment has been minimized.
CI errors seem to be related? |
I like the change (and actually need it for the mailer), but I think a few usages have been overlooked. |
*/ | ||
default Uni<Map<String, String>> getCredentialsAsync(String credentialsProviderName) { | ||
return Uni.createFrom().item(() -> getCredentials(credentialsProviderName)) | ||
.runSubscriptionOn(Infrastructure.getDefaultExecutor()); |
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'm not sure it is correct, this is not where the subscription should be enabled, as the consumer of the api may be using another pool, this is probably what is causing the test failures...
I believe the default
implementation should be return Uni.createFrom().item(getCredentials(credentialsProviderName));
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'm not sure what you mean with subscription enabled.
Subscription does not happen here. runSubscriptionOn
operator configures the Uni
so that, when the subscription happens, it happens on the default executor (a Quarkus worker thread).
Since existing getCrendentials
implementation are synchronous, it is safer to invoke the method on a worker thread.
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.
@tsegismont Sorry, I meant to say, that this is not a concern of this api method. It is a problem for the consumers of this api.
I'm 100% sure tests will pass without it. Otherwise it becomes a breaking change unfortunately.
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.
To avoid some embarassment, let me say I'm quite sure it will be fixed without it, rather than quoting 100% 🙂.
I support this PR
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 problem was not with the default implementation of getCredentialsAsync
, but with the synchronous nature of io.quarkus.oidc.common.runtime.OidcCommonUtils#fromCredentialsProvider
even though this method is invoked on an event loop thread.
I reverted the change in this method and added a comment. Ideally, the problem would be addressed in a follow-up PR, but I'm not knowledgeable with this area. I could help with a PR review though
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 @tsegismont.
When I get to that, I'll definitely cc you.
IMHO though, it would still be better to avoid setting a particular thread pool in the default implementation, since its role is to avoid breaking existing code, not to offer an actual async implementation.
Custom extensions or code that has no async CP requirements or immediate capabilites may have no other option but to keep the deprecated method ref.
IMHO your original update to OidcCommonUtils should've worked as you typed it.
In any case, thanks for the PR
Thanks for the feedback, I'm looking into the failures now |
54e8cad
to
4e62892
Compare
@Deprecated | ||
default Map<String, String> getCredentials(String credentialsProviderName) { |
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'm not sure it's a great idea to deprecate the blocking variant considering some are allergic to reactive and would have a blocking implementation anyway.
Keeping both methods would be confusing though. I suppose the alternative would be to have two interfaces, and creating adapters depending on what the consumer that calls the provider prefers (blocking or reactive)?
Anyway, I suppose you discussed this with @cescoffier , so +0 from me.
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 discussion started from #31873 (comment)
The problem we're trying to address is twofold:
- there are implementations of
CredentialsProvider
that rely on async code, and they must block until the result is available - there are extensions that call the currently synchronous contract of
CredentialsProvider
from an event loop thread (which is bad because it can block the event loop)
Having a single async method allows to solve both problems, but it is true that some people prefer to stay away from reactive. A separate interface (BlockingCredentialsProvider
?) could do the trick. Are there other places in Quarkus where user-provided code can be invoked from an extension, possibly from an event loop? I'm asking because if the problem has been solved elsewhere we can inspire from the solution.
Thoughts @cescoffier ?
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.
@tsegismont Hi,
Having a single async method allows to solve both problems, but it is true that some people prefer to stay away from reactive. A separate interface (BlockingCredentialsProvider ?) could do the trick.
It is certainly not me :-), I'll have no problems updating the OIDC code (not immediately, but I opened an issue with CC to you), I think your new interface method is perfect, my only reservation is that if someone has a blocking call working for whatever reason, then, ideally, the same blocking call expressed in Uni await().indefinitely()
terms should also work to allow a simple move away from the deprecated method.
What if the the consumer of the API already running on a ManagedExecutor worker thread, in a blocking mode, where a custom CP can have some request scoped bean injected...
In any case, apologies, I'm not really an expert in how to link various threads together across all the chain call, so please ignore me if I talk some nonsense :-)
I'm going quiet now, as I'm in Out of office mode
Thanks
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.
Sorry, 1 more comment, I'm not 100% sure one more interface is necessary, as we discussed it before with Clement, Roberto, since this interface is not part of the Quarkus Configuration system. Ideally, some dynamism would be added to the ConfigSource or other smallrye-config specific provider if refreshing secrets is necessary...
Cheers
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.
My point was that users would not necessarily want to write reactive code for this -- especially if their credentials retrieval code is blocking anyway.
Now if we consider this is more of an SPI, where implementors would be extension maintainers (e.g. Vault, AWS, etc.) and that users have no business writing their own... Then I agree reactive-only is fine.
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.
My point was that users would not necessarily want to write reactive code for this -- especially if their credentials retrieval code is blocking anyway.
Right
Now if we consider this is more of an SPI, where implementors would be extension maintainers (e.g. Vault, AWS, etc.) and that users have no business writing their own... Then I agree reactive-only is fine.
In most cases, users don't have to implement their own, but it happens https://quarkus.io/guides/credentials-provider#custom-credentials-provider
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 would not deprecate, but give the choice.
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 @tsegismont
This comment has been minimized.
This comment has been minimized.
@yrodiere any last comments? |
Still this for me:
|
So, no I discovered the PR. |
The implementation does exactly this, so we're good on that front. No problem, I'll remove the deprecation part |
4e62892
to
04136cd
Compare
@cescoffier PTAL |
This comment has been minimized.
This comment has been minimized.
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.
For me the question is how the credential provider is intended to be implemented and used.
For the usage, generalizing async and awaiting if needed is accepetable.
However, to do that, the provider needs to be implemented using an async API.
I would not deprecate the blocking API, but instead try to find a way to have both, with one using the other if needed. We cool imagine adding a new interface for async credential providers if needed.
For blocking (or synchronous) async provider, extensions should be careful and call them from a different thread if they are on the event loop.
04136cd
to
9097171
Compare
@cescoffier in the last commit, I removed the deprecation and the synchronous method is invoked from the async one (as discussed in #32021 (comment)) So, if I understand correctly, you changed your mind and would prefer separate interfaces for async/sync credential providers? It's fine with me, I just want to make sure we agree on the desired implementation before changing the PR. |
This comment has been minimized.
This comment has been minimized.
Closes quarkusio#32021 By default, the synchronous method is invoked by the asynchronous variant on a worker thread. Extensions must be updated to invoke the asynchronous variant.
9097171
to
1c46002
Compare
Status for workflow
|
Can we have both in the same interface? |
Yes, this is what this PR does |
The synchronous method is deprecated and is invoked by the asynchronous variant.
Extensions must be updated to invoke the asynchronous variant.