| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 81819ad commit 3bdb64d
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -92,7 +92,7 @@ class BroadcastImpl { | |||
| 92 | 92 | #options; | |
| 93 | 93 | #writer = null; | |
| 94 | 94 | #cachedMinCursor = 0; | |
| 95 | - #minCursorDirty = false; | ||
| 95 | + #cachedMinCursorConsumers = 0; | ||
| 96 | 96 | ||
| 97 | 97 | constructor(options) { | |
| 98 | 98 | this.#options = options; | |
@@ -150,13 +150,13 @@ class BroadcastImpl { | |||
| 150 | 150 | }; | |
| 151 | 151 | ||
| 152 | 152 | this.#consumers.add(state); | |
| 153 | - // New consumer starts at buffer start; recalculate min cursor | ||
| 154 | - // since this consumer may now be the slowest. | ||
| 155 | 153 | if (this.#consumers.size === 1) { | |
| 156 | 154 | this.#cachedMinCursor = state.cursor; | |
| 157 | - this.#minCursorDirty = false; | ||
| 155 | + this.#cachedMinCursorConsumers = 1; | ||
| 156 | + } else if (state.cursor === this.#cachedMinCursor) { | ||
| 157 | + this.#cachedMinCursorConsumers++; | ||
| 158 | 158 | } else { | |
| 159 | - this.#minCursorDirty = true; | ||
| 159 | + this.#recomputeMinCursor(); | ||
| 160 | 160 | } | |
| 161 | 161 | const self = this; | |
| 162 | 162 | ||
@@ -167,9 +167,9 @@ class BroadcastImpl { | |||
| 167 | 167 | state.detached = true; | |
| 168 | 168 | state.resolve = null; | |
| 169 | 169 | state.reject = null; | |
| 170 | - self.#consumers.delete(state); | ||
| 171 | - self.#minCursorDirty = true; | ||
| 172 | - self.#tryTrimBuffer(); | ||
| 170 | + if (self.#deleteConsumer(state)) { | ||
| 171 | + self.#tryTrimBuffer(); | ||
| 172 | + } | ||
| 173 | 173 | } | |
| 174 | 174 | ||
| 175 | 175 | return { | |
@@ -186,19 +186,19 @@ class BroadcastImpl { | |||
| 186 | 186 | const bufferIndex = state.cursor - self.#bufferStart; | |
| 187 | 187 | if (bufferIndex < self.#buffer.length) { | |
| 188 | 188 | const chunk = self.#buffer.get(bufferIndex); | |
| 189 | - // If this consumer was at the min cursor, mark dirty | ||
| 190 | - if (state.cursor <= self.#cachedMinCursor) { | ||
| 191 | - self.#minCursorDirty = true; | ||
| 192 | - } | ||
| 189 | + const cursor = state.cursor; | ||
| 193 | 190 | state.cursor++; | |
| 194 | - self.#tryTrimBuffer(); | ||
| 191 | + if (cursor === self.#cachedMinCursor && | ||
| 192 | + --self.#cachedMinCursorConsumers === 0) { | ||
| 193 | + self.#tryTrimBuffer(); | ||
| 194 | + } | ||
| 195 | 195 | return PromiseResolve( | |
| 196 | 196 | { __proto__: null, done: false, value: chunk }); | |
| 197 | 197 | } | |
| 198 | 198 | ||
| 199 | 199 | if (self.#error) { | |
| 200 | 200 | state.detached = true; | |
| 201 | - self.#consumers.delete(state); | ||
| 201 | + self.#deleteConsumer(state); | ||
| 202 | 202 | return PromiseReject(self.#error); | |
| 203 | 203 | } | |
| 204 | 204 | ||
@@ -253,6 +253,7 @@ class BroadcastImpl { | |||
| 253 | 253 | consumer.detached = true; | |
| 254 | 254 | } | |
| 255 | 255 | this.#consumers.clear(); | |
| 256 | + this.#cachedMinCursorConsumers = 0; | ||
| 256 | 257 | } | |
| 257 | 258 | ||
| 258 | 259 | [SymbolDispose]() { | |
@@ -274,9 +275,11 @@ class BroadcastImpl { | |||
| 274 | 275 | this.#bufferStart++; | |
| 275 | 276 | for (const consumer of this.#consumers) { | |
| 276 | 277 | if (consumer.cursor < this.#bufferStart) { | |
| 278 | + this.#deleteConsumerFromMin(consumer); | ||
| 277 | 279 | consumer.cursor = this.#bufferStart; | |
| 278 | 280 | } | |
| 279 | 281 | } | |
| 282 | + this.#recomputeMinCursor(); | ||
| 280 | 283 | break; | |
| 281 | 284 | case 'drop-newest': | |
| 282 | 285 | return true; | |
@@ -297,7 +300,12 @@ class BroadcastImpl { | |||
| 297 | 300 | const bufferIndex = consumer.cursor - this.#bufferStart; | |
| 298 | 301 | if (bufferIndex < this.#buffer.length) { | |
| 299 | 302 | const chunk = this.#buffer.get(bufferIndex); | |
| 303 | + const cursor = consumer.cursor; | ||
| 300 | 304 | consumer.cursor++; | |
| 305 | + if (cursor === this.#cachedMinCursor && | ||
| 306 | + --this.#cachedMinCursorConsumers === 0) { | ||
| 307 | + this.#tryTrimBuffer(); | ||
| 308 | + } | ||
| 301 | 309 | consumer.resolve({ __proto__: null, done: false, value: chunk }); | |
| 302 | 310 | } else { | |
| 303 | 311 | consumer.resolve({ __proto__: null, done: true, value: undefined }); | |
@@ -323,6 +331,7 @@ class BroadcastImpl { | |||
| 323 | 331 | consumer.detached = true; | |
| 324 | 332 | } | |
| 325 | 333 | this.#consumers.clear(); | |
| 334 | + this.#cachedMinCursorConsumers = 0; | ||
| 326 | 335 | } | |
| 327 | 336 | ||
| 328 | 337 | [kGetDesiredSize]() { | |
@@ -343,14 +352,14 @@ class BroadcastImpl { | |||
| 343 | 352 | // Private methods | |
| 344 | 353 | ||
| 345 | 354 | #recomputeMinCursor() { | |
| 346 | - const { minCursor } = getMinCursor( | ||
| 355 | + const { minCursor, minCursorConsumers } = getMinCursor( | ||
| 347 | 356 | this.#consumers, this.#bufferStart + this.#buffer.length); | |
| 348 | 357 | this.#cachedMinCursor = minCursor; | |
| 349 | - this.#minCursorDirty = false; | ||
| 358 | + this.#cachedMinCursorConsumers = minCursorConsumers; | ||
| 350 | 359 | } | |
| 351 | 360 | ||
| 352 | 361 | #tryTrimBuffer() { | |
| 353 | - if (this.#minCursorDirty) { | ||
| 362 | + if (this.#cachedMinCursorConsumers === 0) { | ||
| 354 | 363 | this.#recomputeMinCursor(); | |
| 355 | 364 | } | |
| 356 | 365 | const trimCount = this.#cachedMinCursor - this.#bufferStart; | |
@@ -377,10 +386,12 @@ class BroadcastImpl { | |||
| 377 | 386 | const bufferIndex = consumer.cursor - this.#bufferStart; | |
| 378 | 387 | if (bufferIndex < this.#buffer.length) { | |
| 379 | 388 | const chunk = this.#buffer.get(bufferIndex); | |
| 380 | - if (consumer.cursor <= this.#cachedMinCursor) { | ||
| 381 | - this.#minCursorDirty = true; | ||
| 382 | - } | ||
| 389 | + const cursor = consumer.cursor; | ||
| 383 | 390 | consumer.cursor++; | |
| 391 | + if (cursor === this.#cachedMinCursor && | ||
| 392 | + --this.#cachedMinCursorConsumers === 0) { | ||
| 393 | + this.#tryTrimBuffer(); | ||
| 394 | + } | ||
| 384 | 395 | const resolve = consumer.resolve; | |
| 385 | 396 | consumer.resolve = null; | |
| 386 | 397 | consumer.reject = null; | |
@@ -392,6 +403,21 @@ class BroadcastImpl { | |||
| 392 | 403 | } | |
| 393 | 404 | } | |
| 394 | 405 | } | |
| 406 | + | ||
| 407 | + #deleteConsumerFromMin(consumer) { | ||
| 408 | + if (consumer.cursor === this.#cachedMinCursor) { | ||
| 409 | + this.#cachedMinCursorConsumers--; | ||
| 410 | + return this.#cachedMinCursorConsumers === 0; | ||
| 411 | + } | ||
| 412 | + return false; | ||
| 413 | + } | ||
| 414 | + | ||
| 415 | + #deleteConsumer(consumer) { | ||
| 416 | + if (this.#consumers.delete(consumer)) { | ||
| 417 | + return this.#deleteConsumerFromMin(consumer); | ||
| 418 | + } | ||
| 419 | + return false; | ||
| 420 | + } | ||
| 395 | 421 | } | |
| 396 | 422 | ||
| 397 | 423 | // ============================================================================= | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -96,6 +96,33 @@ async function testRingbufferGrow() { | |||
| 96 | 96 | } | |
| 97 | 97 | } | |
| 98 | 98 | ||
| 99 | + // Multiple consumers at the minimum cursor should trim only after the last | ||
| 100 | + // one advances or detaches. | ||
| 101 | + async function testFanOutMinCursorTrimming() { | ||
| 102 | + const { writer, broadcast: bc } = broadcast({ highWaterMark: 4 }); | ||
| 103 | + const iter1 = bc.push()[Symbol.asyncIterator](); | ||
| 104 | + const iter2 = bc.push()[Symbol.asyncIterator](); | ||
| 105 | + | ||
| 106 | + writer.writeSync(new Uint8Array([1])); | ||
| 107 | + writer.writeSync(new Uint8Array([2])); | ||
| 108 | + assert.strictEqual(bc.bufferSize, 2); | ||
| 109 | + | ||
| 110 | + assert.strictEqual((await iter1.next()).done, false); | ||
| 111 | + assert.strictEqual(bc.bufferSize, 2); | ||
| 112 | + | ||
| 113 | + assert.strictEqual((await iter2.next()).done, false); | ||
| 114 | + assert.strictEqual(bc.bufferSize, 1); | ||
| 115 | + | ||
| 116 | + await iter1.return(); | ||
| 117 | + assert.strictEqual(bc.bufferSize, 1); | ||
| 118 | + | ||
| 119 | + assert.strictEqual((await iter2.next()).done, false); | ||
| 120 | + assert.strictEqual(bc.bufferSize, 0); | ||
| 121 | + | ||
| 122 | + writer.endSync(); | ||
| 123 | + assert.strictEqual((await iter2.next()).done, true); | ||
| 124 | + } | ||
| 125 | + | ||
| 99 | 126 | // Broadcast drainableProtocol after close returns null | |
| 100 | 127 | async function testDrainableAfterClose() { | |
| 101 | 128 | const { drainableProtocol } = require('stream/iter'); | |
@@ -111,5 +138,6 @@ Promise.all([ | |||
| 111 | 138 | testBroadcastFromSyncIterable(), | |
| 112 | 139 | testBroadcastFromSyncIterableStrings(), | |
| 113 | 140 | testRingbufferGrow(), | |
| 141 | + testFanOutMinCursorTrimming(), | ||
| 114 | 142 | testDrainableAfterClose(), | |
| 115 | 143 | ]).then(common.mustCall()); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments