Skip to content

Commit 4b12706

Browse files
authored
perf(cf): race OOPIF candidates concurrently with Effect.raceAll (#127)
Replace sequential OOPIF scanning with Effect.raceAll — first match wins, losers interrupted. Adds parentFrameId pre-filter to skip cross-tab OOPIFs and 3s per-probe timeout (was 30s CDP default). Fixes compound label assertion in integration test.
1 parent e0febfe commit 4b12706

3 files changed

Lines changed: 67 additions & 44 deletions

File tree

src/session/cf/cf-phase-oopif.ts

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import { Effect } from 'effect';
1313
import type { CdpSessionId, TargetId } from '../../shared/cloudflare-detection.js';
1414
import { SolverEvents } from './cf-services.js';
15-
import { MAX_OOPIF_POLLS, OOPIF_POLL_DELAY } from './cf-schedules.js';
15+
import { MAX_OOPIF_POLLS, OOPIF_POLL_DELAY, OOPIF_PROBE_TIMEOUT } from './cf-schedules.js';
1616

1717
/** Effect-returning CDP sender — eliminates the Promise bridge. */
1818
type EffectSend = (
@@ -138,36 +138,49 @@ export function phase2OOPIFResolution(
138138
// ── Matching strategies ─────────────────────────────────────────
139139

140140
/**
141-
* Try frameId match: Phase 1 gave us the iframe's frameId — attach
142-
* each candidate and compare frame tree root ID against iframeFrameId.
141+
* Try frameId match: Phase 1 gave us the iframe's frameId — race all
142+
* candidates concurrently. First match wins, losers interrupted.
143+
*
144+
* Pre-filters by parentFrameId to skip OOPIFs from sibling tabs.
145+
* Per-probe timeout prevents stale targets from blocking (30s CDP default).
143146
*/
144147
const tryFrameIdMatch = (
145148
candidates: readonly OOPIFCandidate[],
146149
poll: number,
147150
): Effect.Effect<OOPIFMatch | null> => {
148151
if (!iframeFrameId) return Effect.succeed(null);
152+
// Pre-filter: only scan OOPIFs parented to OUR page
153+
const ours = pageFrameId
154+
? candidates.filter((t) => t.parentFrameId === pageFrameId)
155+
: candidates;
156+
if (ours.length === 0) return Effect.succeed(null);
149157
return Effect.fn('cf.phase2.frameIdScan')(function*() {
150-
for (const target of candidates) {
151-
const sid = yield* attachToTarget(target, 'frameId', poll);
152-
if (!sid) continue;
153-
const fid = yield* getFrameTreeId(sid);
154-
if (fid && (fid === iframeFrameId || target.targetId === iframeFrameId)) {
155-
yield* events.marker(pageTargetId, 'cf.oopif_discovered', {
156-
method: 'active', via, filter: 'frameId_match',
157-
targetId: target.targetId, url: target.url?.substring(0, 100),
158-
total_candidates: candidates.length, poll,
159-
});
160-
return { sessionId: sid, target, method: 'frameId_match' as const };
161-
}
162-
}
163-
return null;
158+
// Race all candidates — first frameId match wins, losers interrupted
159+
return yield* Effect.raceAll(
160+
ours.map((target) =>
161+
Effect.fn('cf.phase2.probe')(function*() {
162+
const sid = yield* attachToTarget(target, 'frameId', poll);
163+
if (!sid) return yield* Effect.fail('no-session' as const);
164+
const fid = yield* getFrameTreeId(sid);
165+
if (!fid || (fid !== iframeFrameId && target.targetId !== iframeFrameId)) {
166+
return yield* Effect.fail('no-match' as const);
167+
}
168+
yield* events.marker(pageTargetId, 'cf.oopif_discovered', {
169+
method: 'active', via, filter: 'frameId_match',
170+
targetId: target.targetId, url: target.url?.substring(0, 100),
171+
total_candidates: ours.length, poll,
172+
});
173+
return { sessionId: sid, target, method: 'frameId_match' as const } satisfies OOPIFMatch;
174+
})().pipe(Effect.timeout(OOPIF_PROBE_TIMEOUT)),
175+
),
176+
).pipe(Effect.orElseSucceed(() => null as OOPIFMatch | null));
164177
})();
165178
};
166179

167180
/**
168181
* Try parentFrameId match: filter candidates whose parentFrameId matches
169-
* the page's root frameId, then cross-validate against iframeFrameId
170-
* when available to reject stale OOPIFs.
182+
* the page's root frameId, race concurrently, cross-validate against
183+
* iframeFrameId when available.
171184
*/
172185
const tryParentMatch = (
173186
candidates: readonly OOPIFCandidate[],
@@ -177,30 +190,33 @@ export function phase2OOPIFResolution(
177190
const filtered = candidates.filter((t) => t.parentFrameId === pageFrameId);
178191
if (filtered.length === 0) return Effect.succeed(null);
179192
return Effect.fn('cf.phase2.parentScan')(function*() {
180-
for (const target of filtered) {
181-
const sid = yield* attachToTarget(target, 'parentFrameId', poll);
182-
if (!sid) continue;
183-
// Cross-validate against Phase 1 frameId when available
184-
if (iframeFrameId) {
185-
const fid = yield* getFrameTreeId(sid);
186-
if (fid && fid !== iframeFrameId && target.targetId !== iframeFrameId) {
187-
yield* events.marker(pageTargetId, 'cf.oopif_stale', {
188-
via, targetId: target.targetId,
189-
expected_frame_id: (iframeFrameId as string).substring(0, 20),
190-
actual_frame_id: fid.substring(0, 20),
191-
poll,
193+
return yield* Effect.raceAll(
194+
filtered.map((target) =>
195+
Effect.fn('cf.phase2.probe')(function*() {
196+
const sid = yield* attachToTarget(target, 'parentFrameId', poll);
197+
if (!sid) return yield* Effect.fail('no-session' as const);
198+
// Cross-validate against Phase 1 frameId when available
199+
if (iframeFrameId) {
200+
const fid = yield* getFrameTreeId(sid);
201+
if (fid && fid !== iframeFrameId && target.targetId !== iframeFrameId) {
202+
yield* events.marker(pageTargetId, 'cf.oopif_stale', {
203+
via, targetId: target.targetId,
204+
expected_frame_id: (iframeFrameId as string).substring(0, 20),
205+
actual_frame_id: fid.substring(0, 20),
206+
poll,
207+
});
208+
return yield* Effect.fail('stale' as const);
209+
}
210+
}
211+
yield* events.marker(pageTargetId, 'cf.oopif_discovered', {
212+
method: 'active', via, filter: 'parentFrameId',
213+
targetId: target.targetId, url: target.url?.substring(0, 100),
214+
total_candidates: filtered.length, poll,
192215
});
193-
continue;
194-
}
195-
}
196-
yield* events.marker(pageTargetId, 'cf.oopif_discovered', {
197-
method: 'active', via, filter: 'parentFrameId',
198-
targetId: target.targetId, url: target.url?.substring(0, 100),
199-
total_candidates: filtered.length, poll,
200-
});
201-
return { sessionId: sid, target, method: 'parentFrameId' as const };
202-
}
203-
return null;
216+
return { sessionId: sid, target, method: 'parentFrameId' as const } satisfies OOPIFMatch;
217+
})().pipe(Effect.timeout(OOPIF_PROBE_TIMEOUT)),
218+
),
219+
).pipe(Effect.orElseSucceed(() => null as OOPIFMatch | null));
204220
})();
205221
};
206222

src/session/cf/cf-schedules.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ export const MAX_OOPIF_POLLS = 6;
7474
* plenty of margin while discovering the OOPIF 2.5× faster. */
7575
export const OOPIF_POLL_DELAY = '200 millis' as const;
7676

77+
/** OOPIF probe: per-candidate timeout for attach+getFrameTree.
78+
* Normal attach+getFrameTree is <35ms. 3s gives generous headroom while
79+
* preventing stale/closing OOPIFs from blocking for the 30s CDP default. */
80+
export const OOPIF_PROBE_TIMEOUT = '3 seconds' as const;
81+
7782
/** Phase 3 checkbox polling: max attempts. */
7883
export const MAX_CHECKBOX_POLLS = 16;
7984

src/session/cf/cf-sites.integration.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -543,8 +543,10 @@ describe.concurrent('CF Solver Multi-Site', () => {
543543
const labelHasClick = summary!.label.includes('✓');
544544
const labelEndsAuto = summary!.label.endsWith('→');
545545

546-
// Click delivered → label MUST be ✓ (the original phantom bug: click_solve overwritten to →)
547-
if (hasClickMarker && labelEndsAuto) {
546+
// Click delivered → label MUST contain ✓ (the original phantom bug: click_solve overwritten to →)
547+
// For compound labels (Int✓ Emb→), the ✓ is from the click phase and → from a separate
548+
// auto-solve phase — both are legitimate. Only fail when NO ✓ exists despite a click.
549+
if (hasClickMarker && labelEndsAuto && !labelHasClick) {
548550
throw fail(
549551
`cf.oopif_click ok=true but label '${summary!.label}' shows auto (→) — ` +
550552
`phantom auto_solve overwrote click_solve`,

0 commit comments

Comments
 (0)