Skip to content

Commit 6a389fe

Browse files
authored
fix: deployed worker unable to invoke itself in memory queue (#445)
* fix: deployed worker unable to invoke itself in memory queue * fix unit tests * add more text to changeset
1 parent e05a57e commit 6a389fe

File tree

7 files changed

+61
-7
lines changed

7 files changed

+61
-7
lines changed

.changeset/spicy-seas-appear.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
"@opennextjs/cloudflare": patch
3+
---
4+
5+
fix: deployed worker unable to invoke itself in memory queue
6+
7+
In deployments, Cloudflare Workers are unable to invoke workers on the same account via fetch, and the recommended way to call a worker is to use a service binding. This change switches to use service bindings for the memory queue to avoid issues with worker-to-worker subrequests.
8+
9+
To continue using the memory queue, add a service binding to your wrangler config for the binding `NEXT_CACHE_REVALIDATION_WORKER`.
10+
11+
```json
12+
{
13+
"services": [
14+
{
15+
"binding": "NEXT_CACHE_REVALIDATION_WORKER",
16+
"service": "<WORKER_NAME>"
17+
}
18+
]
19+
}
20+
```

examples/e2e/app-pages-router/wrangler.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,11 @@
1313
"binding": "NEXT_CACHE_WORKERS_KV",
1414
"id": "<BINDING_ID>"
1515
}
16+
],
17+
"services": [
18+
{
19+
"binding": "NEXT_CACHE_REVALIDATION_WORKER",
20+
"service": "app-pages-router"
21+
}
1622
]
1723
}

examples/e2e/app-router/wrangler.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,11 @@
2020
"database_id": "NEXT_CACHE_D1",
2121
"database_name": "NEXT_CACHE_D1"
2222
}
23+
],
24+
"services": [
25+
{
26+
"binding": "NEXT_CACHE_REVALIDATION_WORKER",
27+
"service": "app-router"
28+
}
2329
]
2430
}

examples/e2e/pages-router/wrangler.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,11 @@
1313
"binding": "NEXT_CACHE_WORKERS_KV",
1414
"id": "<BINDING_ID>"
1515
}
16+
],
17+
"services": [
18+
{
19+
"binding": "NEXT_CACHE_REVALIDATION_WORKER",
20+
"service": "pages-router"
21+
}
1622
]
1723
}

packages/cloudflare/src/api/cloudflare-context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ declare global {
66
NEXT_CACHE_D1?: D1Database;
77
NEXT_CACHE_D1_TAGS_TABLE?: string;
88
NEXT_CACHE_D1_REVALIDATIONS_TABLE?: string;
9+
NEXT_CACHE_REVALIDATION_WORKER?: Service;
910
ASSETS?: Fetcher;
1011
}
1112
}

packages/cloudflare/src/api/memory-queue.spec.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import { generateMessageGroupId } from "@opennextjs/aws/core/routing/queue.js";
22
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
33

4-
import cache, { DEFAULT_REVALIDATION_TIMEOUT_MS } from "./memory-queue";
4+
import cache, { DEFAULT_REVALIDATION_TIMEOUT_MS } from "./memory-queue.js";
55

66
vi.mock("./.next/prerender-manifest.json", () => Promise.resolve({ preview: { previewModeId: "id" } }));
77

8+
const mockServiceWorkerFetch = vi.fn();
9+
vi.mock("./cloudflare-context", () => ({
10+
getCloudflareContext: () => ({
11+
env: { NEXT_CACHE_REVALIDATION_WORKER: { fetch: mockServiceWorkerFetch } },
12+
}),
13+
}));
14+
815
describe("MemoryQueue", () => {
916
beforeAll(() => {
1017
vi.useFakeTimers();
@@ -21,7 +28,7 @@ describe("MemoryQueue", () => {
2128
});
2229
vi.advanceTimersByTime(DEFAULT_REVALIDATION_TIMEOUT_MS);
2330
await firstRequest;
24-
expect(globalThis.internalFetch).toHaveBeenCalledTimes(1);
31+
expect(mockServiceWorkerFetch).toHaveBeenCalledTimes(1);
2532

2633
const secondRequest = cache.send({
2734
MessageBody: { host: "test.local", url: "/test" },
@@ -30,7 +37,7 @@ describe("MemoryQueue", () => {
3037
});
3138
vi.advanceTimersByTime(1);
3239
await secondRequest;
33-
expect(globalThis.internalFetch).toHaveBeenCalledTimes(2);
40+
expect(mockServiceWorkerFetch).toHaveBeenCalledTimes(2);
3441
});
3542

3643
it("should process revalidations for multiple paths", async () => {
@@ -41,7 +48,7 @@ describe("MemoryQueue", () => {
4148
});
4249
vi.advanceTimersByTime(1);
4350
await firstRequest;
44-
expect(globalThis.internalFetch).toHaveBeenCalledTimes(1);
51+
expect(mockServiceWorkerFetch).toHaveBeenCalledTimes(1);
4552

4653
const secondRequest = cache.send({
4754
MessageBody: { host: "test.local", url: "/test" },
@@ -50,7 +57,7 @@ describe("MemoryQueue", () => {
5057
});
5158
vi.advanceTimersByTime(1);
5259
await secondRequest;
53-
expect(globalThis.internalFetch).toHaveBeenCalledTimes(2);
60+
expect(mockServiceWorkerFetch).toHaveBeenCalledTimes(2);
5461
});
5562

5663
it("should de-dupe revalidations", async () => {
@@ -68,6 +75,6 @@ describe("MemoryQueue", () => {
6875
];
6976
vi.advanceTimersByTime(1);
7077
await Promise.all(requests);
71-
expect(globalThis.internalFetch).toHaveBeenCalledTimes(1);
78+
expect(mockServiceWorkerFetch).toHaveBeenCalledTimes(1);
7279
});
7380
});

packages/cloudflare/src/api/memory-queue.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import logger from "@opennextjs/aws/logger.js";
22
import type { Queue, QueueMessage } from "@opennextjs/aws/types/overrides.js";
3+
import { IgnorableError } from "@opennextjs/aws/utils/error.js";
4+
5+
import { getCloudflareContext } from "./cloudflare-context";
36

47
export const DEFAULT_REVALIDATION_TIMEOUT_MS = 10_000;
58

69
/**
710
* The Memory Queue offers basic ISR revalidation by directly requesting a revalidation of a route.
811
*
912
* It offers basic support for in-memory de-duping per isolate.
13+
*
14+
* A service binding called `NEXT_CACHE_REVALIDATION_WORKER` that points to your worker is required.
1015
*/
1116
export class MemoryQueue implements Queue {
1217
readonly name = "memory-queue";
@@ -16,6 +21,9 @@ export class MemoryQueue implements Queue {
1621
constructor(private opts = { revalidationTimeoutMs: DEFAULT_REVALIDATION_TIMEOUT_MS }) {}
1722

1823
async send({ MessageBody: { host, url }, MessageGroupId }: QueueMessage): Promise<void> {
24+
const service = getCloudflareContext().env.NEXT_CACHE_REVALIDATION_WORKER;
25+
if (!service) throw new IgnorableError("No service binding for cache revalidation worker");
26+
1927
if (this.revalidatedPaths.has(MessageGroupId)) return;
2028

2129
this.revalidatedPaths.set(
@@ -30,7 +38,7 @@ export class MemoryQueue implements Queue {
3038
// TODO: Drop the import - https://github.com/opennextjs/opennextjs-cloudflare/issues/361
3139
// @ts-ignore
3240
const manifest = await import("./.next/prerender-manifest.json");
33-
await globalThis.internalFetch(`${protocol}://${host}${url}`, {
41+
await service.fetch(`${protocol}://${host}${url}`, {
3442
method: "HEAD",
3543
headers: {
3644
"x-prerender-revalidate": manifest.preview.previewModeId,

0 commit comments

Comments
 (0)