Skip to content

Commit 22dad97

Browse files
Brian M Huntclaude
authored andcommitted
Handle NaN/Infinity in flushBatch; add NaN regression test
Per CodeRabbit's follow-up finding: the `<= 0` guard in flushBatch caught the mid-flight 0 case, but not NaN. `NaN <= 0` is false, so a NaN batch size would fall through to `splice(0, NaN)`, which coerces to `splice(0, 0)` — reinstating the exact infinite reschedule loop we were trying to prevent. Switch to `Math.trunc` + `Number.isFinite` so any non-positive or non-finite value routes through `flushAll`. Added a regression test covering the NaN case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 200f8ae commit 22dad97

2 files changed

Lines changed: 17 additions & 8 deletions

File tree

packages/utils.jsx/spec/jsxCleanBehaviors.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,24 @@ describe('jsxClean queue', function () {
102102
it('drains synchronously if batchSize is flipped to 0 while a timer is pending', function () {
103103
const nodes = [makeNode(), makeNode(), makeNode()]
104104
for (const n of nodes) queueCleanNode(n)
105-
assert.lengthOf(cleaned, 0)
106105
assert.equal(clock.countTimers(), 1)
107106

108-
// Consumer flips to sync mid-flight. When the timer fires it must
109-
// fall through to a full drain — not splice(0, 0) forever.
110107
options.jsxCleanBatchSize = 0
111108
clock.tick(25)
112109

113110
assert.deepEqual(cleaned, nodes)
114111
assert.equal(clock.countTimers(), 0, 'no re-armed timer')
115112
})
113+
114+
it('drains synchronously if batchSize becomes non-finite while a timer is pending', function () {
115+
const nodes = [makeNode(), makeNode()]
116+
for (const n of nodes) queueCleanNode(n)
117+
118+
options.jsxCleanBatchSize = Number.NaN as unknown as number
119+
clock.tick(25)
120+
121+
assert.deepEqual(cleaned, nodes)
122+
assert.equal(clock.countTimers(), 0)
123+
})
116124
})
117125
})

packages/utils.jsx/src/jsxClean.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,15 @@ function scheduleBatch() {
3333

3434
function flushBatch() {
3535
cleanNodeTimeoutID = null
36-
// If the option was flipped to 0 (or below) while the timer was pending,
37-
// fall through to synchronous drain. Otherwise splice(0, 0) would remove
38-
// nothing and scheduleBatch would re-arm the timer every 25ms forever.
39-
if (options.jsxCleanBatchSize <= 0) {
36+
// If the option was flipped to a non-positive / non-finite value while the
37+
// timer was pending, fall through to synchronous drain — otherwise
38+
// splice(0, <=0|NaN) removes nothing and scheduleBatch re-arms forever.
39+
const batchSize = Math.trunc(options.jsxCleanBatchSize)
40+
if (!Number.isFinite(batchSize) || batchSize <= 0) {
4041
flushAll()
4142
return
4243
}
43-
const nodes = cleanNodeQueue.splice(0, options.jsxCleanBatchSize)
44+
const nodes = cleanNodeQueue.splice(0, batchSize)
4445
for (const node of nodes) {
4546
cleanNode(node)
4647
}

0 commit comments

Comments
 (0)