Skip to content

fix: prevent duplicate TCP bootstrap server creation - #7012

Merged
Aias00 merged 2 commits into
apache:masterfrom
lymerin:fix/6735-tcp-bootstrap-race
Sep 2, 2026
Merged

fix: prevent duplicate TCP bootstrap server creation#7012
Aias00 merged 2 commits into
apache:masterfrom
lymerin:fix/6735-tcp-bootstrap-race

Conversation

@lymerin

@lymerin lymerin commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Fixes #6735.

What is changed

TcpProxySelectorDataHandler previously used a non-atomic check-then-act sequence:

inCache -> createBootstrapServer -> cache

Concurrent selector synchronization events could both observe a cache miss and start bootstrap servers for the same selector. This could cause port binding failures or leave an overwritten server and its event-loop resources without lifecycle management.

This change:

  • adds per-selector single-flight creation in TcpBootstrapFactory;
  • ensures concurrent creation requests for the same selector wait for one in-flight creation;
  • performs server startup outside ConcurrentHashMap atomic callbacks and without a global lifecycle lock;
  • allows different selectors to be created and removed independently;
  • uses putIfAbsent when publishing the started server and shuts down a losing instance if another server was already cached;
  • routes TcpProxySelectorDataHandler through the atomic creation and removal methods;
  • preserves the existing public factory methods for compatibility;
  • rolls back LoopResources when bootstrap startup fails;
  • makes TcpBootstrapServer.shutdown() idempotent and preserves shutdown failures using suppressed exceptions.

The change is limited to the duplicate bootstrap creation and related resource lifecycle described in #6735. Existing selector configuration update behavior is unchanged.

Tests

Added or extended tests covering:

  • concurrent creation of the same selector creates only one server;
  • a failed creation can be retried;
  • waiting callers receive the original creation failure;
  • slow shutdown of one selector does not block removal of another selector;
  • bootstrap startup failure disposes its loop resources;
  • repeated shutdown is idempotent;
  • failures from both server and loop-resource disposal are preserved.

The following targeted Maven build passed locally:

mvn -pl shenyu-protocol/shenyu-protocol-tcp,shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-tcp \
    -am \
    -Dtest=TcpBootstrapFactoryTest,TcpBootstrapServerTest,TcpProxySelectorDataHandlerTest \
    -Dsurefire.failIfNoSpecifiedTests=false \
    test

Results:

  • TcpBootstrapServerTest: 11 tests passed
  • TcpBootstrapFactoryTest: 4 tests passed
  • TcpProxySelectorDataHandlerTest: 3 tests passed
  • Checkstyle: no violations

Checklist

@lymerin lymerin closed this Aug 31, 2026
@lymerin lymerin reopened this Aug 31, 2026

@Aias00 Aias00 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: Prevent duplicate TCP bootstrap server creation (#6735)

Verdict: APPROVE

Analysis

Solid, well-tested fix addressing three related issues in the TCP bootstrap lifecycle.

1. Duplicate-creation guard (TcpBootstrapFactory)

  • Introduces a creations ConcurrentMap<String, CompletableFuture<BootstrapServer>> to track in-flight creations.
  • createBootstrapServerIfAbsent:
    • cache.containsKey fast path returns false if already present.
    • creations.putIfAbsent ensures only one thread performs creation; concurrent callers awaitCreation (which correctly unwraps CompletionException to the original cause) and return false.
    • Double-checked cache.get after winning the creation future; if another server already landed, the new one is shutdown() (no leak) and the existing is returned.
    • On RuntimeException, completeExceptionally + rethrow, with finally doing creations.remove(selectorName, creation) (atomic, value-checked cleanup). getCache exists (verified) and createBootstrapServer calls start() internally, so a bind failure propagates and the partially-built server is discarded correctly.
  • removeAndShutdown is idempotent (no-op when absent).

2. Resource leak on bind failure (TcpBootstrapServer.start)

  • start() now wraps bindNow() in try/catch; on failure it disposes the LoopResources (which was already assigned to the field before bind) before rethrowing. No leaked event-loop resources.

3. Idempotent / safe shutdown (TcpBootstrapServer.shutdown)

  • Now synchronized with a disposed guard, so repeated shutdown is a no-op. Both server and loopResources are null-guarded, and failures are collected with addSuppressed so no exception is silently swallowed.

Tests: TcpBootstrapServerTest + TcpBootstrapFactoryTest cover concurrent-single-creation, retry-after-failure, failure unwrapping, non-blocking removal of a different selector during shutdown, loop-resource disposal on bind failure, single shutdown, and exception propagation on disposal failure. Coverage is thorough.

Conclusion

The concurrency model is correct, imports are clean (no leftover unused imports in TcpProxySelectorDataHandler), and the edge cases are exercised. Approving.

@lymerin lymerin changed the title 1fix: prevent duplicate TCP bootstrap server creation fix: prevent duplicate TCP bootstrap server creation Sep 2, 2026
@Aias00
Aias00 merged commit ce93f58 into apache:master Sep 2, 2026
40 checks passed
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.

[BUG] TcpProxySelectorDataHandler has a TOCTOU race creating duplicate/leaked bootstrap servers

2 participants