@encharm/cws 4.8.0 → 4.8.3

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 CHANGED
@@ -1,3 +1,13 @@
1
+ ## Released 4.8.3
2
+ * Corked sends up to 1 KB use the per-loop block pool instead of malloc/free per message; queued messages remember their pool block. Also initialise the `reserved` callback argument, which was passed uninitialised for messages that hit the write queue.
3
+ * JS: resolve the native namespace once per socket instead of on every `send`/`close`/`ping`.
4
+
5
+ ## Released 4.8.2
6
+ * Accept compressed messages that end with a BFINAL=1 DEFLATE block (RFC 7692 section 7.2.3.4). Such clients (libdeflate-based and some non-zlib implementations) were disconnected on their first compressed message.
7
+
8
+ ## Released 4.8.1
9
+ * Skip the kernel-level cork/uncork (two `setsockopt` calls) around every socket read when write corking is active; those writes leave in the end-of-tick gathered write anyway. Saves 1-2 us of server CPU per read.
10
+
1
11
  ## Released 4.8.0
2
12
  * Prebuilt bindings now compress with zlib-ng 2.2.4 (vendored in `deps/zlib-ng`, statically linked, native `zng_` API so it cannot collide with Node's zlib). Measured on a real RPC stream: level 1 costs 2.2-2.5 us/KB vs 5.6-8 us/KB with zlib. The node-gyp fallback build keeps using Node's zlib through the same `src/Zlib.cpp` wrapper.
3
13
  * New `perMessageDeflate.level` option (default 2) and a `zlibBackend` export naming the compiled-in implementation.
package/README.md CHANGED
@@ -14,6 +14,9 @@ This table is true if you run ssl directly with `cws` (`Node.js`). In case if yo
14
14
 
15
15
  | cWS Version | Node 26 | Node 24 | Node 22 | Node 20 |
16
16
  |-------------|---------|----------|---------|----------
17
+ | 4.8.3 | X | X | X | X |
18
+ | 4.8.2 | X | X | X | X |
19
+ | 4.8.1 | X | X | X | X |
17
20
  | 4.8.0 | X | X | X | X |
18
21
  | 4.7.0 | X | X | X | X |
19
22
  | 4.6.0 | X | X | X | X |
package/dist/client.d.ts CHANGED
@@ -13,6 +13,7 @@ export declare class WebSocket {
13
13
  private external;
14
14
  private socketType;
15
15
  private compressThreshold;
16
+ private nativeApi;
16
17
  constructor(url: string, options?: any);
17
18
  get bufferedAmount(): number;
18
19
  get _socket(): SocketAddress;
package/dist/client.js CHANGED
@@ -26,8 +26,10 @@ class WebSocket {
26
26
  message: shared_1.noop
27
27
  };
28
28
  this.socketType = 'client';
29
+ this.nativeApi = shared_1.native.client;
29
30
  if (!this.url && this.options.external) {
30
31
  this.socketType = 'server';
32
+ this.nativeApi = shared_1.native.server;
31
33
  this.external = this.options.external;
32
34
  this.compressThreshold = this.options.compressThreshold;
33
35
  }
@@ -90,7 +92,7 @@ class WebSocket {
90
92
  else if (this.compressThreshold !== undefined) {
91
93
  compress = messageByteLength(message) >= this.compressThreshold;
92
94
  }
93
- shared_1.native[this.socketType].send(this.external, message, opCode, cb ? () => process.nextTick(cb) : null, compress);
95
+ this.nativeApi.send(this.external, message, opCode, cb ? () => process.nextTick(cb) : null, compress);
94
96
  }
95
97
  else if (cb) {
96
98
  cb(new Error('Socket not connected'));
@@ -98,18 +100,18 @@ class WebSocket {
98
100
  }
99
101
  ping(message) {
100
102
  if (this.external) {
101
- shared_1.native[this.socketType].send(this.external, message, shared_1.OPCODE_PING);
103
+ this.nativeApi.send(this.external, message, shared_1.OPCODE_PING);
102
104
  }
103
105
  }
104
106
  close(code = 1000, reason) {
105
107
  if (this.external) {
106
- shared_1.native[this.socketType].close(this.external, code, reason);
108
+ this.nativeApi.close(this.external, code, reason);
107
109
  this.external = null;
108
110
  }
109
111
  }
110
112
  terminate() {
111
113
  if (this.external) {
112
- shared_1.native[this.socketType].terminate(this.external);
114
+ this.nativeApi.terminate(this.external);
113
115
  this.external = null;
114
116
  }
115
117
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@encharm/cws",
3
- "version": "4.8.0",
3
+ "version": "4.8.3",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "description": "cWS - fast C++ WebSocket implementation for Node.js",
@@ -289,7 +289,7 @@ void HttpSocket<isServer>::onEnd(cS::Socket *s) {
289
289
  if (message->callback) {
290
290
  message->callback(nullptr, message->callbackData, true, nullptr);
291
291
  }
292
- httpSocket->messageQueue.pop();
292
+ httpSocket->messageQueue.pop(httpSocket->nodeData);
293
293
  }
294
294
 
295
295
  while (httpSocket->outstandingResponsesHead) {
package/src/Socket.h CHANGED
@@ -50,20 +50,32 @@ protected:
50
50
  Message *nextMessage = nullptr;
51
51
  void (*callback)(void *socket, void *data, bool cancelled, void *reserved) = nullptr;
52
52
  void *callbackData = nullptr, *reserved = nullptr;
53
+ // >= 0: block came from NodeData's small-block pool (index for freeSmallMemoryBlock);
54
+ // -1: heap (new char[]). Messages are allocated as raw char arrays, so every
55
+ // allocation site must set this explicitly (the initializers above never run).
56
+ int poolIndex = -1;
53
57
  };
54
58
 
55
59
  Message *head = nullptr, *tail = nullptr;
56
60
  size_t totalLength = 0;
57
61
 
58
- void pop()
62
+ static void release(NodeData *nodeData, Message *message) {
63
+ if (message->poolIndex >= 0) {
64
+ nodeData->freeSmallMemoryBlock((char *) message, message->poolIndex);
65
+ } else {
66
+ delete [] (char *) message;
67
+ }
68
+ }
69
+
70
+ void pop(NodeData *nodeData)
59
71
  {
60
72
  Message *nextMessage;
61
73
  if ((nextMessage = head->nextMessage)) {
62
74
  totalLength -= head->length;
63
- delete [] (char *) head;
75
+ release(nodeData, head);
64
76
  head = nextMessage;
65
77
  } else {
66
- delete [] (char *) head;
78
+ release(nodeData, head);
67
79
  head = tail = nullptr;
68
80
  totalLength = 0;
69
81
  }
@@ -174,7 +186,7 @@ protected:
174
186
  // which frees the queue (use-after-free otherwise).
175
187
  auto callback = messagePtr->callback;
176
188
  void *callbackData = messagePtr->callbackData, *reserved = messagePtr->reserved;
177
- socket->messageQueue.pop();
189
+ socket->messageQueue.pop(socket->nodeData);
178
190
  if (callback) {
179
191
  callback(p, callbackData, false, reserved);
180
192
  if (socket->isClosed()) {
@@ -256,7 +268,7 @@ protected:
256
268
  // which frees the queue (use-after-free otherwise).
257
269
  auto callback = messagePtr->callback;
258
270
  void *callbackData = messagePtr->callbackData, *reserved = messagePtr->reserved;
259
- socket->messageQueue.pop();
271
+ socket->messageQueue.pop(socket->nodeData);
260
272
  if (callback) {
261
273
  callback(p, callbackData, false, reserved);
262
274
  if (socket->isClosed()) {
@@ -318,6 +330,10 @@ protected:
318
330
  messagePtr->length = length;
319
331
  messagePtr->data = ((char *) messagePtr) + sizeof(Queue::Message);
320
332
  messagePtr->nextMessage = nullptr;
333
+ messagePtr->callback = nullptr;
334
+ messagePtr->callbackData = nullptr;
335
+ messagePtr->reserved = nullptr;
336
+ messagePtr->poolIndex = -1;
321
337
 
322
338
  if (data) {
323
339
  memcpy((char *) messagePtr->data, data, messagePtr->length);
@@ -385,10 +401,21 @@ protected:
385
401
  if (corkActive()) {
386
402
  // Frame now, write later: everything sent to this socket during the
387
403
  // current loop iteration goes out in one gathered write (uncork()).
388
- Queue::Message *messagePtr = allocMessage(estimatedLength - sizeof(Queue::Message));
404
+ // Small messages come from the per-loop block pool (pop() returns them).
405
+ Queue::Message *messagePtr;
406
+ if (estimatedLength <= cS::NodeData::preAllocMaxSize) {
407
+ int memoryIndex = nodeData->getMemoryBlockIndex((int) estimatedLength);
408
+ messagePtr = (Queue::Message *) nodeData->getSmallMemoryBlock(memoryIndex);
409
+ messagePtr->data = ((char *) messagePtr) + sizeof(Queue::Message);
410
+ messagePtr->poolIndex = memoryIndex;
411
+ } else {
412
+ messagePtr = allocMessage(estimatedLength - sizeof(Queue::Message));
413
+ }
389
414
  messagePtr->length = T::transform(message, (char *) messagePtr->data, length, transformData);
415
+ messagePtr->nextMessage = nullptr;
390
416
  messagePtr->callback = callback;
391
417
  messagePtr->callbackData = callbackData;
418
+ messagePtr->reserved = nullptr;
392
419
  enqueue(messagePtr);
393
420
  if (!corkPending) {
394
421
  corkPending = true;
@@ -405,6 +432,9 @@ protected:
405
432
  Queue::Message *messagePtr = (Queue::Message *) nodeData->getSmallMemoryBlock(memoryIndex);
406
433
  messagePtr->data = ((char *) messagePtr) + sizeof(Queue::Message);
407
434
  messagePtr->length = T::transform(message, (char *) messagePtr->data, length, transformData);
435
+ messagePtr->nextMessage = nullptr;
436
+ messagePtr->reserved = nullptr;
437
+ messagePtr->poolIndex = memoryIndex;
408
438
 
409
439
  bool wasTransferred;
410
440
  if (write(messagePtr, wasTransferred)) {
@@ -589,7 +619,7 @@ public:
589
619
  if (callbacks && m->callback) {
590
620
  callbacks->push_back({m->callback, m->callbackData, m->reserved});
591
621
  }
592
- messageQueue.pop();
622
+ messageQueue.pop(nodeData);
593
623
  } else {
594
624
  m->length -= sent;
595
625
  m->data += sent;
package/src/WebSocket.cpp CHANGED
@@ -151,6 +151,9 @@ void WebSocket<isServer>::sendPrepared(typename WebSocket<isServer>::PreparedMes
151
151
  Queue::Message *messagePtr = (Queue::Message *) nodeData->getSmallMemoryBlock(memoryIndex);
152
152
  messagePtr->data = preparedMessage->buffer;
153
153
  messagePtr->length = preparedMessage->length;
154
+ messagePtr->nextMessage = nullptr;
155
+ messagePtr->reserved = nullptr;
156
+ messagePtr->poolIndex = memoryIndex;
154
157
 
155
158
  bool wasTransferred;
156
159
  if (write(messagePtr, wasTransferred)) {
@@ -195,9 +198,15 @@ cS::Socket *WebSocket<isServer>::onData(cS::Socket *s, char *data, size_t length
195
198
 
196
199
  webSocket->hasOutstandingPong = false;
197
200
  if (!webSocket->isShuttingDown()) {
198
- webSocket->cork(true);
201
+ // The kernel-level cork (TCP_CORK/TCP_NOPUSH) around a read only helps when
202
+ // replies are written immediately; with write corking they leave in one gathered
203
+ // write at the end of the tick, so skip the two setsockopt calls per read.
204
+ bool kernelCork = !webSocket->corkActive();
205
+ if (kernelCork) {
206
+ webSocket->cork(true);
207
+ }
199
208
  WebSocketProtocol<isServer, WebSocket<isServer>>::consume(data, (unsigned int) length, webSocket);
200
- if (!webSocket->isClosed()) {
209
+ if (kernelCork && !webSocket->isClosed()) {
201
210
  webSocket->cork(false);
202
211
  }
203
212
  }
@@ -306,7 +315,7 @@ void WebSocket<isServer>::onEnd(cS::Socket *s) {
306
315
  if (message->callback) {
307
316
  message->callback(nullptr, message->callbackData, true, nullptr);
308
317
  }
309
- webSocket->messageQueue.pop();
318
+ webSocket->messageQueue.pop(webSocket->nodeData);
310
319
  }
311
320
 
312
321
  webSocket->nodeData->clearPendingPollChanges(webSocket);
package/src/Zlib.cpp CHANGED
@@ -106,7 +106,10 @@ char *inflate(Stream *stream, char *data, size_t &length, size_t maxPayload, cha
106
106
  zs.next_out = (OutPtr) buffer;
107
107
  zs.avail_out = (unsigned int) bufferSize;
108
108
  err = ZFN(inflate)(&zs, Z_FINISH);
109
- if (!zs.avail_in) {
109
+ // Z_STREAM_END: the sender ended the message with a BFINAL=1 block, which
110
+ // RFC 7692 section 7.2.3.4 permits (libdeflate and some non-zlib clients do
111
+ // this); anything left in the input is the 4-byte tail and is ignored.
112
+ if (!zs.avail_in || err == Z_STREAM_END) {
110
113
  break;
111
114
  }
112
115
 
@@ -115,7 +118,7 @@ char *inflate(Stream *stream, char *data, size_t &length, size_t maxPayload, cha
115
118
 
116
119
  ZFN(inflateReset)(&zs);
117
120
 
118
- if ((err != Z_BUF_ERROR && err != Z_OK) || dynamic.length() > maxPayload) {
121
+ if ((err != Z_BUF_ERROR && err != Z_OK && err != Z_STREAM_END) || dynamic.length() > maxPayload) {
119
122
  length = 0;
120
123
  return nullptr;
121
124
  }