spring-projects / spring-security

Spring Security
http://spring.io/projects/spring-security
Apache License 2.0
8.73k stars 5.86k forks source link

Fix build error related to Mono.deferWithContext() #8881

Closed jgrandja closed 4 years ago

jgrandja commented 4 years ago

There was a recent change in reactor-core that is causing the snapshot build to fail.

Mono.deferWithContext() was changed from:

Mono.deferWithContext(Function<Context, ? extends Mono<? extends T>> supplier)

to:

Mono.deferWithContext(Function<ContextView, ? extends Mono<? extends T>> supplier)

ContextView was added after 3.4.0-M1 (or 2020.0.0-M1 release train).

Reference Issue reactor-core#2279 and commit.

As a temporary workaround, until a solution is provided, we will force snapshot to build against 2020.0.0-M1.

simonbasle commented 4 years ago

After looking at the two use sites of deferWithContext, I don't think you need it at all. The Context (or now ContextView) provided to the Consumer is only used for "reinjection", which is redundant:

Mono.empty()
    .switchIfEmpty(Mono.deferWithContext(ctx ->
        Mono.just("foo")
            //.otherOperatorsThatDontUseCtx()
            .subscriberContext(ctx)
    ));

is effectively the same as

Mono.empty()
    .switchIfEmpty(Mono.just("foo")/*.otherOperatorsThatDontUseCtx()*/);

in the sense that switchIfEmpty will make its own downstream Context visible to the Mono.just (or whatever fallback Mono you define).

jgrandja commented 4 years ago

@simonbasle Here is the line of code that was causing a compile error.

I was able to resolve the compile error by changing from:

Mono.deferWithContext(context ->

to:

Mono.defer(() -> Mono.subscriberContext().flatMap(context ->

The tests still pass with this change so I'm assuming this is equivalent behaviour.

This resolved the compile error but now I'm getting java.lang.NoClassDefFoundError: reactor/util/context/ContextView in our Spring Boot samples.

I tried updating the Spring Boot version to 2.4.0-SNAPSHOT, however, snapshot is building against 2020.0.0-M1.

As @rwinch mentioned, our goal is to remain passive when we release 5.4.0. Ultimately, we don't want to force users to upgrade to reactor-core:3.4.0. And since Spring Boot 2.4.0 is releasing after Spring Security 5.4.0, users will be required to explicitly define reactor-core:3.4.0 to make it work.

I'm wondering if you considered deprecating the existing operation as such:

@Deprecated
public static <T> Mono<T> deferWithContext(Function<Context, ? extends Mono<? extends T>> supplier) {
    ...
}

public static <T> Mono<T> deferWithContext(Function<ContextView, ? extends Mono<? extends T>> supplier) {
    ...
}

For reference, here is the complete stack trace:

java.lang.NoClassDefFoundError: reactor/util/context/ContextView
    at org.springframework.security.test.context.support.ReactorContextTestExecutionListener$DelegateTestExecutionListener.lambda$beforeTestMethod$0(ReactorContextTestExecutionListener.java:63)
    at reactor.core.publisher.Operators$LiftFunction.lambda$liftScannable$1(Operators.java:2466)
    at reactor.core.publisher.MonoLift.subscribeOrReturn(MonoLift.java:42)
    at reactor.core.publisher.Mono.subscribe(Mono.java:4204)
    at reactor.core.publisher.Mono.block(Mono.java:1702)
    at org.springframework.test.web.reactive.server.DefaultWebTestClient$DefaultRequestBodyUriSpec.exchange(DefaultWebTestClient.java:307)
    at sample.OAuth2ResourceServerControllerTests.indexGreetsAuthenticatedUser(OAuth2ResourceServerControllerTests.java:54)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
    at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
    at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:118)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
    at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:413)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
    at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ClassNotFoundException: reactor.util.context.ContextView
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
    ... 63 more
simonbasle commented 4 years ago

@jgrandja I'm aware of the line of code that caused an issue, and that is the one I'm talking about. The whole wrapping in a deferWithContext(context -> coupled with a .subscriberContext(context) is redundant. switchIfEmpty should already do that.

as for the boot samples, I tried to reproduce but couldn't find how. could you instruct me how to run one problematic test ? It sounds more like a build configuration issue though, right?

I assume you have reactorVersion=2020.0.0-SNAPSHOT in gradle.properties? Have you looked at gradle :spring-security-core:dependencyInsights --dependency reactor-bom ?

jgrandja commented 4 years ago

@simonbasle

The whole wrapping in a deferWithContext(context -> coupled with a .subscriberContext(context) is redundant.

You are right. I misinterpreted your previous comment. Thanks for the tip! I cleaned it up.

To reproduce the Boot samples issue, see this 104d785 for the exact command to run.

jgrandja commented 4 years ago

@simonbasle Here is a branch you can checkout and run this:

./gradlew test -PforceMavenRepositories=snapshot -PspringVersion='5.+' -PreactorVersion=20+ -PspringDataVersion=Lovelace-BUILD-SNAPSHOT -PrsocketVersion=1.1.0-SNAPSHOT -PlocksDisabled --stacktrace

This will trigger the NoClassDefFoundError.

One thing to note is that gradle.properties defines springBootVersion=2.4.0-M1 but I tested on 2.4.0-SNAPSHOT and still same issue.

rwinch commented 4 years ago

With the changes in https://github.com/reactor/reactor-core/pull/2294 our build is working with the latest reactor snapshots. Thanks to @simonbasle for all the help!