Skip to content

Commit 314e8b4

Browse files
committed
fix(rivetkit): properly apply clientEndpoint from metadata lookup
1 parent 22a431f commit 314e8b4

File tree

4 files changed

+82
-56
lines changed

4 files changed

+82
-56
lines changed

rivetkit-typescript/packages/rivetkit/src/client/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export const ClientConfigSchema = z.object({
4141
getUpgradeWebSocket: z.custom<GetUpgradeWebSocket>().optional(),
4242

4343
/** Whether to automatically perform health checks when the client is created. */
44-
disableHealthCheck: z.boolean().optional().default(false),
44+
disableMetadataLookup: z.boolean().optional().default(false),
4545
});
4646

4747
export type ClientConfig = z.infer<typeof ClientConfigSchema>;

rivetkit-typescript/packages/rivetkit/src/registry/mod.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export class Registry<A extends RegistryActors> {
8080
// a serverless runner request, so we do not know what to health check
8181
if (config.runnerKind === "serverless") {
8282
logger().debug("disabling health check since using serverless");
83-
config.disableHealthCheck = true;
83+
config.disableMetadataLookup = true;
8484
}
8585

8686
// Auto-configure serverless runner if not in prod
@@ -290,7 +290,7 @@ async function configureServerlessRunner(config: RunnerConfig): Promise<void> {
290290
transport: config.transport,
291291
headers: config.headers,
292292
getUpgradeWebSocket: config.getUpgradeWebSocket,
293-
disableHealthCheck: true, // We don't need health check for this operation
293+
disableMetadataLookup: true, // We don't need health check for this operation
294294
};
295295

296296
// Fetch all datacenters
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import pRetry from "p-retry";
2+
import type { ClientConfig } from "@/client/client";
3+
import type { MetadataResponse } from "@/common/router";
4+
import { stringifyError } from "@/common/utils";
5+
import { getMetadata } from "./api-endpoints";
6+
import { getEndpoint } from "./api-utils";
7+
import { logger } from "./log";
8+
9+
// Global cache to store metadata check promises for each endpoint
10+
const metadataLookupCache = new Map<string, Promise<MetadataResponse>>();
11+
12+
export async function lookupMetadataCached(
13+
config: ClientConfig,
14+
): Promise<MetadataResponse> {
15+
const endpoint = getEndpoint(config);
16+
17+
// Check if metadata lookup is already in progress or completed for this endpoint
18+
const existingPromise = metadataLookupCache.get(endpoint);
19+
if (existingPromise) {
20+
return existingPromise;
21+
}
22+
23+
// Create and store the promise immediately to prevent racing requests
24+
const metadataLookupPromise = pRetry(
25+
async () => {
26+
logger().debug({
27+
msg: "fetching metadata",
28+
endpoint,
29+
});
30+
31+
const metadataData = await getMetadata(config);
32+
33+
logger().debug({
34+
msg: "received metadata",
35+
endpoint,
36+
clientEndpoint: metadataData.clientEndpoint,
37+
});
38+
39+
return metadataData;
40+
},
41+
{
42+
retries: Number.MAX_SAFE_INTEGER,
43+
minTimeout: 500,
44+
maxTimeout: 15_000,
45+
onFailedAttempt: (error) => {
46+
logger().warn({
47+
msg: "failed to fetch metadata, retrying",
48+
endpoint,
49+
attempt: error.attemptNumber,
50+
error: stringifyError(error),
51+
});
52+
},
53+
},
54+
);
55+
56+
metadataLookupCache.set(endpoint, metadataLookupPromise);
57+
return metadataLookupPromise;
58+
}

rivetkit-typescript/packages/rivetkit/src/remote-manager-driver/mod.ts

Lines changed: 21 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import invariant from "invariant";
44
import { deserializeActorKey, serializeActorKey } from "@/actor/keys";
55
import { generateRandomString } from "@/actor/utils";
66
import type { ClientConfig } from "@/client/client";
7+
import type { MetadataResponse } from "@/common/router";
78
import { noopNext, stringifyError } from "@/common/utils";
89
import type {
910
ActorOutput,
@@ -32,6 +33,7 @@ import {
3233
} from "./api-endpoints";
3334
import { EngineApiError, getEndpoint } from "./api-utils";
3435
import { logger } from "./log";
36+
import { lookupMetadataCached } from "./metadata";
3537
import { createWebSocketProxy } from "./ws-proxy";
3638

3739
// TODO:
@@ -48,9 +50,6 @@ import { createWebSocketProxy } from "./ws-proxy";
4850
// };
4951
// })();
5052

51-
// Global cache to store metadata check promises for each endpoint
52-
const metadataCheckCache = new Map<string, Promise<void>>();
53-
5453
export class RemoteManagerDriver implements ManagerDriver {
5554
#config: ClientConfig;
5655
#metadataPromise: Promise<void> | undefined;
@@ -63,64 +62,33 @@ export class RemoteManagerDriver implements ManagerDriver {
6362
logger().info(
6463
"detected next.js build phase, disabling health check",
6564
);
66-
runConfig.disableHealthCheck = true;
65+
runConfig.disableMetadataLookup = true;
6766
}
6867

6968
this.#config = runConfig;
7069

7170
// Perform metadata check if enabled
72-
if (!runConfig.disableHealthCheck) {
73-
this.#metadataPromise = this.#performMetadataCheck(runConfig);
74-
this.#metadataPromise.catch((error) => {
75-
logger().error({
76-
msg: "metadata check failed",
77-
error:
78-
error instanceof Error ? error.message : String(error),
79-
});
80-
});
81-
}
82-
}
83-
84-
async #performMetadataCheck(config: ClientConfig): Promise<void> {
85-
const endpoint = getEndpoint(config);
86-
87-
// Check if metadata check is already in progress or completed for this endpoint
88-
const existingPromise = metadataCheckCache.get(endpoint);
89-
if (existingPromise) {
90-
return existingPromise;
91-
}
92-
93-
// Create and store the promise immediately to prevent racing requests
94-
const metadataCheckPromise = (async () => {
95-
try {
96-
const metadataData = await getMetadata(config);
71+
if (!runConfig.disableMetadataLookup) {
72+
this.#metadataPromise = lookupMetadataCached(this.#config).then(
73+
(metadataData) => {
74+
// Override endpoint for all future requests
75+
if (metadataData.clientEndpoint) {
76+
this.#config.endpoint = metadataData.clientEndpoint;
77+
logger().info({
78+
msg: "overriding cached client endpoint",
79+
endpoint: metadataData.clientEndpoint,
80+
});
81+
}
9782

98-
if (metadataData.clientEndpoint) {
9983
logger().info({
100-
msg: "received new client endpoint from metadata",
101-
endpoint: metadataData.clientEndpoint,
84+
msg: "connected to rivetkit manager",
85+
runtime: metadataData.runtime,
86+
version: metadataData.version,
87+
runner: metadataData.runner,
10288
});
103-
this.#config.endpoint = metadataData.clientEndpoint;
104-
}
105-
106-
// Log successful metadata check with runtime and version info
107-
logger().info({
108-
msg: "connected to rivetkit manager",
109-
runtime: metadataData.runtime,
110-
version: metadataData.version,
111-
runner: metadataData.runner,
112-
});
113-
} catch (error) {
114-
logger().error({
115-
msg: "health check failed, validate the Rivet endpoint is configured correctly",
116-
endpoint,
117-
error: stringifyError(error),
118-
});
119-
}
120-
})();
121-
122-
metadataCheckCache.set(endpoint, metadataCheckPromise);
123-
return metadataCheckPromise;
89+
},
90+
);
91+
}
12492
}
12593

12694
async getForId({

0 commit comments

Comments
 (0)