@encharm/cws 4.5.2 → 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.
- package/CHANGELOG.md +9 -0
- package/CLAUDE.md +81 -0
- package/README.md +10 -5
- package/dist/bindings/cws_darwin_arm64_node115.node +0 -0
- package/dist/bindings/cws_darwin_arm64_node127.node +0 -0
- package/dist/bindings/cws_darwin_arm64_node137.node +0 -0
- package/dist/bindings/cws_darwin_arm64_node147.node +0 -0
- package/dist/bindings/cws_linux_arm64_node115.node +0 -0
- package/dist/bindings/cws_linux_arm64_node127.node +0 -0
- package/dist/bindings/cws_linux_arm64_node137.node +0 -0
- package/dist/bindings/cws_linux_arm64_node147.node +0 -0
- package/dist/bindings/cws_linux_x64_node115.node +0 -0
- package/dist/bindings/cws_linux_x64_node127.node +0 -0
- package/dist/bindings/cws_linux_x64_node137.node +0 -0
- package/dist/bindings/cws_linux_x64_node147.node +0 -0
- package/dist/bindings/cws_win32_x64_node115.node +0 -0
- package/dist/bindings/cws_win32_x64_node127.node +0 -0
- package/dist/bindings/cws_win32_x64_node137.node +0 -0
- package/dist/bindings/cws_win32_x64_node147.node +0 -0
- package/package.json +1 -1
- package/src/Addon.cpp +2 -1
- package/src/Addon.h +70 -14
- package/src/Hub.h +1 -0
- package/src/Networking.h +11 -0
- package/src/Socket.h +201 -6
- package/src/WebSocket.cpp +4 -1
- package/src/cSNode.cpp +2 -0
- package/src/cSNode.h +4 -0
- package/src/headers/24/tracing/agent.h +195 -0
- package/src/headers/24/tracing/trace_event.h +722 -0
- package/src/headers/26/acorn_version.h +6 -0
- package/src/headers/26/aliased_buffer-inl.h +264 -0
- package/src/headers/26/aliased_buffer.h +205 -0
- package/src/headers/26/aliased_struct-inl.h +143 -0
- package/src/headers/26/aliased_struct.h +248 -0
- package/src/headers/26/amaro_version.h +6 -0
- package/src/headers/26/async_context_frame.h +33 -0
- package/src/headers/26/async_wrap-inl.h +123 -0
- package/src/headers/26/async_wrap.h +254 -0
- package/src/headers/26/base_object-inl.h +333 -0
- package/src/headers/26/base_object.h +339 -0
- package/src/headers/26/base_object_types.h +76 -0
- package/src/headers/26/blob_serializer_deserializer-inl.h +385 -0
- package/src/headers/26/blob_serializer_deserializer.h +142 -0
- package/src/headers/26/builtin_info.h +57 -0
- package/src/headers/26/callback_queue-inl.h +97 -0
- package/src/headers/26/callback_queue.h +79 -0
- package/src/headers/26/cares_wrap.h +475 -0
- package/src/headers/26/cleanup_queue-inl.h +36 -0
- package/src/headers/26/cleanup_queue.h +79 -0
- package/src/headers/26/compile_cache.h +134 -0
- package/src/headers/26/connect_wrap.h +26 -0
- package/src/headers/26/connection_wrap.h +30 -0
- package/src/headers/26/cppgc_helpers-inl.h +69 -0
- package/src/headers/26/cppgc_helpers.h +162 -0
- package/src/headers/26/crypto/crypto_aes.h +107 -0
- package/src/headers/26/crypto/crypto_argon2.h +88 -0
- package/src/headers/26/crypto/crypto_bio.h +193 -0
- package/src/headers/26/crypto/crypto_chacha20_poly1305.h +62 -0
- package/src/headers/26/crypto/crypto_cipher.h +291 -0
- package/src/headers/26/crypto/crypto_common.h +48 -0
- package/src/headers/26/crypto/crypto_context.h +219 -0
- package/src/headers/26/crypto/crypto_dh.h +104 -0
- package/src/headers/26/crypto/crypto_dsa.h +50 -0
- package/src/headers/26/crypto/crypto_ec.h +102 -0
- package/src/headers/26/crypto/crypto_hash.h +157 -0
- package/src/headers/26/crypto/crypto_hkdf.h +62 -0
- package/src/headers/26/crypto/crypto_hmac.h +92 -0
- package/src/headers/26/crypto/crypto_kem.h +131 -0
- package/src/headers/26/crypto/crypto_keygen.h +382 -0
- package/src/headers/26/crypto/crypto_keys.h +366 -0
- package/src/headers/26/crypto/crypto_kmac.h +79 -0
- package/src/headers/26/crypto/crypto_pbkdf2.h +76 -0
- package/src/headers/26/crypto/crypto_pqc.h +39 -0
- package/src/headers/26/crypto/crypto_random.h +127 -0
- package/src/headers/26/crypto/crypto_rsa.h +108 -0
- package/src/headers/26/crypto/crypto_scrypt.h +85 -0
- package/src/headers/26/crypto/crypto_sig.h +159 -0
- package/src/headers/26/crypto/crypto_spkac.h +21 -0
- package/src/headers/26/crypto/crypto_timing.h +20 -0
- package/src/headers/26/crypto/crypto_tls.h +342 -0
- package/src/headers/26/crypto/crypto_turboshake.h +105 -0
- package/src/headers/26/crypto/crypto_util.h +775 -0
- package/src/headers/26/crypto/crypto_x509.h +140 -0
- package/src/headers/26/dataqueue/queue.h +311 -0
- package/src/headers/26/debug_utils-inl.h +291 -0
- package/src/headers/26/debug_utils.h +200 -0
- package/src/headers/26/diagnosticfilename-inl.h +33 -0
- package/src/headers/26/embedded_data.h +17 -0
- package/src/headers/26/encoding_binding.h +59 -0
- package/src/headers/26/env-inl.h +956 -0
- package/src/headers/26/env.h +1294 -0
- package/src/headers/26/env_properties.h +562 -0
- package/src/headers/26/ffi/data.h +30 -0
- package/src/headers/26/ffi/fast.h +96 -0
- package/src/headers/26/ffi/jit_memory.h +26 -0
- package/src/headers/26/ffi/types.h +99 -0
- package/src/headers/26/handle_wrap.h +123 -0
- package/src/headers/26/histogram-inl.h +211 -0
- package/src/headers/26/histogram.h +474 -0
- package/src/headers/26/inspector/dom_storage_agent.h +68 -0
- package/src/headers/26/inspector/inspector_object_utils.h +38 -0
- package/src/headers/26/inspector/io_agent.h +30 -0
- package/src/headers/26/inspector/main_thread_interface.h +116 -0
- package/src/headers/26/inspector/network_agent.h +110 -0
- package/src/headers/26/inspector/network_inspector.h +45 -0
- package/src/headers/26/inspector/network_requests_buffer.h +195 -0
- package/src/headers/26/inspector/network_resource_manager.h +29 -0
- package/src/headers/26/inspector/node_json.h +24 -0
- package/src/headers/26/inspector/node_string.h +101 -0
- package/src/headers/26/inspector/notification_emitter.h +38 -0
- package/src/headers/26/inspector/protocol_helper.h +27 -0
- package/src/headers/26/inspector/runtime_agent.h +41 -0
- package/src/headers/26/inspector/storage_agent.h +33 -0
- package/src/headers/26/inspector/target_agent.h +59 -0
- package/src/headers/26/inspector/target_manager.h +70 -0
- package/src/headers/26/inspector/tracing_agent.h +43 -0
- package/src/headers/26/inspector/worker_agent.h +40 -0
- package/src/headers/26/inspector/worker_inspector.h +124 -0
- package/src/headers/26/inspector_agent.h +168 -0
- package/src/headers/26/inspector_io.h +78 -0
- package/src/headers/26/inspector_profiler.h +149 -0
- package/src/headers/26/inspector_socket.h +60 -0
- package/src/headers/26/inspector_socket_server.h +110 -0
- package/src/headers/26/js_native_api.h +634 -0
- package/src/headers/26/js_native_api_types.h +244 -0
- package/src/headers/26/js_native_api_v8.h +485 -0
- package/src/headers/26/js_native_api_v8_internals.h +45 -0
- package/src/headers/26/js_stream.h +57 -0
- package/src/headers/26/json_utils.h +172 -0
- package/src/headers/26/large_pages/node_large_page.h +34 -0
- package/src/headers/26/lru_cache-inl.h +69 -0
- package/src/headers/26/memory_tracker-inl.h +461 -0
- package/src/headers/26/memory_tracker.h +341 -0
- package/src/headers/26/module_wrap.h +231 -0
- package/src/headers/26/ncrypto.h +2008 -0
- package/src/headers/26/node.h +1693 -0
- package/src/headers/26/node_api.h +265 -0
- package/src/headers/26/node_api_internals.h +48 -0
- package/src/headers/26/node_api_types.h +58 -0
- package/src/headers/26/node_binding.h +170 -0
- package/src/headers/26/node_blob.h +160 -0
- package/src/headers/26/node_bob-inl.h +36 -0
- package/src/headers/26/node_bob.h +107 -0
- package/src/headers/26/node_buffer.h +92 -0
- package/src/headers/26/node_builtins.h +239 -0
- package/src/headers/26/node_concepts.h +46 -0
- package/src/headers/26/node_config_file.h +77 -0
- package/src/headers/26/node_constants.h +82 -0
- package/src/headers/26/node_context_data.h +171 -0
- package/src/headers/26/node_contextify.h +261 -0
- package/src/headers/26/node_crypto.h +66 -0
- package/src/headers/26/node_debug.h +30 -0
- package/src/headers/26/node_diagnostics_channel.h +121 -0
- package/src/headers/26/node_dir.h +52 -0
- package/src/headers/26/node_dotenv.h +45 -0
- package/src/headers/26/node_errors.h +378 -0
- package/src/headers/26/node_exit_code.h +54 -0
- package/src/headers/26/node_external_reference.h +171 -0
- package/src/headers/26/node_ffi.h +212 -0
- package/src/headers/26/node_file-inl.h +421 -0
- package/src/headers/26/node_file.h +561 -0
- package/src/headers/26/node_file_utils.h +39 -0
- package/src/headers/26/node_hash.h +212 -0
- package/src/headers/26/node_http2.h +1202 -0
- package/src/headers/26/node_http2_state.h +159 -0
- package/src/headers/26/node_http_common-inl.h +199 -0
- package/src/headers/26/node_http_common.h +535 -0
- package/src/headers/26/node_i18n.h +132 -0
- package/src/headers/26/node_internals.h +485 -0
- package/src/headers/26/node_locks.h +184 -0
- package/src/headers/26/node_main_instance.h +67 -0
- package/src/headers/26/node_mem-inl.h +113 -0
- package/src/headers/26/node_mem.h +45 -0
- package/src/headers/26/node_messaging.h +389 -0
- package/src/headers/26/node_metadata.h +172 -0
- package/src/headers/26/node_modules.h +102 -0
- package/src/headers/26/node_mutex.h +323 -0
- package/src/headers/26/node_object_wrap.h +145 -0
- package/src/headers/26/node_options-inl.h +524 -0
- package/src/headers/26/node_options.h +710 -0
- package/src/headers/26/node_perf.h +167 -0
- package/src/headers/26/node_perf_common.h +103 -0
- package/src/headers/26/node_platform.h +290 -0
- package/src/headers/26/node_process-inl.h +26 -0
- package/src/headers/26/node_process.h +112 -0
- package/src/headers/26/node_profiling.h +38 -0
- package/src/headers/26/node_realm-inl.h +156 -0
- package/src/headers/26/node_realm.h +236 -0
- package/src/headers/26/node_report.h +43 -0
- package/src/headers/26/node_revert.h +82 -0
- package/src/headers/26/node_root_certs.h +2937 -0
- package/src/headers/26/node_sea.h +101 -0
- package/src/headers/26/node_shadow_realm.h +46 -0
- package/src/headers/26/node_snapshot_builder.h +57 -0
- package/src/headers/26/node_snapshotable.h +167 -0
- package/src/headers/26/node_sockaddr-inl.h +248 -0
- package/src/headers/26/node_sockaddr.h +459 -0
- package/src/headers/26/node_sqlite.h +607 -0
- package/src/headers/26/node_stat_watcher.h +73 -0
- package/src/headers/26/node_task_runner.h +92 -0
- package/src/headers/26/node_threadsafe_cow-inl.h +54 -0
- package/src/headers/26/node_threadsafe_cow.h +105 -0
- package/src/headers/26/node_union_bytes.h +81 -0
- package/src/headers/26/node_url.h +101 -0
- package/src/headers/26/node_url_pattern.h +118 -0
- package/src/headers/26/node_v8.h +78 -0
- package/src/headers/26/node_v8_embedder.h +26 -0
- package/src/headers/26/node_v8_platform-inl.h +96 -0
- package/src/headers/26/node_version.h +116 -0
- package/src/headers/26/node_wasi.h +184 -0
- package/src/headers/26/node_wasm_web_api.h +55 -0
- package/src/headers/26/node_watchdog.h +164 -0
- package/src/headers/26/node_webstorage.h +62 -0
- package/src/headers/26/node_worker.h +162 -0
- package/src/headers/26/path.h +36 -0
- package/src/headers/26/permission/boolean_permission.h +50 -0
- package/src/headers/26/permission/fs_permission.h +192 -0
- package/src/headers/26/permission/permission.h +155 -0
- package/src/headers/26/permission/permission_base.h +79 -0
- package/src/headers/26/pipe_wrap.h +80 -0
- package/src/headers/26/quic/application.h +279 -0
- package/src/headers/26/quic/arena.h +373 -0
- package/src/headers/26/quic/bindingdata.h +440 -0
- package/src/headers/26/quic/cid.h +127 -0
- package/src/headers/26/quic/data.h +358 -0
- package/src/headers/26/quic/defs.h +417 -0
- package/src/headers/26/quic/endpoint.h +519 -0
- package/src/headers/26/quic/guard.h +5 -0
- package/src/headers/26/quic/http3.h +25 -0
- package/src/headers/26/quic/packet.h +166 -0
- package/src/headers/26/quic/preferredaddress.h +72 -0
- package/src/headers/26/quic/session.h +770 -0
- package/src/headers/26/quic/session_manager.h +107 -0
- package/src/headers/26/quic/sessionticket.h +110 -0
- package/src/headers/26/quic/streams.h +518 -0
- package/src/headers/26/quic/tlscontext.h +353 -0
- package/src/headers/26/quic/tokens.h +263 -0
- package/src/headers/26/quic/transportparams.h +184 -0
- package/src/headers/26/req_wrap-inl.h +175 -0
- package/src/headers/26/req_wrap.h +79 -0
- package/src/headers/26/spawn_sync.h +243 -0
- package/src/headers/26/stream_base-inl.h +170 -0
- package/src/headers/26/stream_base.h +487 -0
- package/src/headers/26/stream_pipe.h +76 -0
- package/src/headers/26/stream_wrap.h +137 -0
- package/src/headers/26/string_bytes.h +114 -0
- package/src/headers/26/string_decoder-inl.h +31 -0
- package/src/headers/26/string_decoder.h +49 -0
- package/src/headers/26/tcp_wrap.h +138 -0
- package/src/headers/26/threadpoolwork-inl.h +70 -0
- package/src/headers/26/timer_wrap-inl.h +32 -0
- package/src/headers/26/timer_wrap.h +87 -0
- package/src/headers/26/timers.h +70 -0
- package/src/headers/26/tracing/agent.h +125 -0
- package/src/headers/26/tracing/agent_legacy.h +146 -0
- package/src/headers/26/tracing/agent_perfetto.h +145 -0
- package/src/headers/26/tracing/node_trace_buffer.h +91 -0
- package/src/headers/26/tracing/node_trace_writer.h +79 -0
- package/src/headers/26/tracing/trace_event.h +27 -0
- package/src/headers/26/tracing/trace_event_helper.h +40 -0
- package/src/headers/26/tracing/trace_event_legacy.h +1113 -0
- package/src/headers/26/tracing/trace_event_legacy_inl.h +711 -0
- package/src/headers/26/tracing/trace_event_perfetto.h +47 -0
- package/src/headers/26/tracing/traced_value.h +167 -0
- package/src/headers/26/tty_wrap.h +65 -0
- package/src/headers/26/udp_wrap.h +231 -0
- package/src/headers/26/undici_version.h +6 -0
- package/src/headers/26/util-inl.h +788 -0
- package/src/headers/26/util.h +1207 -0
- package/src/headers/26/v8-cpp-heap-external.h +56 -0
- package/src/headers/26/v8-external-memory-accounter.h +60 -0
- package/src/headers/26/v8-fast-api-calls.h +784 -0
- package/src/headers/26/v8-inspector-protocol.h +13 -0
- package/src/headers/26/v8-inspector.h +458 -0
- package/src/headers/26/v8-metrics.h +273 -0
- package/src/headers/26/v8-trace-categories.h +28 -0
- package/src/headers/26/v8-unwinder-state.h +30 -0
- package/src/headers/26/v8-util.h +549 -0
- package/src/headers/26/v8-value-serializer-version.h +24 -0
- package/src/headers/26/v8-version-string.h +38 -0
- package/src/headers/26/v8-wasm-trap-handler-posix.h +31 -0
- package/src/headers/26/v8-wasm-trap-handler-win.h +28 -0
- package/src/headers/26/zlib_version.h +6 -0
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,11 +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
|
|
16
|
-
|
|
17
|
-
| 4.
|
|
18
|
-
|
|
19
|
-
Node <14 are supported with @clusterws/cws.
|
|
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 |
|
|
20
19
|
|
|
21
20
|
## Documentation
|
|
22
21
|
|
|
@@ -264,6 +263,12 @@ server.on('upgrade', (request, socket, head) => {
|
|
|
264
263
|
|
|
265
264
|
**For more information check typings (`*.d.ts`) files in [dist](https://github.com/encharm/cWS/blob/master/dist) folder**
|
|
266
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
|
+
|
|
267
272
|
### Secure WebSocket
|
|
268
273
|
You can use `wss://` with `cws` by providing `https` server to `cws` and setting `secureProtocol` on https options:
|
|
269
274
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
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 =
|
|
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
|
-
|
|
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
|
-
|
|
364
|
-
|
|
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
|
-
|
|
428
|
-
|
|
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
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
|
-
|
|
164
|
-
|
|
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
|
-
|
|
239
|
-
|
|
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);
|