Skip to content

🔨 replace Netty server engine with CIO on desktop - #4911

Merged
guiyanakuang merged 1 commit into
mainfrom
hammer/replace-netty-with-cio
Sep 3, 2026
Merged

🔨 replace Netty server engine with CIO on desktop#4911
guiyanakuang merged 1 commit into
mainfrom
hammer/replace-netty-with-cio

Conversation

@guiyanakuang

@guiyanakuang guiyanakuang commented Sep 3, 2026

Copy link
Copy Markdown
Member

Summary

Part of #4908 (thread count growing over long uptime).

The desktop sync server and the local MCP server were the only Netty users in the app. Netty keeps three event loop groups sized for a server host (connection = parallelism/2+1, worker = parallelism/2+1, call = parallelism), starts each event loop thread lazily on first use, and never releases it until shutdown. On an 18-core machine that is up to 38 permanently parked eventLoopGroupProxy-* threads that only appear to "grow" over days because they start on demand.

This PR swaps both servers to Ktor's CIO engine and removes the Netty dependency entirely:

  • DesktopServerFactory / DesktopPasteServer / DesktopNetworkModule now use CIOApplicationEngine.
  • DesktopMcpServer uses embeddedServer(CIO, ...); the MCP SDK's mcp {} plugin is engine agnostic.
  • ktor-server-netty is dropped from the version catalog and the desktop dependencies, along with the -Dio.netty.maxDirectMemory JVM flag. The whole io.netty:* 4.2.x stack disappears from the runtime classpath.
  • The Netty bootstrap options are gone: Ktor's network layer already sets TCP_NODELAY on accepted sockets, and the 30 s WebSocket ping already covers keep-alive.

CIO has no dedicated threads. The selector loop and request handling run on Dispatchers.IO, whose idle workers are reclaimed after 60 s, so the server no longer contributes a fixed thread pool at all. The CLI server has been running on CIO since it was introduced, and the commonMain routing is engine agnostic (the mobile apps cannot use Netty), so no routing or protocol code changes.

Verified from Ktor 3.5.2 sources that CIO's connectionIdleTimeoutSeconds (45 s) only applies while waiting between HTTP responses on a keep-alive connection; after a WebSocket upgrade the pipeline closes its writer channel and hands the connection over, so long-lived sync sockets are unaffected.

Test plan

  • ./gradlew app:desktopTest (fast tier): 1680 tests, 0 failures
  • ./gradlew app:desktopTest -PintegrationTests --tests SyncIntegrationTest (3 servers, full pairing + sync in one JVM): 15/15
  • DesktopPasteServerTest port-in-use fallback path still passes
  • app:dependencies --configuration desktopRuntimeClasspath contains no Netty artifacts
  • Real-machine: two devices, large file transfer throughput and a WebSocket sync session idle for more than 45 s
  • Real-machine: thread count (ps -M <pid> | wc -l) after several hours compared with 2.2.0

The desktop sync server and the local MCP server were the only Netty
users. Netty keeps a connection/worker/call event loop group sized for a
server host (parallelism/2+1, parallelism/2+1, parallelism threads) and
every event loop thread it starts lives until shutdown, so a long-running
CrossPaste instance ends up holding dozens of parked eventLoopGroupProxy
threads (#4908).

CIO has no dedicated threads: the selector loop and request handling run
on Dispatchers.IO, whose idle workers are reclaimed automatically. The
CLI server already runs on CIO, and the commonMain routing is engine
agnostic, so this only swaps the engine type, drops the Netty bootstrap
options (Ktor's network layer already sets TCP_NODELAY on accepted
sockets and the WebSocket ping covers keep-alive) and removes the Netty
dependency and its maxDirectMemory JVM flag.
@guiyanakuang
guiyanakuang merged commit 018290e into main Sep 3, 2026
7 checks passed
@guiyanakuang
guiyanakuang deleted the hammer/replace-netty-with-cio branch September 3, 2026 04:10
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.

1 participant