Skip to content

Commit 5df309f

Browse files
authored
Merge branch 'main' into main
2 parents 88dd931 + 8e6ede5 commit 5df309f

File tree

13 files changed

+152
-40
lines changed

13 files changed

+152
-40
lines changed

.changeset/few-bobcats-brake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
When reporting errors to Sentry, Wrangler will now include the console output as additional metadata
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudflare/workers-shared": patch
3+
---
4+
5+
fix: interpolation search method now checks for a single match at the beginning of every iteration

.changeset/kind-spiders-hunt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudflare/workers-shared": patch
3+
---
4+
5+
chore: Bump interpolation search method for asset manifest reading to 1%

.changeset/stale-zoos-drop.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@cloudflare/vite-plugin": minor
3+
---
4+
5+
No longer call `next` in server middleware.
6+
7+
This is so that the Cloudflare plugin can override subsequent dev middleware for framework integrations.

packages/vite-plugin-cloudflare/playground/hot-channel/vite.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { defineConfig } from "vite";
44

55
export default defineConfig({
66
plugins: [
7-
cloudflare({ persistState: false }),
87
{
98
name: "test-plugin",
109
configureServer(viteDevServer) {
@@ -22,5 +21,6 @@ export default defineConfig({
2221
};
2322
},
2423
},
24+
cloudflare({ persistState: false }),
2525
],
2626
});

packages/vite-plugin-cloudflare/src/cloudflare-environment.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ export function createCloudflareEnvironmentOptions(
161161
optimizeDeps: {
162162
// Note: ssr pre-bundling is opt-in and we need to enable it by setting `noDiscovery` to false
163163
noDiscovery: false,
164+
entries: workerConfig.main,
164165
exclude: [
165166
...cloudflareBuiltInModules,
166167
// we have to exclude all node modules to work in dev-mode not just the unenv externals...

packages/vite-plugin-cloudflare/src/index.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -267,11 +267,14 @@ export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin[] {
267267
miniflare
268268
);
269269

270-
const middleware = createMiddleware(({ request }) => {
271-
return entryWorker.fetch(toMiniflareRequest(request), {
272-
redirect: "manual",
273-
}) as any;
274-
});
270+
const middleware = createMiddleware(
271+
({ request }) => {
272+
return entryWorker.fetch(toMiniflareRequest(request), {
273+
redirect: "manual",
274+
}) as any;
275+
},
276+
{ alwaysCallNext: false }
277+
);
275278

276279
handleWebSocket(
277280
viteDevServer.httpServer,
@@ -293,11 +296,14 @@ export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin[] {
293296
)
294297
);
295298

296-
const middleware = createMiddleware(({ request }) => {
297-
return miniflare.dispatchFetch(toMiniflareRequest(request), {
298-
redirect: "manual",
299-
}) as any;
300-
});
299+
const middleware = createMiddleware(
300+
({ request }) => {
301+
return miniflare.dispatchFetch(toMiniflareRequest(request), {
302+
redirect: "manual",
303+
}) as any;
304+
},
305+
{ alwaysCallNext: false }
306+
);
301307

302308
handleWebSocket(
303309
vitePreviewServer.httpServer,

packages/workers-shared/asset-worker/src/assets-manifest.ts

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -88,24 +88,36 @@ export const interpolationSearch = (
8888
if (arr.byteLength === 0) {
8989
return false;
9090
}
91+
// top and tail the search space
9192
let low = 0;
9293
let high = arr.byteLength / ENTRY_SIZE - 1;
93-
if (high === low) {
94-
const current = new Uint8Array(arr.buffer, arr.byteOffset, PATH_HASH_SIZE);
95-
if (current.byteLength !== searchValue.byteLength) {
96-
throw new TypeError(
97-
"Search value and current value are of different lengths"
98-
);
99-
}
100-
const cmp = compare(current, searchValue);
101-
if (cmp === 0) {
102-
return new Uint8Array(arr.buffer, arr.byteOffset, ENTRY_SIZE);
103-
} else {
104-
return false;
105-
}
106-
}
10794
const searchValueNumber = uint8ArrayToNumber(searchValue);
10895
while (low <= high) {
96+
if (low === high) {
97+
// search space has been reduced to a single element
98+
const current = new Uint8Array(
99+
arr.buffer,
100+
arr.byteOffset + low * ENTRY_SIZE,
101+
PATH_HASH_SIZE
102+
);
103+
if (current.byteLength !== searchValue.byteLength) {
104+
throw new TypeError(
105+
"Search value and current value are of different lengths"
106+
);
107+
}
108+
const cmp = compare(current, searchValue);
109+
if (cmp === 0) {
110+
// and it's a match!
111+
return new Uint8Array(
112+
arr.buffer,
113+
arr.byteOffset + low * ENTRY_SIZE,
114+
ENTRY_SIZE
115+
);
116+
} else {
117+
// and it's not a match!
118+
return false;
119+
}
120+
}
109121
const lowValue = new Uint8Array(
110122
arr.buffer,
111123
arr.byteOffset + low * ENTRY_SIZE,
@@ -119,17 +131,19 @@ export const interpolationSearch = (
119131
const lowValueNumber = uint8ArrayToNumber(lowValue);
120132
const highValueNumber = uint8ArrayToNumber(highValue);
121133
const denominator = highValueNumber - lowValueNumber;
122-
if (denominator < 0n) {
123-
return false;
134+
if (denominator <= 0n) {
135+
throw new TypeError("Search space is unordered");
124136
}
125137
const numerator = searchValueNumber - lowValueNumber;
126138
if (numerator < 0n) {
139+
// the lowest value in our search space is smaller than the search value (so it can't be in the search space)
127140
return false;
128141
}
129142
const mid = Math.floor(
130143
Number(BigInt(low) + (BigInt(high - low) * numerator) / denominator)
131144
);
132145
if (mid < low || mid > high) {
146+
// our next guess is either entirely out of range of the search space or we've already searched it
133147
return false;
134148
}
135149
const current = new Uint8Array(
@@ -150,8 +164,10 @@ export const interpolationSearch = (
150164
ENTRY_SIZE
151165
);
152166
} else if (cmp < 0) {
167+
// our estimate was too low so our search space now becomes mid+1 -> high
153168
low = mid + 1;
154169
} else {
170+
// our estimate was too high so our search space now becomes low -> mid-1
155171
high = mid - 1;
156172
}
157173
}

packages/workers-shared/asset-worker/src/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ export default class extends WorkerEntrypoint<Env> {
218218
const analytics = new ExperimentAnalytics(this.env.EXPERIMENT_ANALYTICS);
219219
const performance = new PerformanceTimer(this.env.UNSAFE_PERFORMANCE);
220220

221-
const INTERPOLATION_EXPERIMENT_SAMPLE_RATE = 1 / 20_000; // 0.00005 = 0.005%
221+
const INTERPOLATION_EXPERIMENT_SAMPLE_RATE = 1 / 100; // 0.01 = 1%
222222
let searchMethod: "binary" | "interpolation" = "binary";
223223
if (Math.random() < INTERPOLATION_EXPERIMENT_SAMPLE_RATE) {
224224
searchMethod = "interpolation";
@@ -240,7 +240,12 @@ export default class extends WorkerEntrypoint<Env> {
240240
try {
241241
const assetsManifest = new AssetsManifest(this.env.ASSETS_MANIFEST);
242242
if (searchMethod === "interpolation") {
243-
return await assetsManifest.getWithInterpolationSearch(pathname);
243+
try {
244+
return await assetsManifest.getWithInterpolationSearch(pathname);
245+
} catch (e) {
246+
analytics.setData({ manifestReadMethod: "binary-fallback" });
247+
return await assetsManifest.getWithBinarySearch(pathname);
248+
}
244249
} else {
245250
return await assetsManifest.getWithBinarySearch(pathname);
246251
}

packages/workers-shared/asset-worker/tests/assets-manifest.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,18 @@ describe("search methods", async () => {
236236
expect(foundEntry).toBe(false);
237237
}
238238
});
239+
240+
it("throws an error if the search value is longer than the current value", async () => {
241+
const { manifest } = await makeManifestOfLength(10);
242+
expect(() => {
243+
binarySearch(
244+
new Uint8Array(manifest, HEADER_SIZE),
245+
new Uint8Array(PATH_HASH_SIZE + 1).fill(127)
246+
);
247+
}).toThrowErrorMatchingInlineSnapshot(
248+
`[TypeError: Search value and current value are of different lengths]`
249+
);
250+
});
239251
});
240252

241253
describe("interpolation search", () => {
@@ -339,6 +351,57 @@ describe("search methods", async () => {
339351
}
340352
});
341353

354+
it("throws an error if the search value is longer than the current value", async () => {
355+
const { manifest } = await makeManifestOfLength(10);
356+
expect(() => {
357+
interpolationSearch(
358+
new Uint8Array(manifest, HEADER_SIZE),
359+
new Uint8Array(PATH_HASH_SIZE + 1).fill(127)
360+
);
361+
}).toThrowErrorMatchingInlineSnapshot(
362+
`[TypeError: Search value and current value are of different lengths]`
363+
);
364+
});
365+
366+
it("throws an error if the search space is out of order", async () => {
367+
const smallEntry = {
368+
path: "/small",
369+
pathHashBytes: new Uint8Array(PATH_HASH_SIZE),
370+
contentHash: "00000000000000000000000000000000",
371+
};
372+
const largeEntry = {
373+
path: "/large",
374+
pathHashBytes: new Uint8Array(PATH_HASH_SIZE).fill(255),
375+
contentHash: "ffffffffffffffffffffffffffffffff",
376+
};
377+
const entries = [smallEntry, largeEntry];
378+
entries.sort((a, b) => 0 - compare(a.pathHashBytes, b.pathHashBytes)); // inverted order!
379+
380+
const assetManifestBytes = new Uint8Array(
381+
HEADER_SIZE + entries.length * ENTRY_SIZE
382+
);
383+
384+
for (const [i, { pathHashBytes, contentHash }] of entries.entries()) {
385+
const contentHashBytes = hexToBytes(contentHash);
386+
const entryOffset = HEADER_SIZE + i * ENTRY_SIZE;
387+
388+
assetManifestBytes.set(pathHashBytes, entryOffset + PATH_HASH_OFFSET);
389+
assetManifestBytes.set(
390+
contentHashBytes,
391+
entryOffset + CONTENT_HASH_OFFSET
392+
);
393+
}
394+
395+
expect(() => {
396+
interpolationSearch(
397+
new Uint8Array(assetManifestBytes.buffer, HEADER_SIZE),
398+
smallEntry.pathHashBytes
399+
);
400+
}).toThrowErrorMatchingInlineSnapshot(
401+
`[TypeError: Search space is unordered]`
402+
);
403+
});
404+
342405
it("doesn't throw 'RangeError: Division by zero' with extreme manifests", async () => {
343406
const smallEntry = {
344407
path: "/small",

0 commit comments

Comments
 (0)