-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
ContextNotActiveException
in SecurityIdentityAugmentor
since Quarkus 3.10
#41081
Comments
/cc @geoand (kotlin), @sberyozkin (security) |
Hi, By just eyeballing the code it's extremely hard to spot what's going on. |
@mschorsch It does look like that accessing cache now requires a request context, which is not available in Can you try |
Hi @sberyozkin , thanks for your reply. The exception is only thrown on the second access of the cache, i.e. when the cache is updated.
Yes sure, thanks for the hint. Can you give me a hint which instance should be injected? In the example the SecurityIdentity is mentioned, what would I have to change in my case? Which method should I set |
Hi @mschorsch Refactor the code above as shown in the example, have security augmentation done in a supplier, etc |
@sberyozkin Thank you for your help. I understand the documentation and what it is supposed to achieve. However, I don't understand how this would help in my use case, nor how to implement it. Contrary to my intuition, I still tried to adapt the example in the documentation to my use case, but unfortunately this did not help. Fortunately, I managed to create a simple reproducer (cc @geoand). I hope this helps with the analysis. Thanks again. Steps to reproduce
|
I could not reproduce this on my machine, but I have a suggestion: Try using the programmatic API Quarkus provides for accessing the cache instead of using Caffeine directly. The former should properly handle the context switches which the latter knows nothing about. |
@geoand You are right, I had only tested under Windows. The case could not be reproduced under Linux. For this reason, I have adjusted the ‘Steps to reproduce’. With the change, the case can be reproduced without any problems. Can you please test it again? |
I tried and still could not get it to fail. Regardless however, please take a look at my suggestion above. |
@geoand I tried your suggestion with the following change in my reproducer an unfortunaly it doesnt't change anything, i still get the Exception: @ApplicationScoped
class MySecurityIdentityAugmentor @Inject constructor(
@RestClient private val exampleRestClient: ExampleRestClient,
) : SecurityIdentityAugmentor {
@Inject
@CacheName("my-cache")
lateinit var cache: Cache
@Inject
lateinit var identitySupplierInstance: Instance<SecurityIdentitySupplier>
override fun augment(identity: SecurityIdentity, context: AuthenticationRequestContext): Uni<SecurityIdentity> {
val identitySupplier = identitySupplierInstance.get()
identitySupplier.setIdentity(identity)
return getNonBlockingExpensiveValue("test").map { _ ->
identitySupplier.get()
}
}
private fun getNonBlockingExpensiveValue(key: String): Uni<String> {
return cache.getAsync(key) { _ ->
exampleRestClient.getHtml()
}
}
}
@Dependent
class SecurityIdentitySupplier : Supplier<SecurityIdentity> {
private var identity: SecurityIdentity? = null
@ActivateRequestContext
override fun get(): SecurityIdentity {
return QuarkusSecurityIdentity.builder(identity)
.addRole("user")
.build()
}
fun setIdentity(identity: SecurityIdentity) {
this.identity = identity
}
} # application.yaml
quarkus.rest-client."example-client".url=https://www.google.com
quarkus.cache.caffeine."my-cache".expire-after-write=PT0.1S With the reproducer described in #41081 (comment) i can reliable reproduce the problem. |
Thanks for checking. I'll have a look tomorrow. |
@geoand Thanks I've updated the reproducer:
|
I was able to trigger the issue (some of the time) using the steps above. |
It seems like the problem starts occuring in That means we need to do a git bisect between those two version and figure out which commit introduces the problem. P.S. I made one small change to the reproducer: @ApplicationScoped
class MySecurityIdentityAugmentor(
@RestClient private val exampleRestClient: ExampleRestClient,
) : SecurityIdentityAugmentor {
@Inject
@CacheName("my-cache")
lateinit var cache: Cache
@Inject
lateinit var identitySupplierInstance: Instance<SecurityIdentitySupplier>
// private val cache: LoadingCache<String, Uni<String>> = Caffeine.newBuilder()
// .expireAfterWrite(Duration.ofMillis(100L))
// .build { _ ->
// exampleRestClient.getHtml().memoize().indefinitely()
// }
override fun augment(identity: SecurityIdentity, context: AuthenticationRequestContext): Uni<SecurityIdentity> {
val identitySupplier = identitySupplierInstance.get()
identitySupplier.setIdentity(identity)
return getNonBlockingExpensiveValue("test").map { _ ->
identitySupplier.get()
}
}
private fun getNonBlockingExpensiveValue(key: String): Uni<String> {
return cache.getAsync(key) { _ ->
exampleRestClient.getHtml()
}
// return cache.get(key)
}
}
@Dependent
class SecurityIdentitySupplier : Supplier<SecurityIdentity> {
private var identity: SecurityIdentity? = null
// @ActivateRequestContext
override fun get(): SecurityIdentity {
return QuarkusSecurityIdentity.builder(identity)
.addRole("user")
.build()
}
fun setIdentity(identity: SecurityIdentity) {
this.identity = identity
}
} |
Thanks @geoand for checking. This issue is a blocker for us and prevents us from upgrading to quarkus > 3.10.0. It would be great If someone could take a look at this. |
Inspired from quarkus/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheResultInterceptor.java Lines 79 to 121 in 59bad99
import io.quarkus.cache.Cache
import io.quarkus.cache.CacheName
import io.quarkus.security.identity.AuthenticationRequestContext
import io.quarkus.security.identity.SecurityIdentity
import io.quarkus.security.identity.SecurityIdentityAugmentor
import io.quarkus.security.runtime.QuarkusSecurityIdentity
import io.smallrye.mutiny.Uni
import io.vertx.core.Context
import io.vertx.core.Vertx
import io.vertx.core.impl.ContextInternal
import jakarta.enterprise.context.ApplicationScoped
import jakarta.enterprise.context.Dependent
import jakarta.inject.Inject
import org.eclipse.microprofile.rest.client.inject.RestClient
@ApplicationScoped
internal class MySecurityIdentityAugmentor @Inject constructor(
private val examexampleService: ExampleService,
) : SecurityIdentityAugmentor {
override fun augment(identity: SecurityIdentity, context: AuthenticationRequestContext): Uni<SecurityIdentity> {
return examexampleService.getHtml("test").map { _ ->
QuarkusSecurityIdentity.builder(identity)
.addRole("user")
.build()
}
}
}
@Dependent
internal class ExampleService(
@RestClient private val exampleRestClient: ExampleRestClient,
@CacheName("my-cache") private val cache: Cache,
) {
fun getHtml(key: String): Uni<String> {
val context: Context? = Vertx.currentContext()
return cache.getAsync(key) { exampleRestClient.getHtml() }
// Stolen from https://github.com/quarkusio/quarkus/blob/59bad99a294b35e00d7e115e38bf67c4509f60de/extensions/cache/runtime/src/main/java/io/quarkus/cache/runtime/CacheResultInterceptor.java#L79-L121
.emitOn { command ->
// We need make sure we go back to the original context when the cache value is computed.
// Otherwise, we would always emit on the context having computed the value, which could
// break the duplicated context isolation.
val ctx: Context? = Vertx.currentContext()
if (context == null) {
// We didn't capture a context
if (ctx == null) {
// We are not on a context => we can execute immediately.
command.run()
} else {
// We are on a context.
// We cannot continue on the current context as we may share a duplicated context.
// We need a new one. Note that duplicate() does not duplicate the duplicated context,
// but the root context.
(ctx as ContextInternal).duplicate().runOnContext { command.run() }
}
} else {
// We captured a context.
if (ctx == context) {
// We are on the same context => we can execute immediately
command.run()
} else {
// 1) We are not on a context (ctx == null) => we need to switch to the captured context.
// 2) We are on a different context (ctx != null) => we need to switch to the captured context.
context.runOnContext { command.run() }
}
}
}
}
} The declarative approach with |
Don't know anymore if this is a bug not it is not easy to understand. |
Thanks for looking into it! I still think it's worth finding out which commits between |
Thanks @geoand @mschorsch. It may be nothing to do with Cache, I wonder if an update to ArcThreadsetupAction might be related. Let me CC CDI experts, @mkouba, @manovotn |
The problem is that the quarkus cache with annotations behaves differently than the programmatic approach. Using the annotation based approach restores the context correctly while in the case of the programmatic approach the user (developer) has to take care of it. Ideally, both approaches behave in the same way. Before quarkus 3.10.1 both approaches showed the same behavior. |
Yes, we should definitely fix the behavior of the programmatic approach |
@gwenneg I think we should move the handling of the context from the interceptor to the cache itself - WDYT? |
@geoand This is
While it pinpoints the commit, it doesn't really tell me much about the underlying cause as I am unfamiliar with how these related extensions juggle with contexts :-/ |
@gwenneg can you comment on this? Thanks |
Sorry, I only noticed this now. I'm reading all comments. |
IIUC, we're talking about moving from |
Yes, that's the idea |
Describe the bug
Since Quarkus 3.10 we get a
ContextNotActiveException
in our ownSecurityIdentityAugmentor
.We have not yet been able to create a reproducer, but we hope that a Quarkus expert can help with the following sample code. We were able to narrow down the problem, the exception always occurs when the cache is updated.
Expected behavior
No response
Actual behavior
No response
How to Reproduce?
No response
Output of
uname -a
orver
Linux
Output of
java -version
Java 21
Quarkus version or git rev
Quarkus 3.11.1
Build tool (ie. output of
mvnw --version
orgradlew --version
)No response
Additional information
No response
The text was updated successfully, but these errors were encountered: