Skip to content

Commit e45f334

Browse files
KyleAMathewsclaude
andcommitted
Address PR review feedback
- Simplify iteration tracker to lazy evaluation (only compute diagnostics when limit is actually exceeded, not on every iteration) - Revert unnecessary array conversions in collection-subscriber.ts - Revert unused boolean return from loadNextItems - Keep cursorMinValue rename (fixes ESLint shadowing warning) - Confirmed insertedKeys was dead code (never used after being set) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 44d89fc commit e45f334

File tree

6 files changed

+141
-338
lines changed

6 files changed

+141
-338
lines changed

packages/db-ivm/src/d2.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DifferenceStreamWriter } from './graph.js'
2-
import { createIterationTracker } from './iteration-tracker.js'
2+
import { createIterationLimitChecker } from './iteration-tracker.js'
33
import type {
44
BinaryOperator,
55
DifferenceStreamReader,
@@ -61,24 +61,24 @@ export class D2 implements ID2 {
6161
// Safety limit to prevent infinite loops in case of circular data flow
6262
// or other bugs that cause operators to perpetually produce output.
6363
const MAX_RUN_ITERATIONS = 100000
64-
const tracker = createIterationTracker<string>(
65-
MAX_RUN_ITERATIONS,
66-
(state) => `operators with work = [${state}]`,
67-
)
64+
const checkLimit = createIterationLimitChecker(MAX_RUN_ITERATIONS)
6865

6966
while (this.pendingWork()) {
70-
const operatorsWithWork = this.#operators
71-
.filter((op) => op.hasPendingWork())
72-
.map((op) => op.constructor.name)
73-
.sort()
74-
.join(`,`)
75-
76-
if (tracker.trackAndCheckLimit(operatorsWithWork)) {
77-
console.warn(
78-
tracker.formatWarning(`D2 graph execution`, {
79-
totalOperators: this.#operators.length,
80-
}),
81-
)
67+
if (
68+
checkLimit(() => {
69+
// Only compute diagnostics when limit is exceeded (lazy)
70+
const operatorsWithWork = this.#operators
71+
.filter((op) => op.hasPendingWork())
72+
.map((op) => ({ id: op.id, type: op.constructor.name }))
73+
return {
74+
context: `D2 graph execution`,
75+
diagnostics: {
76+
operatorsWithPendingWork: operatorsWithWork,
77+
totalOperators: this.#operators.length,
78+
},
79+
}
80+
})
81+
) {
8282
break
8383
}
8484

Lines changed: 29 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,61 @@
11
/**
2-
* Tracks state transitions during iteration loops for diagnostic purposes.
3-
* Used by circuit breakers to report where iterations were spent when limits are exceeded.
2+
* Creates a simple iteration counter with a limit check.
3+
* When the limit is exceeded, calls the provided diagnostic function to capture state.
44
*
5-
* The tracker collects a history of state transitions, where each entry records
6-
* a period of time (iteration range) spent in a particular state. When the iteration
7-
* limit is exceeded, this history helps diagnose infinite loop causes.
5+
* This design avoids per-iteration overhead - state capture only happens when needed.
86
*
97
* @example
108
* ```ts
11-
* const tracker = createIterationTracker<{ operators: string }>(100000)
9+
* const checkLimit = createIterationLimitChecker(100000)
1210
*
1311
* while (pendingWork()) {
14-
* const state = { operators: getOperatorsWithWork().join(',') }
15-
* if (tracker.trackAndCheckLimit(state)) {
16-
* console.warn(tracker.formatWarning('D2 graph execution', {
17-
* totalOperators: operators.length,
18-
* }))
12+
* if (checkLimit(() => ({
13+
* context: 'D2 graph execution',
14+
* diagnostics: { totalOperators: operators.length }
15+
* }))) {
1916
* break
2017
* }
2118
* step()
2219
* }
2320
* ```
2421
*/
2522

26-
export type StateHistoryEntry<TState> = {
27-
state: TState
28-
startIter: number
29-
endIter: number
30-
}
31-
32-
export type IterationTracker<TState> = {
33-
/**
34-
* Records the current state and increments the iteration counter.
35-
* Returns true if the iteration limit has been exceeded.
36-
*/
37-
trackAndCheckLimit: (state: TState) => boolean
38-
39-
/**
40-
* Formats a warning message with iteration breakdown and diagnostic info.
41-
* Call this after trackAndCheckLimit returns true.
42-
*/
43-
formatWarning: (
44-
context: string,
45-
diagnosticInfo?: Record<string, unknown>,
46-
) => string
47-
48-
/**
49-
* Returns the current iteration count.
50-
*/
51-
getIterations: () => number
52-
53-
/**
54-
* Returns the state history for inspection.
55-
*/
56-
getHistory: () => Array<StateHistoryEntry<TState>>
23+
export type LimitExceededInfo = {
24+
context: string
25+
diagnostics?: Record<string, unknown>
5726
}
5827

5928
/**
60-
* Creates an iteration tracker that monitors loop iterations and records state transitions.
29+
* Creates an iteration limit checker that logs a warning when the limit is exceeded.
6130
*
6231
* @param maxIterations - The maximum number of iterations before the limit is exceeded
63-
* @param stateToKey - Optional function to convert state to a string key for comparison.
64-
* Defaults to JSON.stringify.
32+
* @returns A function that increments the counter and returns true if limit exceeded
6533
*/
66-
export function createIterationTracker<TState>(
34+
export function createIterationLimitChecker(
6735
maxIterations: number,
68-
stateToKey: (state: TState) => string = (state) => JSON.stringify(state),
69-
): IterationTracker<TState> {
70-
const history: Array<StateHistoryEntry<TState>> = []
71-
let currentStateKey: string | null = null
72-
let currentState: TState | null = null
73-
let stateStartIter = 1
36+
): (getInfo: () => LimitExceededInfo) => boolean {
7437
let iterations = 0
7538

76-
function recordCurrentState(): void {
77-
if (currentStateKey !== null && currentState !== null) {
78-
history.push({
79-
state: currentState,
80-
startIter: stateStartIter,
81-
endIter: iterations,
82-
})
83-
}
84-
}
85-
86-
function trackAndCheckLimit(state: TState): boolean {
87-
const stateKey = stateToKey(state)
88-
89-
if (stateKey !== currentStateKey) {
90-
recordCurrentState()
91-
currentStateKey = stateKey
92-
currentState = state
93-
stateStartIter = iterations + 1
94-
}
95-
39+
return function checkLimit(getInfo: () => LimitExceededInfo): boolean {
9640
iterations++
9741

9842
if (iterations > maxIterations) {
99-
recordCurrentState()
43+
// Only capture diagnostic info when we actually exceed the limit
44+
const { context, diagnostics } = getInfo()
45+
46+
const diagnosticSection = diagnostics
47+
? `\nDiagnostic info: ${JSON.stringify(diagnostics, null, 2)}\n`
48+
: `\n`
49+
50+
console.warn(
51+
`[TanStack DB] ${context} exceeded ${maxIterations} iterations. ` +
52+
`Continuing with available data.` +
53+
diagnosticSection +
54+
`Please report this issue at https://github.com/TanStack/db/issues`,
55+
)
10056
return true
10157
}
10258

10359
return false
10460
}
105-
106-
function formatWarning(
107-
context: string,
108-
diagnosticInfo?: Record<string, unknown>,
109-
): string {
110-
const iterationBreakdown = history
111-
.map((h) => ` ${h.startIter}-${h.endIter}: ${stateToKey(h.state)}`)
112-
.join(`\n`)
113-
114-
const diagnosticSection = diagnosticInfo
115-
? `\nDiagnostic info: ${JSON.stringify(diagnosticInfo, null, 2)}\n`
116-
: `\n`
117-
118-
return (
119-
`[TanStack DB] ${context} exceeded ${maxIterations} iterations. ` +
120-
`Continuing with available data.\n` +
121-
`Iteration breakdown (where the loop spent time):\n${iterationBreakdown}` +
122-
diagnosticSection +
123-
`Please report this issue at https://github.com/TanStack/db/issues`
124-
)
125-
}
126-
127-
function getIterations(): number {
128-
return iterations
129-
}
130-
131-
function getHistory(): Array<StateHistoryEntry<TState>> {
132-
return [...history]
133-
}
134-
135-
return {
136-
trackAndCheckLimit,
137-
formatWarning,
138-
getIterations,
139-
getHistory,
140-
}
14161
}

0 commit comments

Comments
 (0)