mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-30 21:49:18 +01:00
fix(runtime): honor provider acquisition timeout defaults (#14097)
Declare provider acquisition budgets so slow Daytona creation does not hit the host’s 30-second fallback. Bound creation and setup to one deadline and preserve scoped cleanup ownership after timeout. Legacy drivers retain their original call shape. Validated by 238 provider/manifest tests, focused database and heartbeat regressions, root and standalone provider typecheck/build, and full CI. Local broad tests also expose recorded Mac baseline limitations. Greptile 5/5. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
7f3c06dac4
commit
ffa32373bc
9 files changed
+295
-77
No files matched your search
@@ -4685,14 +4685,20 @@ describeEmbeddedPostgres("environmentRuntimeService", () => {
|
||||
expect(checks).toBe(readyAfterChecks);
|
||||
});
|
||||
|
||||
it("extends plugin-backed sandbox lease RPC timeouts from provider config", async () => {
|
||||
it.each([
|
||||
{ label: "explicit provider and bridge budgets", config: { timeoutMs: 1_234, bridgeRequestTimeoutMs: 40_000 }, declared: 300_000, expected: 70_000 },
|
||||
{ label: "declared acquisition default", config: {}, declared: 300_000, expected: 330_000 },
|
||||
{ label: "explicit shorter provider budget", config: { timeoutMs: 5_000 }, declared: 300_000, expected: 35_000 },
|
||||
{ label: "bridge budget does not shorten acquisition default", config: { bridgeRequestTimeoutMs: 40_000 }, declared: 300_000, expected: 330_000 },
|
||||
{ label: "bridge extends the acquisition default", config: { bridgeRequestTimeoutMs: 400_000 }, declared: 300_000, expected: 430_000 },
|
||||
{ label: "undeclared provider retains worker default", config: {}, declared: undefined, expected: undefined },
|
||||
])("uses $label for plugin-backed sandbox acquisition", async ({ config, declared, expected }) => {
|
||||
const pluginId = randomUUID();
|
||||
const { companyId, environment: baseEnvironment, runId } = await seedEnvironment();
|
||||
const providerConfig = {
|
||||
provider: "fake-plugin",
|
||||
image: "fake:test",
|
||||
timeoutMs: 1_234,
|
||||
bridgeRequestTimeoutMs: 40_000,
|
||||
...config,
|
||||
reuseLease: false,
|
||||
};
|
||||
const environment = {
|
||||
@@ -4728,7 +4734,10 @@ describeEmbeddedPostgres("environmentRuntimeService", () => {
|
||||
driverKey: "fake-plugin",
|
||||
kind: "sandbox_provider",
|
||||
displayName: "Fake Plugin",
|
||||
configSchema: { type: "object" },
|
||||
defaultAcquireTimeoutMs: declared,
|
||||
// A timeoutMs schema default can mean lease lifetime (for example
|
||||
// E2B). Only the dedicated declaration sets the host RPC budget.
|
||||
configSchema: { type: "object", properties: { timeoutMs: { type: "number", default: 3_600_000 } } },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -4746,8 +4755,7 @@ describeEmbeddedPostgres("environmentRuntimeService", () => {
|
||||
metadata: {
|
||||
provider: "fake-plugin",
|
||||
image: "fake:test",
|
||||
timeoutMs: 1_234,
|
||||
bridgeRequestTimeoutMs: 40_000,
|
||||
...config,
|
||||
reuseLease: false,
|
||||
},
|
||||
};
|
||||
@@ -4774,12 +4782,11 @@ describeEmbeddedPostgres("environmentRuntimeService", () => {
|
||||
driverKey: "fake-plugin",
|
||||
config: {
|
||||
image: "fake:test",
|
||||
timeoutMs: 1_234,
|
||||
bridgeRequestTimeoutMs: 40_000,
|
||||
...config,
|
||||
reuseLease: false,
|
||||
},
|
||||
}),
|
||||
70_000,
|
||||
expected,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7068,7 +7075,7 @@ describeEmbeddedPostgres("environmentRuntimeService", () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it("delegates plugin environment leases through the plugin worker manager", async () => {
|
||||
it.each([undefined, 300_000])("delegates plugin environment leases with acquisition budget %s", async (defaultAcquireTimeoutMs) => {
|
||||
const pluginId = randomUUID();
|
||||
const expiresAt = new Date(Date.now() + 60_000).toISOString();
|
||||
const workerManager = {
|
||||
@@ -7129,6 +7136,7 @@ describeEmbeddedPostgres("environmentRuntimeService", () => {
|
||||
{
|
||||
driverKey: "fake-plugin",
|
||||
displayName: "Fake plugin",
|
||||
defaultAcquireTimeoutMs,
|
||||
configSchema: { type: "object" },
|
||||
},
|
||||
],
|
||||
@@ -7158,7 +7166,7 @@ describeEmbeddedPostgres("environmentRuntimeService", () => {
|
||||
adapterType: undefined,
|
||||
runId,
|
||||
workspaceMode: undefined,
|
||||
});
|
||||
}, ...(defaultAcquireTimeoutMs === undefined ? [] : [330_000]));
|
||||
expect(acquired.lease.providerLeaseId).toBe("plugin-lease-1");
|
||||
expect(acquired.lease.expiresAt?.toISOString()).toBe(expiresAt);
|
||||
expect(acquired.lease.metadata).toMatchObject({
|
||||
|
||||
@@ -485,9 +485,16 @@ export interface EnvironmentDriverReleaseInput {
|
||||
status: Extract<EnvironmentLeaseStatus, "released" | "expired" | "failed">;
|
||||
}
|
||||
|
||||
function resolvePluginSandboxRpcTimeoutMs(config: Record<string, unknown>): number | undefined {
|
||||
function resolvePluginSandboxRpcTimeoutMs(
|
||||
config: Record<string, unknown>,
|
||||
defaultTimeoutMs?: number,
|
||||
): number | undefined {
|
||||
const configuredTimeoutMs = typeof config.timeoutMs === "number" &&
|
||||
Number.isFinite(config.timeoutMs) && config.timeoutMs > 0
|
||||
? config.timeoutMs
|
||||
: defaultTimeoutMs;
|
||||
const timeoutCandidates = [
|
||||
typeof config.timeoutMs === "number" ? config.timeoutMs : undefined,
|
||||
configuredTimeoutMs,
|
||||
typeof config.bridgeRequestTimeoutMs === "number" ? config.bridgeRequestTimeoutMs : undefined,
|
||||
]
|
||||
.filter((value): value is number => typeof value === "number" && Number.isFinite(value) && value > 0)
|
||||
@@ -2084,7 +2091,10 @@ function createSandboxEnvironmentDriver(
|
||||
? { requestedExpiresAt: requestedExpiresAtParam(input.requestedExpiresAt) }
|
||||
: {}),
|
||||
},
|
||||
resolvePluginSandboxRpcTimeoutMs(workerConfig),
|
||||
resolvePluginSandboxRpcTimeoutMs(
|
||||
workerConfig,
|
||||
pluginProvider.resolved.driver.defaultAcquireTimeoutMs,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
const cleanup = readEnvironmentCreationCleanupError(error);
|
||||
@@ -3390,7 +3400,7 @@ function createPluginEnvironmentDriver(
|
||||
if (!workerManager.isRunning(plugin.id)) {
|
||||
throw new Error(`Plugin environment driver "${pluginDriverProviderKey(config)}" has no running worker.`);
|
||||
}
|
||||
return { plugin };
|
||||
return { plugin, driver };
|
||||
}
|
||||
|
||||
async function resolvePluginDriverForRelease(input: EnvironmentDriverReleaseInput) {
|
||||
@@ -3459,7 +3469,12 @@ function createPluginEnvironmentDriver(
|
||||
if (parsed.driver !== "plugin") {
|
||||
throw new Error(`Expected plugin environment config for driver "${input.environment.driver}".`);
|
||||
}
|
||||
const { plugin } = await resolvePluginDriver(parsed.config);
|
||||
const { plugin, driver } = await resolvePluginDriver(parsed.config);
|
||||
const rpcTimeoutMs = resolvePluginSandboxRpcTimeoutMs(
|
||||
parsed.config.driverConfig,
|
||||
driver.defaultAcquireTimeoutMs,
|
||||
);
|
||||
const timeoutOverride: [number?] = rpcTimeoutMs === undefined ? [] : [rpcTimeoutMs];
|
||||
const providerLease = await workerManager.call(plugin.id, "environmentAcquireLease", {
|
||||
driverKey: parsed.config.driverKey,
|
||||
companyId: input.companyId,
|
||||
@@ -3477,7 +3492,7 @@ function createPluginEnvironmentDriver(
|
||||
...(requestedExpiresAtParam(input.requestedExpiresAt) !== undefined
|
||||
? { requestedExpiresAt: requestedExpiresAtParam(input.requestedExpiresAt) }
|
||||
: {}),
|
||||
} as PluginEnvironmentAcquireLeaseParams);
|
||||
} as PluginEnvironmentAcquireLeaseParams, ...timeoutOverride);
|
||||
|
||||
return await environmentsSvc.acquireLease({
|
||||
companyId: input.companyId,
|
||||
|
||||
Reference in new issue
Block a user