| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a46ddad commit 97ef13c
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,6 +9,7 @@ | |||
| 9 | 9 | const { | |
| 10 | 10 | ArrayIsArray, | |
| 11 | 11 | ArrayPrototypePush, | |
| 12 | + ArrayPrototypeShift, | ||
| 12 | 13 | MathMax, | |
| 13 | 14 | PromisePrototypeThen, | |
| 14 | 15 | PromiseReject, | |
@@ -146,6 +147,7 @@ class BroadcastImpl { | |||
| 146 | 147 | cursor: this.#bufferStart, | |
| 147 | 148 | resolve: null, | |
| 148 | 149 | reject: null, | |
| 150 | + pending: [], | ||
| 149 | 151 | detached: false, | |
| 150 | 152 | }; | |
| 151 | 153 | ||
@@ -165,9 +167,10 @@ class BroadcastImpl { | |||
| 165 | 167 | ||
| 166 | 168 | function detach() { | |
| 167 | 169 | state.detached = true; | |
| 168 | - state.resolve?.({ __proto__: null, done: true, value: undefined }); | ||
| 169 | - state.resolve = null; | ||
| 170 | - state.reject = null; | ||
| 170 | + if (state.resolve) { | ||
| 171 | + state.resolve({ __proto__: null, done: true, value: undefined }); | ||
| 172 | + } | ||
| 173 | + self.#resolvePendingDone(state); | ||
| 171 | 174 | if (self.#deleteConsumer(state)) { | |
| 172 | 175 | self.#tryTrimBuffer(); | |
| 173 | 176 | } | |
@@ -208,6 +211,13 @@ class BroadcastImpl { | |||
| 208 | 211 | return kDone; | |
| 209 | 212 | } | |
| 210 | 213 | ||
| 214 | + if (state.resolve) { | ||
| 215 | + const { promise, resolve, reject } = PromiseWithResolvers(); | ||
| 216 | + ArrayPrototypePush(state.pending, | ||
| 217 | + { __proto__: null, resolve, reject }); | ||
| 218 | + return promise; | ||
| 219 | + } | ||
| 220 | + | ||
| 211 | 221 | const { promise, resolve, reject } = PromiseWithResolvers(); | |
| 212 | 222 | state.resolve = resolve; | |
| 213 | 223 | state.reject = reject; | |
@@ -251,6 +261,11 @@ class BroadcastImpl { | |||
| 251 | 261 | consumer.resolve = null; | |
| 252 | 262 | consumer.reject = null; | |
| 253 | 263 | } | |
| 264 | + if (reason !== undefined) { | ||
| 265 | + this.#rejectPending(consumer, reason); | ||
| 266 | + } else { | ||
| 267 | + this.#resolvePendingDone(consumer); | ||
| 268 | + } | ||
| 254 | 269 | consumer.detached = true; | |
| 255 | 270 | } | |
| 256 | 271 | this.#consumers.clear(); | |
@@ -297,7 +312,7 @@ class BroadcastImpl { | |||
| 297 | 312 | this.#ended = true; | |
| 298 | 313 | ||
| 299 | 314 | for (const consumer of this.#consumers) { | |
| 300 | - if (consumer.resolve) { | ||
| 315 | + while (consumer.resolve) { | ||
| 301 | 316 | const bufferIndex = consumer.cursor - this.#bufferStart; | |
| 302 | 317 | if (bufferIndex < this.#buffer.length) { | |
| 303 | 318 | const chunk = this.#buffer.get(bufferIndex); | |
@@ -310,9 +325,15 @@ class BroadcastImpl { | |||
| 310 | 325 | consumer.resolve({ __proto__: null, done: false, value: chunk }); | |
| 311 | 326 | } else { | |
| 312 | 327 | consumer.resolve({ __proto__: null, done: true, value: undefined }); | |
| 328 | + this.#resolvePendingDone(consumer); | ||
| 329 | + consumer.detached = true; | ||
| 313 | 330 | } | |
| 314 | 331 | consumer.resolve = null; | |
| 315 | 332 | consumer.reject = null; | |
| 333 | + if (consumer.detached && this.#deleteConsumer(consumer)) { | ||
| 334 | + this.#tryTrimBuffer(); | ||
| 335 | + break; | ||
| 336 | + } | ||
| 316 | 337 | } | |
| 317 | 338 | } | |
| 318 | 339 | } | |
@@ -329,6 +350,7 @@ class BroadcastImpl { | |||
| 329 | 350 | consumer.resolve = null; | |
| 330 | 351 | consumer.reject = null; | |
| 331 | 352 | } | |
| 353 | + this.#rejectPending(consumer, reason); | ||
| 332 | 354 | consumer.detached = true; | |
| 333 | 355 | } | |
| 334 | 356 | this.#consumers.clear(); | |
@@ -397,6 +419,11 @@ class BroadcastImpl { | |||
| 397 | 419 | consumer.resolve = null; | |
| 398 | 420 | consumer.reject = null; | |
| 399 | 421 | resolve({ __proto__: null, done: false, value: chunk }); | |
| 422 | + if (consumer.detached && this.#deleteConsumer(consumer)) { | ||
| 423 | + this.#tryTrimBuffer(); | ||
| 424 | + } else if (this.#promotePending(consumer)) { | ||
| 425 | + ArrayPrototypePush(this.#waiters, consumer); | ||
| 426 | + } | ||
| 400 | 427 | } else { | |
| 401 | 428 | // Still waiting -- put back | |
| 402 | 429 | ArrayPrototypePush(this.#waiters, consumer); | |
@@ -419,6 +446,31 @@ class BroadcastImpl { | |||
| 419 | 446 | } | |
| 420 | 447 | return false; | |
| 421 | 448 | } | |
| 449 | + | ||
| 450 | + #promotePending(consumer) { | ||
| 451 | + const next = ArrayPrototypeShift(consumer.pending); | ||
| 452 | + if (next === undefined) return false; | ||
| 453 | + consumer.resolve = next.resolve; | ||
| 454 | + consumer.reject = next.reject; | ||
| 455 | + return true; | ||
| 456 | + } | ||
| 457 | + | ||
| 458 | + #resolvePendingDone(consumer) { | ||
| 459 | + if (consumer.resolve) { | ||
| 460 | + consumer.resolve = null; | ||
| 461 | + consumer.reject = null; | ||
| 462 | + } | ||
| 463 | + while (consumer.pending.length > 0) { | ||
| 464 | + ArrayPrototypeShift(consumer.pending).resolve( | ||
| 465 | + { __proto__: null, done: true, value: undefined }); | ||
| 466 | + } | ||
| 467 | + } | ||
| 468 | + | ||
| 469 | + #rejectPending(consumer, reason) { | ||
| 470 | + while (consumer.pending.length > 0) { | ||
| 471 | + ArrayPrototypeShift(consumer.pending).reject(reason); | ||
| 472 | + } | ||
| 473 | + } | ||
| 422 | 474 | } | |
| 423 | 475 | ||
| 424 | 476 | // ============================================================================= | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | 4 | const common = require('../common'); | |
| 5 | 5 | const assert = require('assert'); | |
| 6 | + const { setTimeout } = require('timers/promises'); | ||
| 6 | 7 | const { broadcast, text } = require('stream/iter'); | |
| 7 | 8 | ||
| 8 | 9 | // ============================================================================= | |
@@ -255,6 +256,38 @@ async function testLateJoinerSeesBufferedData() { | |||
| 255 | 256 | assert.strictEqual(result, 'before-join'); | |
| 256 | 257 | } | |
| 257 | 258 | ||
| 259 | + async function testOverlappingNextKeepsEarlierRead() { | ||
| 260 | + const { writer, broadcast: bc } = broadcast(); | ||
| 261 | + const it = bc.push()[Symbol.asyncIterator](); | ||
| 262 | + | ||
| 263 | + const first = it.next(); | ||
| 264 | + const second = it.next(); | ||
| 265 | + | ||
| 266 | + await writer.write('x'); | ||
| 267 | + | ||
| 268 | + const secondResult = await Promise.race([ | ||
| 269 | + second.then((value) => ({ __proto__: null, settled: true, value })), | ||
| 270 | + setTimeout(common.platformTimeout(50), | ||
| 271 | + { __proto__: null, settled: false }), | ||
| 272 | + ]); | ||
| 273 | + assert.deepStrictEqual(secondResult, { | ||
| 274 | + __proto__: null, | ||
| 275 | + settled: false, | ||
| 276 | + }); | ||
| 277 | + | ||
| 278 | + const result = await first; | ||
| 279 | + assert.strictEqual(result.done, false); | ||
| 280 | + assert.strictEqual(Buffer.concat(result.value).toString(), 'x'); | ||
| 281 | + | ||
| 282 | + writer.endSync(); | ||
| 283 | + assert.deepStrictEqual(await second, { | ||
| 284 | + __proto__: null, | ||
| 285 | + done: true, | ||
| 286 | + value: undefined, | ||
| 287 | + }); | ||
| 288 | + assert.strictEqual(bc.consumerCount, 0); | ||
| 289 | + } | ||
| 290 | + | ||
| 258 | 291 | Promise.all([ | |
| 259 | 292 | testBasicBroadcast(), | |
| 260 | 293 | testMultipleWrites(), | |
@@ -270,4 +303,5 @@ Promise.all([ | |||
| 270 | 303 | testFailDetachesConsumers(), | |
| 271 | 304 | testWriterFailIdempotent(), | |
| 272 | 305 | testLateJoinerSeesBufferedData(), | |
| 306 | + testOverlappingNextKeepsEarlierRead(), | ||
| 273 | 307 | ]).then(common.mustCall()); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments