@encharm/cws 4.5.3 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/CLAUDE.md +81 -0
  3. package/README.md +10 -3
  4. package/dist/bindings/cws_darwin_arm64_node115.node +0 -0
  5. package/dist/bindings/cws_darwin_arm64_node127.node +0 -0
  6. package/dist/bindings/cws_darwin_arm64_node137.node +0 -0
  7. package/dist/bindings/cws_darwin_arm64_node147.node +0 -0
  8. package/dist/bindings/cws_linux_arm64_node115.node +0 -0
  9. package/dist/bindings/cws_linux_arm64_node127.node +0 -0
  10. package/dist/bindings/cws_linux_arm64_node137.node +0 -0
  11. package/dist/bindings/cws_linux_arm64_node147.node +0 -0
  12. package/dist/bindings/cws_linux_x64_node115.node +0 -0
  13. package/dist/bindings/cws_linux_x64_node127.node +0 -0
  14. package/dist/bindings/cws_linux_x64_node137.node +0 -0
  15. package/dist/bindings/cws_linux_x64_node147.node +0 -0
  16. package/dist/bindings/cws_win32_x64_node115.node +0 -0
  17. package/dist/bindings/cws_win32_x64_node127.node +0 -0
  18. package/dist/bindings/cws_win32_x64_node137.node +0 -0
  19. package/dist/bindings/cws_win32_x64_node147.node +0 -0
  20. package/package.json +1 -1
  21. package/src/Addon.cpp +2 -1
  22. package/src/Addon.h +70 -14
  23. package/src/Hub.h +1 -0
  24. package/src/Networking.h +11 -0
  25. package/src/Socket.h +201 -6
  26. package/src/WebSocket.cpp +4 -1
  27. package/src/cSNode.cpp +2 -0
  28. package/src/cSNode.h +4 -0
  29. package/src/headers/26/acorn_version.h +6 -0
  30. package/src/headers/26/aliased_buffer-inl.h +264 -0
  31. package/src/headers/26/aliased_buffer.h +205 -0
  32. package/src/headers/26/aliased_struct-inl.h +143 -0
  33. package/src/headers/26/aliased_struct.h +248 -0
  34. package/src/headers/26/amaro_version.h +6 -0
  35. package/src/headers/26/async_context_frame.h +33 -0
  36. package/src/headers/26/async_wrap-inl.h +123 -0
  37. package/src/headers/26/async_wrap.h +254 -0
  38. package/src/headers/26/base_object-inl.h +333 -0
  39. package/src/headers/26/base_object.h +339 -0
  40. package/src/headers/26/base_object_types.h +76 -0
  41. package/src/headers/26/blob_serializer_deserializer-inl.h +385 -0
  42. package/src/headers/26/blob_serializer_deserializer.h +142 -0
  43. package/src/headers/26/builtin_info.h +57 -0
  44. package/src/headers/26/callback_queue-inl.h +97 -0
  45. package/src/headers/26/callback_queue.h +79 -0
  46. package/src/headers/26/cares_wrap.h +475 -0
  47. package/src/headers/26/cleanup_queue-inl.h +36 -0
  48. package/src/headers/26/cleanup_queue.h +79 -0
  49. package/src/headers/26/compile_cache.h +134 -0
  50. package/src/headers/26/connect_wrap.h +26 -0
  51. package/src/headers/26/connection_wrap.h +30 -0
  52. package/src/headers/26/cppgc_helpers-inl.h +69 -0
  53. package/src/headers/26/cppgc_helpers.h +162 -0
  54. package/src/headers/26/crypto/crypto_aes.h +107 -0
  55. package/src/headers/26/crypto/crypto_argon2.h +88 -0
  56. package/src/headers/26/crypto/crypto_bio.h +193 -0
  57. package/src/headers/26/crypto/crypto_chacha20_poly1305.h +62 -0
  58. package/src/headers/26/crypto/crypto_cipher.h +291 -0
  59. package/src/headers/26/crypto/crypto_common.h +48 -0
  60. package/src/headers/26/crypto/crypto_context.h +219 -0
  61. package/src/headers/26/crypto/crypto_dh.h +104 -0
  62. package/src/headers/26/crypto/crypto_dsa.h +50 -0
  63. package/src/headers/26/crypto/crypto_ec.h +102 -0
  64. package/src/headers/26/crypto/crypto_hash.h +157 -0
  65. package/src/headers/26/crypto/crypto_hkdf.h +62 -0
  66. package/src/headers/26/crypto/crypto_hmac.h +92 -0
  67. package/src/headers/26/crypto/crypto_kem.h +131 -0
  68. package/src/headers/26/crypto/crypto_keygen.h +382 -0
  69. package/src/headers/26/crypto/crypto_keys.h +366 -0
  70. package/src/headers/26/crypto/crypto_kmac.h +79 -0
  71. package/src/headers/26/crypto/crypto_pbkdf2.h +76 -0
  72. package/src/headers/26/crypto/crypto_pqc.h +39 -0
  73. package/src/headers/26/crypto/crypto_random.h +127 -0
  74. package/src/headers/26/crypto/crypto_rsa.h +108 -0
  75. package/src/headers/26/crypto/crypto_scrypt.h +85 -0
  76. package/src/headers/26/crypto/crypto_sig.h +159 -0
  77. package/src/headers/26/crypto/crypto_spkac.h +21 -0
  78. package/src/headers/26/crypto/crypto_timing.h +20 -0
  79. package/src/headers/26/crypto/crypto_tls.h +342 -0
  80. package/src/headers/26/crypto/crypto_turboshake.h +105 -0
  81. package/src/headers/26/crypto/crypto_util.h +775 -0
  82. package/src/headers/26/crypto/crypto_x509.h +140 -0
  83. package/src/headers/26/dataqueue/queue.h +311 -0
  84. package/src/headers/26/debug_utils-inl.h +291 -0
  85. package/src/headers/26/debug_utils.h +200 -0
  86. package/src/headers/26/diagnosticfilename-inl.h +33 -0
  87. package/src/headers/26/embedded_data.h +17 -0
  88. package/src/headers/26/encoding_binding.h +59 -0
  89. package/src/headers/26/env-inl.h +956 -0
  90. package/src/headers/26/env.h +1294 -0
  91. package/src/headers/26/env_properties.h +562 -0
  92. package/src/headers/26/ffi/data.h +30 -0
  93. package/src/headers/26/ffi/fast.h +96 -0
  94. package/src/headers/26/ffi/jit_memory.h +26 -0
  95. package/src/headers/26/ffi/types.h +99 -0
  96. package/src/headers/26/handle_wrap.h +123 -0
  97. package/src/headers/26/histogram-inl.h +211 -0
  98. package/src/headers/26/histogram.h +474 -0
  99. package/src/headers/26/inspector/dom_storage_agent.h +68 -0
  100. package/src/headers/26/inspector/inspector_object_utils.h +38 -0
  101. package/src/headers/26/inspector/io_agent.h +30 -0
  102. package/src/headers/26/inspector/main_thread_interface.h +116 -0
  103. package/src/headers/26/inspector/network_agent.h +110 -0
  104. package/src/headers/26/inspector/network_inspector.h +45 -0
  105. package/src/headers/26/inspector/network_requests_buffer.h +195 -0
  106. package/src/headers/26/inspector/network_resource_manager.h +29 -0
  107. package/src/headers/26/inspector/node_json.h +24 -0
  108. package/src/headers/26/inspector/node_string.h +101 -0
  109. package/src/headers/26/inspector/notification_emitter.h +38 -0
  110. package/src/headers/26/inspector/protocol_helper.h +27 -0
  111. package/src/headers/26/inspector/runtime_agent.h +41 -0
  112. package/src/headers/26/inspector/storage_agent.h +33 -0
  113. package/src/headers/26/inspector/target_agent.h +59 -0
  114. package/src/headers/26/inspector/target_manager.h +70 -0
  115. package/src/headers/26/inspector/tracing_agent.h +43 -0
  116. package/src/headers/26/inspector/worker_agent.h +40 -0
  117. package/src/headers/26/inspector/worker_inspector.h +124 -0
  118. package/src/headers/26/inspector_agent.h +168 -0
  119. package/src/headers/26/inspector_io.h +78 -0
  120. package/src/headers/26/inspector_profiler.h +149 -0
  121. package/src/headers/26/inspector_socket.h +60 -0
  122. package/src/headers/26/inspector_socket_server.h +110 -0
  123. package/src/headers/26/js_native_api.h +634 -0
  124. package/src/headers/26/js_native_api_types.h +244 -0
  125. package/src/headers/26/js_native_api_v8.h +485 -0
  126. package/src/headers/26/js_native_api_v8_internals.h +45 -0
  127. package/src/headers/26/js_stream.h +57 -0
  128. package/src/headers/26/json_utils.h +172 -0
  129. package/src/headers/26/large_pages/node_large_page.h +34 -0
  130. package/src/headers/26/lru_cache-inl.h +69 -0
  131. package/src/headers/26/memory_tracker-inl.h +461 -0
  132. package/src/headers/26/memory_tracker.h +341 -0
  133. package/src/headers/26/module_wrap.h +231 -0
  134. package/src/headers/26/ncrypto.h +2008 -0
  135. package/src/headers/26/node.h +1693 -0
  136. package/src/headers/26/node_api.h +265 -0
  137. package/src/headers/26/node_api_internals.h +48 -0
  138. package/src/headers/26/node_api_types.h +58 -0
  139. package/src/headers/26/node_binding.h +170 -0
  140. package/src/headers/26/node_blob.h +160 -0
  141. package/src/headers/26/node_bob-inl.h +36 -0
  142. package/src/headers/26/node_bob.h +107 -0
  143. package/src/headers/26/node_buffer.h +92 -0
  144. package/src/headers/26/node_builtins.h +239 -0
  145. package/src/headers/26/node_concepts.h +46 -0
  146. package/src/headers/26/node_config_file.h +77 -0
  147. package/src/headers/26/node_constants.h +82 -0
  148. package/src/headers/26/node_context_data.h +171 -0
  149. package/src/headers/26/node_contextify.h +261 -0
  150. package/src/headers/26/node_crypto.h +66 -0
  151. package/src/headers/26/node_debug.h +30 -0
  152. package/src/headers/26/node_diagnostics_channel.h +121 -0
  153. package/src/headers/26/node_dir.h +52 -0
  154. package/src/headers/26/node_dotenv.h +45 -0
  155. package/src/headers/26/node_errors.h +378 -0
  156. package/src/headers/26/node_exit_code.h +54 -0
  157. package/src/headers/26/node_external_reference.h +171 -0
  158. package/src/headers/26/node_ffi.h +212 -0
  159. package/src/headers/26/node_file-inl.h +421 -0
  160. package/src/headers/26/node_file.h +561 -0
  161. package/src/headers/26/node_file_utils.h +39 -0
  162. package/src/headers/26/node_hash.h +212 -0
  163. package/src/headers/26/node_http2.h +1202 -0
  164. package/src/headers/26/node_http2_state.h +159 -0
  165. package/src/headers/26/node_http_common-inl.h +199 -0
  166. package/src/headers/26/node_http_common.h +535 -0
  167. package/src/headers/26/node_i18n.h +132 -0
  168. package/src/headers/26/node_internals.h +485 -0
  169. package/src/headers/26/node_locks.h +184 -0
  170. package/src/headers/26/node_main_instance.h +67 -0
  171. package/src/headers/26/node_mem-inl.h +113 -0
  172. package/src/headers/26/node_mem.h +45 -0
  173. package/src/headers/26/node_messaging.h +389 -0
  174. package/src/headers/26/node_metadata.h +172 -0
  175. package/src/headers/26/node_modules.h +102 -0
  176. package/src/headers/26/node_mutex.h +323 -0
  177. package/src/headers/26/node_object_wrap.h +145 -0
  178. package/src/headers/26/node_options-inl.h +524 -0
  179. package/src/headers/26/node_options.h +710 -0
  180. package/src/headers/26/node_perf.h +167 -0
  181. package/src/headers/26/node_perf_common.h +103 -0
  182. package/src/headers/26/node_platform.h +290 -0
  183. package/src/headers/26/node_process-inl.h +26 -0
  184. package/src/headers/26/node_process.h +112 -0
  185. package/src/headers/26/node_profiling.h +38 -0
  186. package/src/headers/26/node_realm-inl.h +156 -0
  187. package/src/headers/26/node_realm.h +236 -0
  188. package/src/headers/26/node_report.h +43 -0
  189. package/src/headers/26/node_revert.h +82 -0
  190. package/src/headers/26/node_root_certs.h +2937 -0
  191. package/src/headers/26/node_sea.h +101 -0
  192. package/src/headers/26/node_shadow_realm.h +46 -0
  193. package/src/headers/26/node_snapshot_builder.h +57 -0
  194. package/src/headers/26/node_snapshotable.h +167 -0
  195. package/src/headers/26/node_sockaddr-inl.h +248 -0
  196. package/src/headers/26/node_sockaddr.h +459 -0
  197. package/src/headers/26/node_sqlite.h +607 -0
  198. package/src/headers/26/node_stat_watcher.h +73 -0
  199. package/src/headers/26/node_task_runner.h +92 -0
  200. package/src/headers/26/node_threadsafe_cow-inl.h +54 -0
  201. package/src/headers/26/node_threadsafe_cow.h +105 -0
  202. package/src/headers/26/node_union_bytes.h +81 -0
  203. package/src/headers/26/node_url.h +101 -0
  204. package/src/headers/26/node_url_pattern.h +118 -0
  205. package/src/headers/26/node_v8.h +78 -0
  206. package/src/headers/26/node_v8_embedder.h +26 -0
  207. package/src/headers/26/node_v8_platform-inl.h +96 -0
  208. package/src/headers/26/node_version.h +116 -0
  209. package/src/headers/26/node_wasi.h +184 -0
  210. package/src/headers/26/node_wasm_web_api.h +55 -0
  211. package/src/headers/26/node_watchdog.h +164 -0
  212. package/src/headers/26/node_webstorage.h +62 -0
  213. package/src/headers/26/node_worker.h +162 -0
  214. package/src/headers/26/path.h +36 -0
  215. package/src/headers/26/permission/boolean_permission.h +50 -0
  216. package/src/headers/26/permission/fs_permission.h +192 -0
  217. package/src/headers/26/permission/permission.h +155 -0
  218. package/src/headers/26/permission/permission_base.h +79 -0
  219. package/src/headers/26/pipe_wrap.h +80 -0
  220. package/src/headers/26/quic/application.h +279 -0
  221. package/src/headers/26/quic/arena.h +373 -0
  222. package/src/headers/26/quic/bindingdata.h +440 -0
  223. package/src/headers/26/quic/cid.h +127 -0
  224. package/src/headers/26/quic/data.h +358 -0
  225. package/src/headers/26/quic/defs.h +417 -0
  226. package/src/headers/26/quic/endpoint.h +519 -0
  227. package/src/headers/26/quic/guard.h +5 -0
  228. package/src/headers/26/quic/http3.h +25 -0
  229. package/src/headers/26/quic/packet.h +166 -0
  230. package/src/headers/26/quic/preferredaddress.h +72 -0
  231. package/src/headers/26/quic/session.h +770 -0
  232. package/src/headers/26/quic/session_manager.h +107 -0
  233. package/src/headers/26/quic/sessionticket.h +110 -0
  234. package/src/headers/26/quic/streams.h +518 -0
  235. package/src/headers/26/quic/tlscontext.h +353 -0
  236. package/src/headers/26/quic/tokens.h +263 -0
  237. package/src/headers/26/quic/transportparams.h +184 -0
  238. package/src/headers/26/req_wrap-inl.h +175 -0
  239. package/src/headers/26/req_wrap.h +79 -0
  240. package/src/headers/26/spawn_sync.h +243 -0
  241. package/src/headers/26/stream_base-inl.h +170 -0
  242. package/src/headers/26/stream_base.h +487 -0
  243. package/src/headers/26/stream_pipe.h +76 -0
  244. package/src/headers/26/stream_wrap.h +137 -0
  245. package/src/headers/26/string_bytes.h +114 -0
  246. package/src/headers/26/string_decoder-inl.h +31 -0
  247. package/src/headers/26/string_decoder.h +49 -0
  248. package/src/headers/26/tcp_wrap.h +138 -0
  249. package/src/headers/26/threadpoolwork-inl.h +70 -0
  250. package/src/headers/26/timer_wrap-inl.h +32 -0
  251. package/src/headers/26/timer_wrap.h +87 -0
  252. package/src/headers/26/timers.h +70 -0
  253. package/src/headers/26/tracing/agent.h +125 -0
  254. package/src/headers/26/tracing/agent_legacy.h +146 -0
  255. package/src/headers/26/tracing/agent_perfetto.h +145 -0
  256. package/src/headers/26/tracing/node_trace_buffer.h +91 -0
  257. package/src/headers/26/tracing/node_trace_writer.h +79 -0
  258. package/src/headers/26/tracing/trace_event.h +27 -0
  259. package/src/headers/26/tracing/trace_event_helper.h +40 -0
  260. package/src/headers/26/tracing/trace_event_legacy.h +1113 -0
  261. package/src/headers/26/tracing/trace_event_legacy_inl.h +711 -0
  262. package/src/headers/26/tracing/trace_event_perfetto.h +47 -0
  263. package/src/headers/26/tracing/traced_value.h +167 -0
  264. package/src/headers/26/tty_wrap.h +65 -0
  265. package/src/headers/26/udp_wrap.h +231 -0
  266. package/src/headers/26/undici_version.h +6 -0
  267. package/src/headers/26/util-inl.h +788 -0
  268. package/src/headers/26/util.h +1207 -0
  269. package/src/headers/26/v8-cpp-heap-external.h +56 -0
  270. package/src/headers/26/v8-external-memory-accounter.h +60 -0
  271. package/src/headers/26/v8-fast-api-calls.h +784 -0
  272. package/src/headers/26/v8-inspector-protocol.h +13 -0
  273. package/src/headers/26/v8-inspector.h +458 -0
  274. package/src/headers/26/v8-metrics.h +273 -0
  275. package/src/headers/26/v8-trace-categories.h +28 -0
  276. package/src/headers/26/v8-unwinder-state.h +30 -0
  277. package/src/headers/26/v8-util.h +549 -0
  278. package/src/headers/26/v8-value-serializer-version.h +24 -0
  279. package/src/headers/26/v8-version-string.h +38 -0
  280. package/src/headers/26/v8-wasm-trap-handler-posix.h +31 -0
  281. package/src/headers/26/v8-wasm-trap-handler-win.h +28 -0
  282. package/src/headers/26/zlib_version.h +6 -0
  283. package/dist/bindings/cws_darwin_arm64_node115.node.dSYM/Contents/Info.plist +0 -20
  284. package/dist/bindings/cws_darwin_arm64_node115.node.dSYM/Contents/Resources/DWARF/cws_darwin_arm64_node115.node +0 -0
  285. package/dist/bindings/cws_darwin_arm64_node115.node.dSYM/Contents/Resources/Relocations/aarch64/cws_darwin_arm64_node115.node.yml +0 -931
  286. package/dist/bindings/cws_darwin_arm64_node127.node.dSYM/Contents/Info.plist +0 -20
  287. package/dist/bindings/cws_darwin_arm64_node127.node.dSYM/Contents/Resources/DWARF/cws_darwin_arm64_node127.node +0 -0
  288. package/dist/bindings/cws_darwin_arm64_node127.node.dSYM/Contents/Resources/Relocations/aarch64/cws_darwin_arm64_node127.node.yml +0 -931
package/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## Released 4.6.0
2
+ * Add Node 26 support (ABI 147, V8 14.6): vendored `src/headers/26`, `MakeCallback` with explicit `async_context`, tagged internal-field reads via `BaseObject::FromJSObject`.
3
+ * Cork writes per event-loop iteration: sends to the same socket within one tick go out in a single gathered write. Disable with `CWS_CORK=0`.
4
+ * Binary messages are detached after the `message` handler returns. A retained `ArrayBuffer` now throws on use instead of exposing another connection's bytes (the receive buffer is shared). Copy with `slice()` if the data is needed later.
5
+ * Fix use-after-free when a send callback closes the socket while its write queue is draining.
6
+ * `terminate()` on an already closed socket is a no-op.
7
+ * The `message` handler is now dispatched through `MakeCallback` like every other handler: correct AsyncLocalStorage propagation, exceptions reach `uncaughtException`, and microtasks drain after each message instead of after the whole poll phase. No measurable throughput cost.
8
+ * Fix `send()` of a typed array that is a subarray (non-zero `byteOffset`): the bytes were read from the start of the underlying ArrayBuffer instead of the view's offset.
9
+
1
10
  ## Released 4.1.0
2
11
  * Add support for socket.bufferedAmount
3
12
 
package/CLAUDE.md ADDED
@@ -0,0 +1,81 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What this is
6
+
7
+ `@encharm/cws` is a native C++ WebSocket implementation for Node.js that aims to be a near drop-in replacement for the `ws` module. It is a fork of ClusterWS/cWS, which itself forked uWebSockets v0.14 (dual MIT + ZLIB license, see `LICENSE` and `src/LICENSE`). Prebuilt binaries for every supported platform/ABI are committed under `dist/bindings/` and published with the package; end users never compile.
8
+
9
+ ## Commands
10
+
11
+ ```sh
12
+ npm run build-ts # lint + tsc: lib/*.ts -> dist/*.js + *.d.ts (dist JS is committed)
13
+ npm run lint # tslint over lib/**/*.ts
14
+ npm test # nyc + mocha, runs tests/**/*.test.ts via ts-node (loads ../lib, not dist)
15
+ npx mocha -r ts-node/register tests/server-client.test.ts -g "ping/pong" --exit # single test
16
+ npm run build-cpp # node-gyp rebuild for the *current* Node ABI only; output goes to build_log.txt, never fails the install
17
+ npm run clean # deletes dist/bindings/* (all committed binaries!) -- don't run casually
18
+ ```
19
+
20
+ Tests bind ports 3000 (ws) and 3001 (wss, certs in `tests/certs/`). The test file currently iterates only `['Non-SSL']`; the SSL loop is wired up but disabled. `tests/autobahn-*.js` are manual Autobahn testsuite harnesses, not part of `npm test`.
21
+
22
+ ### Building the release binaries (multi-ABI)
23
+
24
+ `binding.gyp` is only used by `npm install` for a local build against the running Node. Release binaries are built by the `Makefile` (macOS/Linux) and `make.bat` (Windows), which:
25
+
26
+ 1. Download official Node header tarballs for one pinned version per supported major into `targets/` (`VER_115`=Node 20, `VER_127`=Node 22, `VER_137`=Node 24, `VER_147`=Node 26; the number is the Node ABI / `process.versions.modules`).
27
+ 2. Compile `src/*.cpp` once per ABI with `g++`/`cl` directly, with `-I src/headers/$V` for the matching Node major.
28
+ 3. Emit `dist/bindings/cws_<platform>_<arch>_node<ABI>.node`.
29
+
30
+ ```sh
31
+ make # all three ABIs for the host OS/arch (needs curl + g++/clang)
32
+ ./build-on-docker.sh linux/arm64 # Linux arm64 (native on Apple Silicon, ~1 min)
33
+ ./build-on-docker.sh linux/amd64 # Linux x64 (default; emulated on Apple Silicon, slower)
34
+ make.bat # Windows x64; auto-detects VS 2022 (Build Tools or any edition)
35
+ ```
36
+
37
+ All of it can be driven from the Mac. Docker builds mount the repo and write straight into `dist/bindings/`. The Windows build runs in the Parallels "Windows 11" VM through the shared folder (cmd refuses UNC batch paths, hence `pushd`):
38
+
39
+ ```sh
40
+ prlctl resume "Windows 11"
41
+ prlctl exec "Windows 11" cmd /c "pushd \\\\Mac\\code\\cWS && make.bat" # 4 backslashes in zsh -> \\Mac
42
+ prlctl suspend "Windows 11"
43
+ ```
44
+
45
+ The VM is Windows on ARM: `vcvars64` still emits x64 code, so `make.bat` names outputs `win32_x64` explicitly, and testing them needs an x64 Node in the guest (an x64 zip from nodejs.org extracted under `C:\Temp` works; the guest's own ARM64 Node cannot load them). Docker base image is Ubuntu 22.04 = the minimum glibc baseline. Smoke-test a binding on its target Node with `node node_modules/mocha/bin/mocha -r ts-node/register 'tests/**/*.test.ts' --exit`, e.g. inside `node:22-bookworm` for Linux.
46
+
47
+ Adding a new Node major means: add a `VER_xxx`/ABI line to `Makefile` and `make.bat`, vendor that Node version's private headers into `src/headers/<major>/`, and add a `#if NODE_MAJOR_VERSION==<major>` include block in `src/Addon.h`. Vendoring = copy every `src/**/*.h` from the Node source tarball, plus `deps/ncrypto/ncrypto.h` and any `deps/v8/include/*.h` missing from the official headers tarball, then make `ssl_` public in `crypto/crypto_tls.h` (`TLSWrapSSLGetter` needs it). For 26 there is a second edit: `MaybeStackBuffer::AllocateSufficientStorage` (primary and the concept-constrained `V8Type` partial specialization) and `ToPath` are defined in-class in `util.h` instead of out-of-line in `util-inl.h`, because MSVC `cl.exe` cannot match out-of-line member definitions once that constrained specialization exists (Node builds Windows with clang-cl, which the Parallels VM does not have). Both edits are marked with a `cWS:` comment. Node's gyp-only config macros (e.g. `HAVE_SQLITE`, `HAVE_AMARO` for 26) must be defined before the include. Expect V8 API churn in `Addon.h` on each major; 26 needed tagged internal-field reads (`BaseObject::FromJSObject`) and dropped `Object::GetIsolate` and the `MakeCallback` overload without `async_context`.
48
+
49
+ ## Architecture
50
+
51
+ ### Two layers, one binary
52
+
53
+ - **`lib/*.ts` (thin JS facade)** compiled to `dist/`. `shared.ts` loads the native addon by filename `cws_${platform}_${arch}_node${versions.modules}` and wires native group callbacks (`onConnection`, `onMessage`, `onDisconnection`, …) to per-socket `registeredEvents`. Events are single-listener: `on()` overwrites, with a console warning. `client.ts` exports `WebSocket` (used for both client sockets and server-side peers, distinguished by `options.external`), `server.ts` exports `WebSocketServer` (aliased as `WebSocket.Server`).
54
+ - **`src/` (C++ addon)** is the uWebSockets 0.14 core (`Hub`, `Group`, `WebSocket`, `HTTPSocket`, `Networking`, `WebSocketProtocol.h`, `Extensions` for permessage-deflate) plus the V8 binding layer in `src/Addon.h` / `Addon.cpp`. `Backend.h` picks the event backend: libuv everywhere (`-DUSE_LIBUV` is always set), the epoll backend in `Epoll.*` is legacy and not compiled in by the Makefile.
55
+
56
+ ### How a server connection gets into C++ (the important part)
57
+
58
+ cWS does **not** run its own HTTP listener. `WebSocketServer` uses a Node `http`/`https` server (user-supplied or created internally) and hooks its `'upgrade'` event. In `upgradeConnection()`:
59
+
60
+ 1. It grabs the raw libuv handle from the Node socket (`socket._handle`, or `_parent._handle` for TLS).
61
+ 2. For TLS it calls `native.getSSLContext(socket.ssl)` to pull the raw `SSL*` out of Node's internal `TLSWrap`. This is why `src/headers/<major>/` vendors Node **private** headers (`crypto/crypto_tls.h`, `base_object-inl.h`, etc.) and `Addon.h` defines `NODE_WANT_INTERNALS` around them: `TLSWrapSSLGetter` subclasses `node::crypto::TLSWrap` to reach the protected `ssl_` member. This is the ABI-fragile part and the reason binaries are per-Node-major.
62
+ 3. `native.transfer(fd, ssl)` creates a `Ticket` and the Node socket is destroyed. On its `'close'` event, `native.upgrade(group, ticket, secKey, extensions, protocol)` hands the fd to the uWS `Hub`, which performs the WebSocket handshake and owns the socket from then on. On Linux with Node >= 20 the fd is `dup()`ed first as a workaround for io_uring (see comment in `Addon.h`).
63
+
64
+ `noServer: true` + `handleUpgrade()` follows the same path for `ws` compatibility. `path` and `verifyClient` checks happen in JS before the transfer.
65
+
66
+ ### Client connections
67
+
68
+ The client side never touches Node sockets: `native.connect(clientGroup, url, ws)` lets the uWS `Hub` do TCP/TLS itself using a process-wide `SSL_CTX` created in `cSNode.cpp` (TLS 1.0–1.2 only). `secureProtocol = 'TLSv1_2_method'` is exported for users to pin their https server to match.
69
+
70
+ ### Write corking
71
+
72
+ `Socket::sendTransformed` does not write when the socket is corkable (WebSockets only, plain TCP only) and `NodeData::corkState->enabled` is set. It frames the message into the socket's `messageQueue` and registers the socket in the per-loop `corkState->pending` list. `Socket::flushCorked` drains that list with one `sendmsg`/`WSASend` gather per socket from two libuv hooks registered in `registerCheck` (`src/Addon.h`): a `uv_prepare` (before the loop blocks in poll) and the existing `uv_check` (after poll and after Node's immediates). Anything that cannot be written falls through to the normal `UV_WRITABLE` drain loop in `ioHandler`. `closeSocket`/`onEnd`/`transfer` call `flushCorkedOnClose`, which unregisters the socket and pushes what it can to the kernel without invoking callbacks. `CWS_CORK=0` disables it at addon load (`corkEnabledFromEnv`). `HttpSocket` is never corkable because `Hub::upgrade` deletes it synchronously.
73
+
74
+ ### Things that are easy to get wrong
75
+
76
+ - `dist/*.js` and `dist/bindings/*.node` are committed. Rebuild TS with `npm run build-ts` and commit the output; binaries must be rebuilt on each platform when C++ changes.
77
+ - Binary messages reach JS as an `ArrayBuffer` that is a zero-copy view over the per-loop receive buffer shared by every socket. `onMessage` in `Addon.h` detaches it after the handler returns, so retaining it without `slice()` throws on later use rather than exposing another connection's bytes. Text frames are copied into a V8 string. Ping/pong payloads also go through the string path.
78
+ - Every handler, including `message`, is invoked through `node::MakeCallback`, which establishes an async-context scope and drains nextTick/microtasks after the handler returns. Because that drain happens before `onMessage` detaches the binary `ArrayBuffer`, a continuation that resolves within it still sees the bytes; anything that waits on real I/O sees a detached buffer.
79
+ - `shared.ts` currently has a leftover `console.log('log', ...)` printing the binding path on load.
80
+ - The addon exposes one `Group` per server plus a single shared client group; `ws.external` is the C++ `WebSocket*` and is nulled on disconnect, so every native call in `client.ts` guards on it.
81
+ - Close code defaults to 1005 when the peer sends none (matches `ws`). App-level ping uses the single byte `'9'` (`APP_PING_CODE`) for browser clients that can't see protocol pings.
package/README.md CHANGED
@@ -12,9 +12,10 @@
12
12
 
13
13
  This table is true if you run ssl directly with `cws` (`Node.js`). In case if you use proxy for example `nginx`, `cws` can be run on bigger coverage.
14
14
 
15
- | cWS Version | Node 24 | Node 22 | Node 20 |
16
- |-------------|----------|---------|----------
17
- | 4.5.3 | X | X | X |
15
+ | cWS Version | Node 26 | Node 24 | Node 22 | Node 20 |
16
+ |-------------|---------|----------|---------|----------
17
+ | 4.6.0 | X | X | X | X |
18
+ | 4.5.3 | | X | X | X |
18
19
 
19
20
  ## Documentation
20
21
 
@@ -262,6 +263,12 @@ server.on('upgrade', (request, socket, head) => {
262
263
 
263
264
  **For more information check typings (`*.d.ts`) files in [dist](https://github.com/encharm/cWS/blob/master/dist) folder**
264
265
 
266
+ ### Write corking (performance)
267
+
268
+ All `send()` calls made to a socket during one event-loop iteration are framed immediately but written with a single gathered write (`writev`) when the iteration ends, instead of one syscall per `send()`. This is transparent to callers and preserves ordering; `send()` followed by `close()`/`terminate()` in the same tick still delivers the message. While writes are corked they are counted in `bufferedAmount` until the flush.
269
+
270
+ Set `CWS_CORK=0` in the environment to disable it and write every message immediately (previous behaviour). Corking applies to plain TCP sockets only; TLS sockets always write immediately.
271
+
265
272
  ### Secure WebSocket
266
273
  You can use `wss://` with `cws` by providing `https` server to `cws` and setting `secureProtocol` on https options:
267
274
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@encharm/cws",
3
- "version": "4.5.3",
3
+ "version": "4.6.0",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "description": "cWS - fast C++ WebSocket implementation for Node.js",
package/src/Addon.cpp CHANGED
@@ -3,7 +3,7 @@
3
3
  #include "Addon.h"
4
4
 
5
5
  void Initialize(Local<Object> exports) {
6
- Isolate *isolate = exports->GetIsolate();
6
+ Isolate *isolate = Isolate::GetCurrent();
7
7
  exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "server").ToLocalChecked(),
8
8
  Namespace<cWS::SERVER>(isolate).object);
9
9
  exports->Set(isolate->GetCurrentContext(), String::NewFromUtf8(isolate, "client").ToLocalChecked(),
@@ -20,6 +20,7 @@ void Initialize(Local<Object> exports) {
20
20
  NODE_SET_METHOD(exports, "upgrade", upgrade);
21
21
  NODE_SET_METHOD(exports, "connect", connect);
22
22
  NODE_SET_METHOD(exports, "setNoop", setNoop);
23
+ hub.getNodeData()->corkState->enabled = corkEnabledFromEnv();
23
24
  registerCheck(isolate);
24
25
  }
25
26
 
package/src/Addon.h CHANGED
@@ -24,6 +24,19 @@
24
24
  #include "headers/24/base_object-inl.h"
25
25
  #endif
26
26
 
27
+ #if NODE_MAJOR_VERSION==26
28
+ // Build-configuration macros normally supplied by Node's own gyp build;
29
+ // official binaries are built with both enabled.
30
+ #ifndef HAVE_SQLITE
31
+ #define HAVE_SQLITE 1
32
+ #endif
33
+ #ifndef HAVE_AMARO
34
+ #define HAVE_AMARO 1
35
+ #endif
36
+ #include "headers/26/crypto/crypto_tls.h"
37
+ #include "headers/26/base_object-inl.h"
38
+ #endif
39
+
27
40
  #include <tcp_wrap.h>
28
41
 
29
42
  using BaseObject = node::BaseObject;
@@ -64,8 +77,14 @@ using namespace v8;
64
77
 
65
78
  cWS::Hub hub(0, true);
66
79
  uv_check_t check;
80
+ uv_prepare_t corkPrepare;
67
81
  Persistent<Function> noop;
68
82
 
83
+ // Corked writes are flushed twice per loop iteration: in the prepare phase
84
+ // (right before the loop blocks in poll, so sends made from timers/nextTick/
85
+ // microtasks go out without waiting for I/O) and in the check phase (after
86
+ // poll and after Node's setImmediate callbacks, so sends made from I/O
87
+ // callbacks go out in the same iteration).
69
88
  void registerCheck(Isolate *isolate) {
70
89
  uv_check_init((uv_loop_t *)hub.getLoop(), &check);
71
90
  check.data = isolate;
@@ -73,9 +92,29 @@ void registerCheck(Isolate *isolate) {
73
92
  Isolate *isolate = (Isolate *)check->data;
74
93
  HandleScope hs(isolate);
75
94
  node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(),
76
- Local<Function>::New(isolate, noop), 0, nullptr);
95
+ Local<Function>::New(isolate, noop), 0, nullptr, node::async_context{0, 0});
96
+ cS::Socket::flushCorked(hub.getNodeData());
77
97
  });
78
98
  uv_unref((uv_handle_t *)&check);
99
+
100
+ uv_prepare_init((uv_loop_t *)hub.getLoop(), &corkPrepare);
101
+ uv_prepare_start(&corkPrepare, [](uv_prepare_t *) {
102
+ cS::Socket::flushCorked(hub.getNodeData());
103
+ });
104
+ uv_unref((uv_handle_t *)&corkPrepare);
105
+ }
106
+
107
+ // CWS_CORK=0 (or "false"/"off") disables write corking; anything else enables.
108
+ bool corkEnabledFromEnv() {
109
+ const char *value = getenv("CWS_CORK");
110
+ if (!value) {
111
+ return true;
112
+ }
113
+ std::string v(value);
114
+ for (char &c : v) {
115
+ c = (char) tolower((unsigned char) c);
116
+ }
117
+ return !(v == "0" || v == "false" || v == "off" || v == "no");
79
118
  }
80
119
 
81
120
  class NativeString {
@@ -102,7 +141,9 @@ class NativeString {
102
141
  #if NODE_MAJOR_VERSION >= 18
103
142
  length = arrayBufferView->ByteLength();
104
143
  auto store = arrayBufferView->Buffer()->GetBackingStore();
105
- data = reinterpret_cast<char*>(store->Data());
144
+ // A typed array may be a window into a larger ArrayBuffer (subarray);
145
+ // honour its offset or we would send bytes from the start of the pool.
146
+ data = reinterpret_cast<char*>(store->Data()) + arrayBufferView->ByteOffset();
106
147
  #else
107
148
  ArrayBuffer::Contents contents = arrayBufferView->Buffer()->GetContents();
108
149
  length = contents.ByteLength();
@@ -271,7 +312,7 @@ void sendCallback(cWS::WebSocket<isServer> *webSocket, void *data,
271
312
  HandleScope hs(sc->isolate);
272
313
  node::MakeCallback(sc->isolate, sc->isolate->GetCurrentContext()->Global(),
273
314
  Local<Function>::New(sc->isolate, sc->jsCallback), 0,
274
- nullptr);
315
+ nullptr, node::async_context{0, 0});
275
316
  }
276
317
  sc->jsCallback.Reset();
277
318
  delete sc;
@@ -358,11 +399,15 @@ void transfer(const FunctionCallbackInfo<Value> &args) {
358
399
  Isolate* isolate = args.GetIsolate();
359
400
  Local<Context> context = isolate->GetCurrentContext();
360
401
  auto arg_0 = args[0]->ToObject(context).ToLocalChecked();
402
+ #if NODE_MAJOR_VERSION >= 26
403
+ // V8 14 tags internal-field pointers; Node's own reader knows the tag.
404
+ // The BaseObject* is the start of the TCPWrap (single inheritance chain).
405
+ void *handleWrap = node::BaseObject::FromJSObject(arg_0);
406
+ #else
361
407
  auto field_to_get = arg_0->InternalFieldCount() > 3 ? 1 : 0;
362
-
363
- uv_fileno((handle = getTcpHandle(
364
- arg_0->GetAlignedPointerFromInternalField(field_to_get))),
365
- (uv_os_fd_t *)&ticket->fd);
408
+ void *handleWrap = arg_0->GetAlignedPointerFromInternalField(field_to_get);
409
+ #endif
410
+ uv_fileno((handle = getTcpHandle(handleWrap)), (uv_os_fd_t *)&ticket->fd);
366
411
  } else {
367
412
  ticket->fd = args[0].As<Integer>()->Value();
368
413
  }
@@ -400,7 +445,7 @@ void onConnection(const FunctionCallbackInfo<Value> &args) {
400
445
  Local<Value> argv[] = {wrapSocket(webSocket, isolate)};
401
446
  node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(),
402
447
  Local<Function>::New(isolate, *connectionCallback),
403
- 1, argv);
448
+ 1, argv, node::async_context{0, 0});
404
449
  });
405
450
  }
406
451
 
@@ -424,8 +469,19 @@ void onMessage(const FunctionCallbackInfo<Value> &args) {
424
469
  HandleScope hs(isolate);
425
470
  Local<Value> argv[] = {wrapMessage(message, length, opCode, isolate),
426
471
  getDataV8(webSocket, isolate)};
427
- Local<Function>::New(isolate, *messageCallback)
428
- ->Call(isolate->GetCurrentContext(), Null(isolate), 2, argv);
472
+ node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(),
473
+ Local<Function>::New(isolate, *messageCallback), 2, argv, node::async_context{0, 0});
474
+ // Binary messages are zero-copy views over the shared per-loop receive
475
+ // buffer, which the next socket read overwrites. Detach the ArrayBuffer
476
+ // once the handler returns so a retained reference reads as empty and
477
+ // throws on use, instead of silently exposing another connection's data.
478
+ // Handlers that need the bytes after returning must copy them (slice()).
479
+ if (opCode == cWS::OpCode::BINARY) {
480
+ Local<ArrayBuffer> arrayBuffer = argv[0].template As<ArrayBuffer>();
481
+ if (arrayBuffer->IsDetachable()) {
482
+ arrayBuffer->Detach();
483
+ }
484
+ }
429
485
  }
430
486
  });
431
487
  }
@@ -446,7 +502,7 @@ void onPing(const FunctionCallbackInfo<Value> &args) {
446
502
  wrapMessage(message, length, cWS::OpCode::PING, isolate),
447
503
  getDataV8(webSocket, isolate)};
448
504
  node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(),
449
- Local<Function>::New(isolate, *pingCallback), 2, argv);
505
+ Local<Function>::New(isolate, *pingCallback), 2, argv, node::async_context{0, 0});
450
506
  });
451
507
  }
452
508
 
@@ -466,7 +522,7 @@ void onPong(const FunctionCallbackInfo<Value> &args) {
466
522
  wrapMessage(message, length, cWS::OpCode::PONG, isolate),
467
523
  getDataV8(webSocket, isolate)};
468
524
  node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(),
469
- Local<Function>::New(isolate, *pongCallback), 2, argv);
525
+ Local<Function>::New(isolate, *pongCallback), 2, argv, node::async_context{0, 0});
470
526
  });
471
527
  }
472
528
 
@@ -492,7 +548,7 @@ void onDisconnection(const FunctionCallbackInfo<Value> &args) {
492
548
  getDataV8(webSocket, isolate)};
493
549
  node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(),
494
550
  Local<Function>::New(isolate, *disconnectionCallback), 4,
495
- argv);
551
+ argv, node::async_context{0, 0});
496
552
  });
497
553
  }
498
554
 
@@ -510,7 +566,7 @@ void onError(const FunctionCallbackInfo<Value> &args) {
510
566
  Local<Value> argv[] = {
511
567
  Local<Value>::New(isolate, *(Persistent<Value> *)user)};
512
568
  node::MakeCallback(isolate, isolate->GetCurrentContext()->Global(),
513
- Local<Function>::New(isolate, *errorCallback), 1, argv);
569
+ Local<Function>::New(isolate, *errorCallback), 1, argv, node::async_context{0, 0});
514
570
 
515
571
  ((Persistent<Value> *)user)->Reset();
516
572
  delete (Persistent<Value> *)user;
package/src/Hub.h CHANGED
@@ -75,6 +75,7 @@ public:
75
75
  using cS::Node::run;
76
76
  using cS::Node::poll;
77
77
  using cS::Node::getLoop;
78
+ using cS::Node::getNodeData;
78
79
  using Group<SERVER>::onConnection;
79
80
  using Group<CLIENT>::onConnection;
80
81
  using Group<SERVER>::onTransfer;
package/src/Networking.h CHANGED
@@ -215,6 +215,17 @@ struct WIN32_EXPORT NodeData {
215
215
  std::vector<Poll *> changePollQueue;
216
216
  static void asyncCallback(Async *async);
217
217
 
218
+ // Write corking: every send() issued during one event-loop iteration is
219
+ // queued and flushed with a single gathered write per socket from the
220
+ // loop's prepare/check hooks (see Socket::flushCorked). One CorkState per
221
+ // loop, shared by pointer between every NodeData copy (Groups copy the
222
+ // Hub's NodeData). Disabled with the CWS_CORK=0 environment variable.
223
+ struct CorkState {
224
+ bool enabled = false;
225
+ std::vector<Poll *> pending;
226
+ };
227
+ CorkState *corkState = nullptr;
228
+
218
229
  static int getMemoryBlockIndex(size_t length) {
219
230
  return (int) ((length >> 4) + bool(length & 15));
220
231
  }
package/src/Socket.h CHANGED
@@ -2,6 +2,9 @@
2
2
  #define SOCKET_CWS_H
3
3
 
4
4
  #include "Networking.h"
5
+ #ifndef _WIN32
6
+ #include <sys/uio.h>
7
+ #endif
5
8
 
6
9
  namespace cS {
7
10
 
@@ -33,6 +36,12 @@ protected:
33
36
  void *user = nullptr;
34
37
  NodeData *nodeData;
35
38
 
39
+ // Corking (see NodeData::CorkState). Only WebSockets opt in; HttpSocket
40
+ // objects are deleted synchronously in Hub::upgrade so they must never
41
+ // sit in the pending list. Plain TCP only, SSL keeps the immediate path.
42
+ bool corkable = false;
43
+ bool corkPending = false;
44
+
36
45
  // this is not needed by HttpSocket!
37
46
  struct Queue {
38
47
  struct Message {
@@ -91,6 +100,7 @@ protected:
91
100
  }
92
101
 
93
102
  void transfer(NodeData *nodeData, void (*cb)(Poll *)) {
103
+ flushCorkedOnClose();
94
104
  // userData is invalid from now on till onTransfer
95
105
  setUserData(new TransferData({getFd(), ssl, getCb(), getPoll(), getUserData(), nodeData, cb}));
96
106
  stop(this->nodeData->loop);
@@ -160,10 +170,17 @@ protected:
160
170
  Queue::Message *messagePtr = socket->messageQueue.front();
161
171
  int sent = SSL_write(socket->ssl, messagePtr->data, (int) messagePtr->length);
162
172
  if (sent == (ssize_t) messagePtr->length) {
163
- if (messagePtr->callback) {
164
- messagePtr->callback(p, messagePtr->callbackData, false, messagePtr->reserved);
165
- }
173
+ // Pop before the callback: it may close/terminate this socket,
174
+ // which frees the queue (use-after-free otherwise).
175
+ auto callback = messagePtr->callback;
176
+ void *callbackData = messagePtr->callbackData, *reserved = messagePtr->reserved;
166
177
  socket->messageQueue.pop();
178
+ if (callback) {
179
+ callback(p, callbackData, false, reserved);
180
+ if (socket->isClosed()) {
181
+ return;
182
+ }
183
+ }
167
184
  if (socket->messageQueue.empty()) {
168
185
  if ((socket->state.poll & UV_WRITABLE) && SSL_want(socket->ssl) != SSL_WRITING) {
169
186
  socket->change(socket->nodeData->loop, socket, socket->setPoll(UV_READABLE));
@@ -235,10 +252,17 @@ protected:
235
252
  Queue::Message *messagePtr = socket->messageQueue.front();
236
253
  ssize_t sent = ::send(socket->getFd(), messagePtr->data, messagePtr->length, MSG_NOSIGNAL);
237
254
  if (sent == (ssize_t) messagePtr->length) {
238
- if (messagePtr->callback) {
239
- messagePtr->callback(p, messagePtr->callbackData, false, messagePtr->reserved);
240
- }
255
+ // Pop before the callback: it may close/terminate this socket,
256
+ // which frees the queue (use-after-free otherwise).
257
+ auto callback = messagePtr->callback;
258
+ void *callbackData = messagePtr->callbackData, *reserved = messagePtr->reserved;
241
259
  socket->messageQueue.pop();
260
+ if (callback) {
261
+ callback(p, callbackData, false, reserved);
262
+ if (socket->isClosed()) {
263
+ return;
264
+ }
265
+ }
242
266
  if (socket->messageQueue.empty()) {
243
267
  // todo, remove bit, don't set directly
244
268
  socket->change(socket->nodeData->loop, socket, socket->setPoll(UV_READABLE));
@@ -358,6 +382,21 @@ protected:
358
382
  void sendTransformed(const char *message, size_t length, void(*callback)(void *socket, void *data, bool cancelled, void *reserved), void *callbackData, D transformData) {
359
383
  size_t estimatedLength = T::estimate(message, length) + sizeof(Queue::Message);
360
384
 
385
+ if (corkActive()) {
386
+ // Frame now, write later: everything sent to this socket during the
387
+ // current loop iteration goes out in one gathered write (uncork()).
388
+ Queue::Message *messagePtr = allocMessage(estimatedLength - sizeof(Queue::Message));
389
+ messagePtr->length = T::transform(message, (char *) messagePtr->data, length, transformData);
390
+ messagePtr->callback = callback;
391
+ messagePtr->callbackData = callbackData;
392
+ enqueue(messagePtr);
393
+ if (!corkPending) {
394
+ corkPending = true;
395
+ nodeData->corkState->pending.push_back(this);
396
+ }
397
+ return;
398
+ }
399
+
361
400
  if (hasEmptyQueue()) {
362
401
  if (estimatedLength <= cS::NodeData::preAllocMaxSize) {
363
402
  int memoryLength = (int) estimatedLength;
@@ -477,8 +516,164 @@ public:
477
516
  }
478
517
  }
479
518
 
519
+ // ---- Write corking ------------------------------------------------------
520
+
521
+ bool corkActive() {
522
+ return corkable && !ssl && nodeData->corkState && nodeData->corkState->enabled;
523
+ }
524
+
525
+ void setCorkable(bool enable) {
526
+ corkable = enable;
527
+ }
528
+
529
+ void unregisterCork() {
530
+ if (!corkPending) {
531
+ return;
532
+ }
533
+ corkPending = false;
534
+ std::vector<Poll *> &pending = nodeData->corkState->pending;
535
+ for (size_t i = 0; i < pending.size(); i++) {
536
+ if (pending[i] == this) {
537
+ pending[i] = pending.back();
538
+ pending.pop_back();
539
+ break;
540
+ }
541
+ }
542
+ }
543
+
544
+ // One gathered write of (up to MAX_IOV) messages from the queue head.
545
+ // Returns bytes written or -1 with errno/WSAGetLastError set. Never pops.
546
+ ssize_t writeQueueGathered() {
547
+ static const int MAX_IOV = 64;
548
+ #ifdef _WIN32
549
+ WSABUF bufs[MAX_IOV];
550
+ DWORD n = 0;
551
+ for (Queue::Message *m = messageQueue.front(); m && n < MAX_IOV; m = m->nextMessage) {
552
+ bufs[n].buf = (CHAR *) m->data;
553
+ bufs[n].len = (ULONG) m->length;
554
+ n++;
555
+ }
556
+ DWORD sent = 0;
557
+ if (WSASend(getFd(), bufs, n, &sent, 0, nullptr, nullptr) == SOCKET_ERROR) {
558
+ return -1;
559
+ }
560
+ return (ssize_t) sent;
561
+ #else
562
+ iovec iov[MAX_IOV];
563
+ int n = 0;
564
+ for (Queue::Message *m = messageQueue.front(); m && n < MAX_IOV; m = m->nextMessage) {
565
+ iov[n].iov_base = (void *) m->data;
566
+ iov[n].iov_len = m->length;
567
+ n++;
568
+ }
569
+ msghdr msg = {};
570
+ msg.msg_iov = iov;
571
+ msg.msg_iovlen = n;
572
+ return ::sendmsg(getFd(), &msg, MSG_NOSIGNAL);
573
+ #endif
574
+ }
575
+
576
+ // Consumes `sent` bytes from the queue head. Fully written messages are
577
+ // popped (their callbacks appended to `callbacks` if given); a partially
578
+ // written head is advanced in place. Returns true if the write was partial.
579
+ struct PendingCallback {
580
+ void (*callback)(void *socket, void *data, bool cancelled, void *reserved);
581
+ void *callbackData, *reserved;
582
+ };
583
+
584
+ bool consumeSent(ssize_t sent, std::vector<PendingCallback> *callbacks) {
585
+ while (sent > 0 && !messageQueue.empty()) {
586
+ Queue::Message *m = messageQueue.front();
587
+ if ((size_t) sent >= m->length) {
588
+ sent -= m->length;
589
+ if (callbacks && m->callback) {
590
+ callbacks->push_back({m->callback, m->callbackData, m->reserved});
591
+ }
592
+ messageQueue.pop();
593
+ } else {
594
+ m->length -= sent;
595
+ m->data += sent;
596
+ messageQueue.totalLength -= sent;
597
+ return true;
598
+ }
599
+ }
600
+ return false;
601
+ }
602
+
603
+ // Flush this socket's corked queue. Called from the loop's prepare/check
604
+ // hooks via flushCorked(). Whatever cannot be written now is left to the
605
+ // regular UV_WRITABLE drain loop, which also surfaces hard errors (onEnd).
606
+ void uncork() {
607
+ corkPending = false;
608
+ if (isClosed() || messageQueue.empty()) {
609
+ return;
610
+ }
611
+
612
+ std::vector<PendingCallback> callbacks;
613
+ bool stalled = false;
614
+ while (!messageQueue.empty() && !stalled) {
615
+ ssize_t sent = writeQueueGathered();
616
+ if (sent < 0) {
617
+ stalled = true; // would block, or hard error: drain loop reports it
618
+ } else {
619
+ stalled = consumeSent(sent, &callbacks);
620
+ }
621
+ }
622
+
623
+ if (!messageQueue.empty()) {
624
+ if ((getPoll() & UV_WRITABLE) == 0) {
625
+ setPoll(getPoll() | UV_WRITABLE);
626
+ changePoll(this);
627
+ }
628
+ } else if (getPoll() & UV_WRITABLE) {
629
+ change(nodeData->loop, this, setPoll(UV_READABLE));
630
+ }
631
+
632
+ for (PendingCallback &c : callbacks) {
633
+ c.callback(this, c.callbackData, false, c.reserved);
634
+ if (isClosed()) {
635
+ break; // callback closed us; nothing further may touch the fd
636
+ }
637
+ }
638
+ }
639
+
640
+ // Best effort on close/terminate/transfer: push what was corked during this
641
+ // iteration to the kernel so send() immediately followed by terminate()
642
+ // still delivers. Callbacks are not invoked; the caller drains the queue.
643
+ void flushCorkedOnClose() {
644
+ if (!corkPending) {
645
+ return;
646
+ }
647
+ unregisterCork();
648
+ if (isClosed()) {
649
+ return;
650
+ }
651
+ while (!messageQueue.empty()) {
652
+ ssize_t sent = writeQueueGathered();
653
+ if (sent <= 0 || consumeSent(sent, nullptr)) {
654
+ break;
655
+ }
656
+ }
657
+ }
658
+
659
+ // Loop hook: flush every socket that corked writes since the last hook.
660
+ static void flushCorked(NodeData *nodeData) {
661
+ if (!nodeData->corkState || nodeData->corkState->pending.empty()) {
662
+ return;
663
+ }
664
+ // Swap out the list: callbacks run during uncork() may cork new writes
665
+ // (picked up by the next hook) or close other sockets (their deletion
666
+ // is deferred to libuv's close phase, so pointers stay valid here).
667
+ std::vector<Poll *> batch;
668
+ batch.swap(nodeData->corkState->pending);
669
+ for (Poll *p : batch) {
670
+ ((Socket *) p)->uncork();
671
+ }
672
+ }
673
+
480
674
  template <class T>
481
675
  void closeSocket() {
676
+ unregisterCork();
482
677
  uv_os_sock_t fd = getFd();
483
678
  Context *netContext = nodeData->netContext;
484
679
  stop(nodeData->loop);