Skip to content

Conversation

tsegismont
Copy link
Contributor

@tsegismont tsegismont commented Sep 13, 2025

The synchronous method is deprecated and is invoked by the asynchronous variant.

Extensions must be updated to invoke the asynchronous variant.

This comment has been minimized.

@gastaldi gastaldi requested a review from sberyozkin September 14, 2025 13:19
@gastaldi
Copy link
Contributor

CI errors seem to be related?

@cescoffier
Copy link
Member

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());
Copy link
Member

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));

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Member

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

Copy link
Contributor Author

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

Copy link
Member

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

@tsegismont
Copy link
Contributor Author

tsegismont commented Sep 15, 2025

I like the change (and actually need it for the mailer), but I think a few usages have been overlooked.

Thanks for the feedback, I'm looking into the failures now

@tsegismont tsegismont force-pushed the async-credentials-api branch from 54e8cad to 4e62892 Compare September 15, 2025 09:21
@geoand geoand requested a review from yrodiere September 15, 2025 09:23
@Deprecated
default Map<String, String> getCredentials(String credentialsProviderName) {
Copy link
Member

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.

Copy link
Contributor Author

@tsegismont tsegismont Sep 15, 2025

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 ?

Copy link
Member

@sberyozkin sberyozkin Sep 15, 2025

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

Copy link
Member

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

Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Member

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.

Copy link
Member

@sberyozkin sberyozkin left a 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.

@geoand
Copy link
Contributor

geoand commented Sep 18, 2025

@yrodiere any last comments?

@yrodiere
Copy link
Member

@yrodiere any last comments?

Still this for me:

Anyway, I suppose you discussed this with @cescoffier , so +0 from me.

@cescoffier
Copy link
Member

So, no I discovered the PR.
As I said as comment, I would not deprecate the blocking API, but implement one on top of the other (using a default method).

@tsegismont
Copy link
Contributor Author

So, no I discovered the PR. As I said as comment, I would not deprecate the blocking API, but implement one on top of the other (using a default method).

The implementation does exactly this, so we're good on that front.

No problem, I'll remove the deprecation part

@tsegismont tsegismont force-pushed the async-credentials-api branch from 4e62892 to 04136cd Compare September 18, 2025 16:57
@tsegismont
Copy link
Contributor Author

@cescoffier PTAL

This comment has been minimized.

Copy link
Member

@cescoffier cescoffier left a 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.

@tsegismont tsegismont force-pushed the async-credentials-api branch from 04136cd to 9097171 Compare September 19, 2025 07:41
@tsegismont
Copy link
Contributor Author

@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.

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.
@tsegismont tsegismont force-pushed the async-credentials-api branch from 9097171 to 1c46002 Compare September 19, 2025 12:28
Copy link

quarkus-bot bot commented Sep 19, 2025

Status for workflow Quarkus CI

This is the status report for running Quarkus CI on commit 1c46002.

✅ The latest workflow run for the pull request has completed successfully.

It should be safe to merge provided you have a look at the other checks in the summary.

You can consult the Develocity build scans.


Flaky tests - Develocity

⚙️ Maven Tests - JDK 17 Windows

📦 integration-tests/devmode

io.quarkus.test.devui.DevUIGrpcSmokeTest.testTestService - History

  • Too many recursions, message not returned for id [1277288190] - java.lang.RuntimeException
java.lang.RuntimeException: Too many recursions, message not returned for id [1277288190]
	at io.quarkus.devui.tests.DevUIJsonRPCTest.objectResultFromJsonRPC(DevUIJsonRPCTest.java:175)
	at io.quarkus.devui.tests.DevUIJsonRPCTest.objectResultFromJsonRPC(DevUIJsonRPCTest.java:178)
	at io.quarkus.devui.tests.DevUIJsonRPCTest.objectResultFromJsonRPC(DevUIJsonRPCTest.java:178)
	at io.quarkus.devui.tests.DevUIJsonRPCTest.objectResultFromJsonRPC(DevUIJsonRPCTest.java:178)
	at io.quarkus.devui.tests.DevUIJsonRPCTest.objectResultFromJsonRPC(DevUIJsonRPCTest.java:178)
	at io.quarkus.devui.tests.DevUIJsonRPCTest.objectResultFromJsonRPC(DevUIJsonRPCTest.java:178)
	at io.quarkus.devui.tests.DevUIJsonRPCTest.objectResultFromJsonRPC(DevUIJsonRPCTest.java:178)

📦 integration-tests/maven

io.quarkus.maven.it.DevMojoIT.testClassLoaderLinkageError - History

  • Condition with Lambda expression in io.quarkus.test.devmode.util.DevModeClient was not fulfilled within 1 minutes. - org.awaitility.core.ConditionTimeoutException
org.awaitility.core.ConditionTimeoutException: Condition with Lambda expression in io.quarkus.test.devmode.util.DevModeClient was not fulfilled within 1 minutes.
	at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
	at org.awaitility.core.CallableCondition.await(CallableCondition.java:78)
	at org.awaitility.core.CallableCondition.await(CallableCondition.java:26)
	at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1160)
	at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1129)
	at io.quarkus.test.devmode.util.DevModeClient.getHttpResponse(DevModeClient.java:164)
	at io.quarkus.test.devmode.util.DevModeClient.getHttpResponse(DevModeClient.java:155)

io.quarkus.maven.it.DevMojoIT.testClassLoaderLinkageError - History

  • Condition with Lambda expression in io.quarkus.test.devmode.util.DevModeClient was not fulfilled within 1 minutes. - org.awaitility.core.ConditionTimeoutException
org.awaitility.core.ConditionTimeoutException: Condition with Lambda expression in io.quarkus.test.devmode.util.DevModeClient was not fulfilled within 1 minutes.
	at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
	at org.awaitility.core.CallableCondition.await(CallableCondition.java:78)
	at org.awaitility.core.CallableCondition.await(CallableCondition.java:26)
	at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1160)
	at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1129)
	at io.quarkus.test.devmode.util.DevModeClient.getHttpResponse(DevModeClient.java:164)
	at io.quarkus.test.devmode.util.DevModeClient.getHttpResponse(DevModeClient.java:155)

@cescoffier
Copy link
Member

Can we have both in the same interface?

@tsegismont
Copy link
Contributor Author

Can we have both in the same interface?

Yes, this is what this PR does

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.

CredentialsProvider API should be asynchronous
6 participants