Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 28 additions & 49 deletions packages/cloudflare/src/cacheability-probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -726,58 +726,37 @@ export async function probeStagedWorkerCacheability(options: {
reportProgress();
};

reportProgress();
let activeProbes = 0;
const slotWaiters: Array<() => void> = [];
const acquireProbeSlot = async (): Promise<void> => {
if (activeProbes < concurrency) {
activeProbes++;
return;
}
await new Promise<void>((resolve) => slotWaiters.push(resolve));
};
const releaseProbeSlot = (): void => {
const next = slotWaiters.shift();
if (next) next();
else activeProbes--;
};
let pendingRouteMovers = groups.filter(
(group) => group.primary.route?.cacheabilityProbe?.routeMayResolve === true,
).length;
let settleRouteMovers: (() => void) | undefined;
const routeMoversSettled =
pendingRouteMovers === 0
? Promise.resolve()
: new Promise<void>((resolve) => {
settleRouteMovers = resolve;
});
const runGroup = async (group: ConcretePathGroup): Promise<void> => {
const mayResolveRoute = group.primary.route?.cacheabilityProbe?.routeMayResolve === true;
while (true) {
if (!mayResolveRoute && group.pattern.pruned && pendingRouteMovers > 0) {
await routeMoversSettled;
}
await acquireProbeSlot();
if (!mayResolveRoute && group.pattern.pruned && pendingRouteMovers > 0) {
releaseProbeSlot();
continue;
const runGroups = async (scheduledGroups: readonly ConcretePathGroup[]): Promise<void> => {
let nextIndex = 0;
const worker = async (): Promise<void> => {
while (!limitFailure && !phaseTimedOut && nextIndex < scheduledGroups.length) {
await classifyConcretePath(scheduledGroups[nextIndex++]);
}
break;
}
try {
if (!limitFailure && !phaseTimedOut) await classifyConcretePath(group);
} finally {
releaseProbeSlot();
if (mayResolveRoute && --pendingRouteMovers === 0) settleRouteMovers?.();
}
};
await Promise.all(
Array.from({ length: Math.min(concurrency, scheduledGroups.length) }, () => worker()),
);
};
const initialPatternGroups = Array.from(patterns.values(), (pattern) => [...pattern.groups]);
await Promise.all(
initialPatternGroups.map(async ([representative, ...siblings]) => {
await runGroup(representative);
await Promise.all(siblings.map(runGroup));
}),

reportProgress();
const routeMovingGroups = groups.filter(
(group) => group.primary.route?.cacheabilityProbe?.routeMayResolve === true,
);
const routeMovingGroupSet = new Set(routeMovingGroups);
const representativeGroups: ConcretePathGroup[] = [];
const siblingGroups: ConcretePathGroup[] = [];
for (const pattern of patterns.values()) {
const [representative, ...siblings] = pattern.groups;
if (representative && !routeMovingGroupSet.has(representative)) {
representativeGroups.push(representative);
}
siblingGroups.push(...siblings.filter((group) => !routeMovingGroupSet.has(group)));
}
// Resolve every route-moving public path before a destination pattern may
// be pruned, then use the same bounded worker loops as ordinary CDN warming.
await runGroups(routeMovingGroups);
if (!limitFailure && !phaseTimedOut) await runGroups(representativeGroups);
if (!limitFailure && !phaseTimedOut) await runGroups(siblingGroups);
if (limitFailure) throw limitFailure;
if (phaseTimedOut || Date.now() >= getDeadlineAt()) {
throw new Error(`cacheability probing made no progress for ${phaseTimeoutMs}ms`);
Expand Down
8 changes: 4 additions & 4 deletions tests/cloudflare-cacheability-probe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,14 +478,14 @@ describe("staged Worker cacheability probes", () => {
);
});

it("unlocks sibling paths without waiting for every pattern representative", async () => {
it("completes pattern representatives before scheduling sibling paths", async () => {
const root = createProbeRoot();
const slow = target("/slow");
const fastRoute = optimizableRoute("/fast/:slug");
const fastFirst = { ...target("/fast/one"), route: fastRoute };
const fastSecond = { ...target("/fast/two"), route: fastRoute };
let slowCompleted = false;
let siblingStartedBeforeSlowCompleted = false;
let siblingStartedAfterSlowCompleted = false;
const result = await probeStagedWorkerCacheability({
buildId: "application-build",
concurrency: 2,
Expand All @@ -495,7 +495,7 @@ describe("staged Worker cacheability probes", () => {
await new Promise((resolve) => setTimeout(resolve, 20));
slowCompleted = true;
} else if (pathname === "/fast/two") {
siblingStartedBeforeSlowCompleted = !slowCompleted;
siblingStartedAfterSlowCompleted = slowCompleted;
}
return Response.json({
kind: "app-page",
Expand All @@ -512,7 +512,7 @@ describe("staged Worker cacheability probes", () => {
});

expect(result).toMatchObject({ failures: [], probed: 3 });
expect(siblingStartedBeforeSlowCompleted).toBe(true);
expect(siblingStartedAfterSlowCompleted).toBe(true);
});

it("does not prune destination siblings before route-moving probes settle", async () => {
Expand Down
Loading