Hello folks at CKB DevRel 🤗
I noticed that a Node process using CCC's default public client stays alive after its RPC completes. The client opens a WebSocket but provides no way to close it.
Minimal Reproduction
import { ccc } from "@ckb-ccc/core";
const client = new ccc.ClientPublicTestnet();
await client.getTipHeader();
console.log("done");
And to run it:
timeout 600 node repro.mjs
Behavior
The RPC completes and done prints, but the process remains alive until killed:
done
exit=124 elapsed=600.02
In Node, the default public client selects WebSocket. A successful response clears its request timer and pending entry but leaves the socket open and referenced. Neither the transport nor client exposes close()/dispose(), and there is no idle close.
Environment
@ckb-ccc/core@1.17.0
- Node.js 24.18.0
- Linux
Impact
A short-lived command can finish its RPC work and print its final output, but still sit until an outer timeout kills it. In CI, cron, or one-shot containers this can turn successful work into a timeout, delay the next run, or cause a supervisor to retry work that already completed.
The problem is more serious when an application creates clients repeatedly. Every used client can leave another WebSocket and file descriptor open. For example, creating one client per second would leave roughly 600 sockets after the ten-minute period reproduced above. Over time this could hit file-descriptor, memory, RPC connection, or upstream endpoint limits. Worker processes multiply the same effect.
Keep up the Great Work 💪
Phroi %48
Hello folks at CKB DevRel 🤗
I noticed that a Node process using CCC's default public client stays alive after its RPC completes. The client opens a WebSocket but provides no way to close it.
Minimal Reproduction
And to run it:
Behavior
The RPC completes and
doneprints, but the process remains alive until killed:In Node, the default public client selects WebSocket. A successful response clears its request timer and pending entry but leaves the socket open and referenced. Neither the transport nor client exposes
close()/dispose(), and there is no idle close.Environment
@ckb-ccc/core@1.17.0Impact
A short-lived command can finish its RPC work and print its final output, but still sit until an outer timeout kills it. In CI, cron, or one-shot containers this can turn successful work into a timeout, delay the next run, or cause a supervisor to retry work that already completed.
The problem is more serious when an application creates clients repeatedly. Every used client can leave another WebSocket and file descriptor open. For example, creating one client per second would leave roughly 600 sockets after the ten-minute period reproduced above. Over time this could hit file-descriptor, memory, RPC connection, or upstream endpoint limits. Worker processes multiply the same effect.
Keep up the Great Work 💪
Phroi %48