Skip to content
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

HttpOperations#initShortId() can suffer from ClassCastException #3541

Closed
joschi opened this issue Dec 11, 2024 · 1 comment · Fixed by #3542
Closed

HttpOperations#initShortId() can suffer from ClassCastException #3541

joschi opened this issue Dec 11, 2024 · 1 comment · Fixed by #3542
Assignees
Labels
type/bug A general bug
Milestone

Comments

@joschi
Copy link
Contributor

joschi commented Dec 11, 2024

We are running a comparatively small application using Spring Boot 3.4.0 with Reactor BOM 2024.0.0 and are experiencing the following exception:

java.lang.ClassCastException: class reactor.netty.channel.ChannelOperations$DisposedConnection cannot be cast to class java.util.concurrent.atomic.AtomicLong (reactor.netty.channel.ChannelOperations$DisposedConnection is in unnamed module of loader org.springframework.boot.loader.launch.LaunchedClassLoader @e9e54c2; java.util.concurrent.atomic.AtomicLong is in module java.base of loader 'bootstrap')
	at reactor.netty.http.HttpOperations.initShortId(HttpOperations.java:381)
	at reactor.netty.channel.ChannelOperations.asShortText(ChannelOperations.java:609)
	at org.springframework.http.server.reactive.ReactorServerHttpRequest.initLogPrefix(ReactorServerHttpRequest.java:140)
Full exception stack trace
java.lang.ClassCastException: class reactor.netty.channel.ChannelOperations$DisposedConnection cannot be cast to class java.util.concurrent.atomic.AtomicLong (reactor.netty.channel.ChannelOperations$DisposedConnection is in unnamed module of loader org.springframework.boot.loader.launch.LaunchedClassLoader @e9e54c2; java.util.concurrent.atomic.AtomicLong is in module java.base of loader 'bootstrap')
	at reactor.netty.http.HttpOperations.initShortId(HttpOperations.java:381)
	at reactor.netty.channel.ChannelOperations.asShortText(ChannelOperations.java:609)
	at org.springframework.http.server.reactive.ReactorServerHttpRequest.initLogPrefix(ReactorServerHttpRequest.java:140)
	at org.springframework.http.server.reactive.AbstractServerHttpRequest.getLogPrefix(AbstractServerHttpRequest.java:242)
	at org.springframework.http.server.reactive.ReactorHttpHandlerAdapter.lambda$apply$1(ReactorHttpHandlerAdapter.java:67)
	at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onComplete(MonoPeekTerminal.java:289)
	at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onComplete(MonoPeekTerminal.java:299)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:210)
	at reactor.core.publisher.Operators.complete(Operators.java:137)
	at reactor.core.publisher.MonoEmpty.subscribe(MonoEmpty.java:46)
	at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:241)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:204)
	at reactor.core.publisher.Operators.complete(Operators.java:137)
	at reactor.core.publisher.MonoEmpty.subscribe(MonoEmpty.java:46)
	at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53)
	at reactor.core.publisher.Mono.subscribe(Mono.java:4576)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:265)
	at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:204)
	at reactor.core.publisher.FluxTap$TapSubscriber.onComplete(FluxTap.java:286)
	at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2231)
	at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onComplete(MonoPeekTerminal.java:299)
	at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2231)
	at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onComplete(MonoPeekTerminal.java:299)
	at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2231)
	at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onComplete(MonoPeekTerminal.java:299)
	at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2231)
	at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onComplete(MonoPeekTerminal.java:299)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:250)
	at reactor.core.publisher.MonoFlatMap$FlatMapInner.onComplete(MonoFlatMap.java:324)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:250)
	at reactor.core.publisher.MonoFlatMap$FlatMapInner.onComplete(MonoFlatMap.java:324)
	at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2231)
	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onComplete(FluxOnAssembly.java:549)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:250)
	at reactor.core.publisher.MonoFlatMap$FlatMapInner.onComplete(MonoFlatMap.java:324)
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onComplete(FluxContextWrite.java:126)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:250)
	at reactor.core.publisher.MonoFlatMap$FlatMapInner.onComplete(MonoFlatMap.java:324)
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onComplete(FluxContextWrite.java:126)
	at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onComplete(MonoPeekTerminal.java:299)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:250)
	at reactor.core.publisher.MonoFlatMap$FlatMapInner.onComplete(MonoFlatMap.java:324)
	at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onComplete(FluxPeekFuseable.java:277)
	at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onComplete(MonoPeekTerminal.java:299)
	at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onComplete(MonoIgnoreElements.java:89)
	at reactor.core.publisher.FluxConcatArray$ConcatArraySubscriber.onComplete(FluxConcatArray.java:209)
	at reactor.core.publisher.FluxConcatArray.subscribe(FluxConcatArray.java:79)
	at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165)
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
	at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2571)
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.request(FluxContextWrite.java:136)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.request(MonoFlatMap.java:194)
	at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.request(MonoPeekTerminal.java:139)
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.request(FluxContextWrite.java:136)
	at reactor.core.publisher.MonoFlatMap$FlatMapInner.onSubscribe(MonoFlatMap.java:291)
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onSubscribe(FluxContextWrite.java:101)
	at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onSubscribe(MonoPeekTerminal.java:152)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onSubscribe(MonoFlatMap.java:117)
	at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onSubscribe(FluxContextWrite.java:101)
	at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:55)
	at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165)
	at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74)
	at reactor.core.publisher.Operators$MonoInnerProducerBase.complete(Operators.java:2864)
	at reactor.core.publisher.MonoSingle$SingleSubscriber.onComplete(MonoSingle.java:180)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onComplete(FluxMapFuseable.java:152)
	at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2573)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:171)
	at reactor.core.publisher.MonoSingle$SingleSubscriber.doOnRequest(MonoSingle.java:103)
	at reactor.core.publisher.Operators$MonoInnerProducerBase.request(Operators.java:2931)
	at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2367)
	at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2241)
	at reactor.core.publisher.MonoSingle$SingleSubscriber.onSubscribe(MonoSingle.java:115)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:96)
	at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:55)
	at reactor.core.publisher.FluxFromMonoOperator.subscribe(FluxFromMonoOperator.java:85)
	at reactor.core.publisher.FluxDeferContextual.subscribe(FluxDeferContextual.java:57)
	at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165)
	at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:245)
	at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:305)
	at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:122)
	at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129)
	at reactor.core.publisher.Operators$BaseFluxToMonoOperator.completePossiblyEmpty(Operators.java:2097)
	at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:145)
	at reactor.core.publisher.FluxFlatMap$FlatMapMain.checkTerminated(FluxFlatMap.java:850)
	at reactor.core.publisher.FluxFlatMap$FlatMapMain.drainLoop(FluxFlatMap.java:612)
	at reactor.core.publisher.FluxFlatMap$FlatMapMain.innerComplete(FluxFlatMap.java:898)
	at reactor.core.publisher.FluxFlatMap$FlatMapInner.onComplete(FluxFlatMap.java:1001)
	at reactor.core.publisher.MonoZip$ZipCoordinator.signal(MonoZip.java:298)
	at reactor.core.publisher.MonoZip$ZipInner.onNext(MonoZip.java:478)
	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129)
	at reactor.core.publisher.FluxSubscribeOnCallable$CallableSubscribeOnSubscription.run(FluxSubscribeOnCallable.java:252)
	at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68)
	at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
	at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
	at java.base/java.lang.Thread.run(Thread.java:1575)

Apparently HttpOperations#initShortId() isn't thread-safe and is causing this exception if the call to connection() in line 380 returns a different Connection instance than in line 381.

if (connection() instanceof AtomicLong) {
return channel().id().asShortText() + '-' + ((AtomicLong) connection()).incrementAndGet();
}

Expected Behavior

HttpOperations#initShortId() SHOULD NOT throw a ClassCastException.

Actual Behavior

HttpOperations#initShortId() DOES throw a ClassCastException.

Steps to Reproduce

Unfortunately I couldn't come up with a real-life minimal reproducer for this.

Possible Solution

Store the result of connection() and create the short ID from that if the branch with instanceof AtomicLong is taken.

	@Override
	protected final String initShortId() {
                Connection connection = connection();
		if (connection instanceof AtomicLong) {
			return channel().id().asShortText() + '-' + ((AtomicLong) connection).incrementAndGet();
		}
		return super.initShortId();
	}

Your Environment

@joschi joschi added status/need-triage A new issue that still need to be evaluated as a whole type/bug A general bug labels Dec 11, 2024
joschi added a commit to joschi/reactor-netty that referenced this issue Dec 11, 2024
Calling `HttpOperations#initShortId()` could cause a
`ClassCastException` if the underlying connection was replaced between
checking its type and casting it to `AtomicLong`, for example if
`ChannelOperations#terminate()` was called on another thread.

See also:
https://github.com/reactor/reactor-netty/blob/667f8c9cbf5a227a15f6d0a2f3aab7c4777613da/reactor-netty-core/src/main/java/reactor/netty/channel/ChannelOperations.java#L515

Fixes reactor#3541
@violetagg violetagg self-assigned this Dec 11, 2024
@violetagg violetagg removed the status/need-triage A new issue that still need to be evaluated as a whole label Dec 11, 2024
@violetagg violetagg added this to the 1.1.26 milestone Dec 11, 2024
@violetagg
Copy link
Member

@joschi Thanks for the detailed report and the PR!

violetagg added a commit that referenced this issue Dec 11, 2024
Calling `HttpOperations#initShortId()` could cause a
`ClassCastException` if the underlying connection was replaced between
checking its type and casting it to `AtomicLong`, for example if
`ChannelOperations#terminate()` was called on another thread.

See also:
https://github.com/reactor/reactor-netty/blob/667f8c9cbf5a227a15f6d0a2f3aab7c4777613da/reactor-netty-core/src/main/java/reactor/netty/channel/ChannelOperations.java#L515

Fixes #3541

Co-authored-by: Violeta Georgieva <milesg78@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type/bug A general bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants