Skip to content

Update Netty 4.1→4.2 and companion dependency upgrades#237

Open
usmansaleem wants to merge 2 commits into
Consensys:masterfrom
usmansaleem:update-netty-4.2
Open

Update Netty 4.1→4.2 and companion dependency upgrades#237
usmansaleem wants to merge 2 commits into
Consensys:masterfrom
usmansaleem:update-netty-4.2

Conversation

@usmansaleem

@usmansaleem usmansaleem commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Bumps Netty 4.1.115.Final → 4.2.15.Final, plus companion upgrades: Guava 33.2→33.6, Log4j 2.23→2.26, BouncyCastle 1.78→1.84, Vert.x 4.5.9→4.5.28, Tuweni 2.7.0→2.7.2
  • Adds org.jspecify:jspecify:1.0.0 (compileOnly) to replace javax.annotation.Nonnull which was previously provided transitively by Netty 4.1 but dropped in 4.2 (aligns with Besu's migration to JSpecify)
  • Resolves all deprecation warnings introduced by these upgrades (Reactor ReplayProcessorSinks, Netty NioEventLoopGroupMultiThreadIoEventLoopGroup, InternetProtocolFamilyjava.net.StandardProtocolFamily, Guava CacheBuilder duration overload)

Details

Netty 4.2 deprecations

  • NioEventLoopGroupMultiThreadIoEventLoopGroup(1, NioIoHandler.newFactory()) in NettyDiscoveryServerImpl
  • InternetProtocolFamilyjava.net.StandardProtocolFamily in NettyDiscoveryClientImpl, DiscoveryManagerImpl, DiscoverySystemBuilder

Reactor Sinks migration (ReplayProcessor/FluxProcessor/FluxSink deprecated since Reactor 3.5)

  • ReplayProcessor.cacheLast() + FluxSinkSinks.many().replay().latest() across NettyDiscoveryServerImpl, DiscoveryManagerImpl, PipelineImpl, SimpleProcessor, IncomingMessageSink, OutgoingParcelHandler
  • PipelineImpl.push() uses emitNext with FAIL_NON_SERIALIZED retry rather than tryEmitNext — necessary because incomingPipeline can receive concurrent pushes from IPv4 and IPv6 Netty event-loop threads; the old ReplayProcessor.FluxSink serialized these internally, the new safe Sinks.Many does not
  • DelegatingReactorScheduler.start(): suppress deprecated override (preserves delegation behaviour for downstream schedulers)

Other

  • CacheBuilder.expireAfterWrite(long, TimeUnit)expireAfterWrite(Duration) in ExpirationSet
  • @Nonnull@NonNull (JSpecify) in DelegatingReactorScheduler, ErrorHandlingScheduler

Test plan

  • ./gradlew build passes (all 16 integration tests green)
  • ./gradlew compileJava --rerun-tasks with -Xlint:deprecation produces zero deprecation warnings

Note

Medium Risk
Touches core UDP discovery I/O and message pipelines with a major Netty bump and changed concurrent emit semantics, though failures are logged and retries are bounded.

Overview
Upgrades Netty (4.1→4.2) and several companion libraries (Guava, Log4j, BouncyCastle, Vert.x, Tuweni), and adds org.jspecify as compileOnly so @NonNull replaces javax.annotation.Nonnull after Netty dropped that transitively.

Netty 4.2 API updates: discovery servers use MultiThreadIoEventLoopGroup + NioIoHandler instead of NioEventLoopGroup; IPv4/IPv6 channel maps and listen-address validation use java.net.StandardProtocolFamily instead of Netty’s InternetProtocolFamily.

Reactor deprecation cleanup: ReplayProcessor / FluxSink paths become Sinks.many().replay().latest() (discovery server, manager, pipelines, SimpleProcessor, incoming/outgoing handlers). Emits use tryEmitNext with logging on failure; PipelineImpl.push and OutgoingParcelHandler call new Utils.emitNextWithRetry to handle FAIL_NON_SERIALIZED when multiple event-loop threads push concurrently (behavior the old sink serialized internally). Guava ExpirationSet switches to expireAfterWrite(Duration).

Reviewed by Cursor Bugbot for commit def500b. Bugbot is set up for automated code reviews on this repo. Configure here.

Dependency version bumps:
- Netty 4.1.115.Final → 4.2.15.Final
- Guava 33.2.1-jre → 33.6.0-jre
- Log4j 2.23.1 → 2.26.0
- BouncyCastle 1.78.1 → 1.84
- Vert.x 4.5.9 → 4.5.28
- Tuweni 2.7.0 → 2.7.2
- Add org.jspecify:jspecify:1.0.0 (compileOnly)

Fix deprecations introduced by these upgrades:

Netty 4.2:
- Replace NioEventLoopGroup with MultiThreadIoEventLoopGroup + NioIoHandler
- Replace InternetProtocolFamily with java.net.StandardProtocolFamily

Reactor 3.5+:
- Migrate ReplayProcessor/FluxProcessor/FluxSink to Sinks.many().replay().latest()
- PipelineImpl.push() uses emitNext with FAIL_NON_SERIALIZED retry to handle
  concurrent emissions from IPv4 and IPv6 Netty event-loop threads
- Suppress deprecated Scheduler.start() override in DelegatingReactorScheduler

JSpecify (replacing javax.annotation removed by Netty 4.2 transitive dep):
- Replace @nonnull with @nonnull from org.jspecify.annotations

Guava:
- Replace CacheBuilder.expireAfterWrite(long, TimeUnit) with expireAfterWrite(Duration)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR upgrades core networking and supporting dependencies (notably Netty 4.1→4.2) and updates the discovery networking/reactive plumbing to match new APIs and remove deprecations, including the migration from Reactor ReplayProcessor/FluxSink to Sinks.Many and adoption of JSpecify annotations.

Changes:

  • Upgrade Netty (4.1→4.2) and companion libraries (Guava, Log4j, BouncyCastle, Vert.x, Tuweni), plus add org.jspecify:jspecify as compileOnly.
  • Replace deprecated Reactor processors/sinks with Sinks.many().replay().latest() across pipeline, networking, and scheduler utilities.
  • Replace Netty InternetProtocolFamily usage with java.net.StandardProtocolFamily mapping helpers.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/test/java/org/ethereum/beacon/discovery/pipeline/handler/OutgoingParcelHandlerTest.java Updates test to mock Sinks.Many instead of FluxSink and verify emission via Reactor sinks API.
src/main/java/org/ethereum/beacon/discovery/scheduler/SimpleProcessor.java Migrates from ReplayProcessor/FluxSink to Sinks.Many for a lightweight processor abstraction.
src/main/java/org/ethereum/beacon/discovery/scheduler/ErrorHandlingScheduler.java Switches @Nonnull to JSpecify @NonNull on scheduler APIs.
src/main/java/org/ethereum/beacon/discovery/scheduler/DelegatingReactorScheduler.java Switches @Nonnull to JSpecify @NonNull and suppresses deprecated start() override.
src/main/java/org/ethereum/beacon/discovery/pipeline/PipelineImpl.java Migrates pipeline source from ReplayProcessor to Sinks.Many with non-serialized emission retry.
src/main/java/org/ethereum/beacon/discovery/pipeline/handler/OutgoingParcelHandler.java Migrates outgoing parcel emission from FluxSink.next to Sinks.Many.tryEmitNext.
src/main/java/org/ethereum/beacon/discovery/network/NettyDiscoveryServerImpl.java Updates Netty event loop initialization for Netty 4.2 and replaces inbound processor with Sinks.Many.
src/main/java/org/ethereum/beacon/discovery/network/NettyDiscoveryClientImpl.java Replaces InternetProtocolFamily channel selection with StandardProtocolFamily helper.
src/main/java/org/ethereum/beacon/discovery/network/IncomingMessageSink.java Updates inbound Netty handler to emit into Sinks.Many instead of FluxSink.
src/main/java/org/ethereum/beacon/discovery/DiscoverySystemBuilder.java Updates listen-address validation to use StandardProtocolFamily instead of Netty’s InternetProtocolFamily.
src/main/java/org/ethereum/beacon/discovery/DiscoveryManagerImpl.java Migrates outgoing message stream to Sinks.Many, updates client wiring, and uses StandardProtocolFamily for channel map keys.
src/main/java/org/ethereum/beacon/discovery/database/ExpirationSet.java Migrates Guava cache expiration API to Duration overload.
gradle/versions.gradle Bumps Netty/Guava/Log4j/BouncyCastle/Vert.x/Tuweni versions and adds managed JSpecify dependency.
build.gradle Adds compileOnly org.jspecify:jspecify to support JSpecify annotations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 53 to 54
outgoingSink.tryEmitNext(parcel);
envelope.remove(Field.INCOMING);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in def500b: OutgoingParcelHandler now retries on FAIL_NON_SERIALIZED (bounded, via a new Utils.emitNextWithRetry helper) and only removes Field.INCOMING after a confirmed successful emit; any other failure is logged at ERROR instead of silently dropping the packet.

protected void channelRead0(ChannelHandlerContext ctx, Envelope msg) {
LOG.trace(() -> String.format("Incoming packet %s in session %s", msg, ctx));
messageSink.next(msg);
messageSink.tryEmitNext(msg);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in def500b: IncomingMessageSink now checks the EmitResult and logs a WARN if the emit fails, so a dropped incoming packet is no longer silent.

Comment on lines 69 to 72
@Override
public void onNext(T t) {
sink.next(t);
sinks.tryEmitNext(t);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in def500b: SimpleProcessor.onNext now checks the returned EmitResult and logs a WARN on failure instead of discarding it silently.

Comment on lines 75 to 77
public void onError(Throwable throwable) {
sink.error(throwable);
sinks.tryEmitError(throwable);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in def500b: SimpleProcessor.onError now checks the returned EmitResult and logs a WARN on failure instead of discarding it silently.

Comment on lines 79 to 82
@Override
public void onComplete() {
sink.complete();
sinks.tryEmitComplete();
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in def500b: SimpleProcessor.onComplete now checks the returned EmitResult and logs a WARN on failure instead of discarding it silently.

Comment on lines 56 to +58
handler.handle(envelope);

verify(outgoingSink).next(parcel);
verify(outgoingSink).tryEmitNext(parcel);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in def500b: the test now stubs outgoingSink.tryEmitNext(any()) to return Sinks.EmitResult.OK before exercising shouldSendPacketsToAllowedHosts, so it stays representative now that OutgoingParcelHandler branches on the emit result.

tryEmitNext/tryEmitError/tryEmitComplete return an EmitResult that the
Netty 4.2/Reactor migration was ignoring, silently dropping packets on
failure. Add Utils.emitNextWithRetry(), a bounded retry on
FAIL_NON_SERIALIZED (contended concurrent producers), and use it in
OutgoingParcelHandler and PipelineImpl.push() instead of retrying
forever; only remove Field.INCOMING after a confirmed successful emit.
IncomingMessageSink and SimpleProcessor log a warning on failure since
they have no concurrent-producer path requiring a retry.

Addresses Copilot review comments on PR Consensys#237.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants