From 608405ad61b6e9c86740eaf3f1761f98346c5150 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 24 Nov 2025 11:38:22 -0800 Subject: [PATCH 1/5] feat: tie scheduled function attemptDeadline to timeoutSeconds --- spec/v2/providers/scheduler.spec.ts | 35 +++++++++++++++++++++++++---- src/v2/providers/scheduler.ts | 20 +++++++++-------- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/spec/v2/providers/scheduler.spec.ts b/spec/v2/providers/scheduler.spec.ts index 983e2f761..461ab6f20 100644 --- a/spec/v2/providers/scheduler.spec.ts +++ b/spec/v2/providers/scheduler.spec.ts @@ -28,6 +28,7 @@ import { MINIMAL_V2_ENDPOINT } from "../../fixtures"; import { onInit } from "../../../src/v2/core"; import { MockRequest } from "../../fixtures/mockrequest"; import { runHandler } from "../../helper"; +import * as params from "../../../src/params"; const MINIMAL_SCHEDULE_TRIGGER: ManifestEndpoint["scheduleTrigger"] = { schedule: "", @@ -67,7 +68,6 @@ describe("schedule", () => { expect(schedule.getOpts(options)).to.deep.eq({ schedule: "* * * * *", timeZone: "utc", - attemptDeadlineSeconds: undefined, retryConfig: { retryCount: 3, maxRetrySeconds: 1, @@ -110,7 +110,7 @@ describe("schedule", () => { { schedule: "* * * * *", timeZone: "utc", - attemptDeadlineSeconds: 300, + timeoutSeconds: 300, retryCount: 3, maxRetrySeconds: 10, minBackoffSeconds: 11, @@ -127,6 +127,7 @@ describe("schedule", () => { platform: "gcfv2", labels: { key: "val" }, region: ["us-central1"], + timeoutSeconds: 300, scheduleTrigger: { schedule: "* * * * *", timeZone: "utc", @@ -157,13 +158,12 @@ describe("schedule", () => { () => console.log(1) ); - expect(schfn.__endpoint).to.deep.eq({ + expect(schfn.__endpoint).to.deep.equal({ platform: "gcfv2", labels: {}, scheduleTrigger: { schedule: "* * * * *", timeZone: undefined, - attemptDeadlineSeconds: undefined, retryConfig: { retryCount: undefined, maxRetrySeconds: undefined, @@ -181,6 +181,33 @@ describe("schedule", () => { ]); }); + it("should cap attemptDeadlineSeconds at 1800s", () => { + const schfn = schedule.onSchedule( + { + schedule: "* * * * *", + timeoutSeconds: 3600, + }, + () => undefined + ); + + expect(schfn.__endpoint.timeoutSeconds).to.deep.eq(3600); + expect(schfn.__endpoint.scheduleTrigger?.attemptDeadlineSeconds).to.deep.eq(1800); + }); + + it("should set attemptDeadlineSeconds from Expression timeoutSeconds", () => { + const timeout = params.defineInt("TIMEOUT"); + const schfn = schedule.onSchedule( + { + schedule: "* * * * *", + timeoutSeconds: timeout, + }, + () => undefined + ); + + expect(schfn.__endpoint.timeoutSeconds).to.deep.eq(timeout); + expect(schfn.__endpoint.scheduleTrigger?.attemptDeadlineSeconds).to.deep.eq(timeout); + }); + it("should have a .run method", async () => { const testObj = { foo: "bar", diff --git a/src/v2/providers/scheduler.ts b/src/v2/providers/scheduler.ts index 3b0af111f..f3ad80774 100644 --- a/src/v2/providers/scheduler.ts +++ b/src/v2/providers/scheduler.ts @@ -42,7 +42,6 @@ import { withInit } from "../../common/onInit"; interface SeparatedOpts { schedule: string | Expression; timeZone?: timezone | Expression | ResetValue; - attemptDeadlineSeconds?: number | Expression | ResetValue; retryConfig?: { retryCount?: number | Expression | ResetValue; maxRetrySeconds?: number | Expression | ResetValue; @@ -64,7 +63,6 @@ export function getOpts(args: string | ScheduleOptions): SeparatedOpts { return { schedule: args.schedule, timeZone: args.timeZone, - attemptDeadlineSeconds: args.attemptDeadlineSeconds, retryConfig: { retryCount: args.retryCount, maxRetrySeconds: args.maxRetrySeconds, @@ -113,12 +111,6 @@ export interface ScheduleOptions extends options.GlobalOptions { /** The timezone that the schedule executes in. */ timeZone?: timezone | Expression | ResetValue; - /** - * The deadline for job attempts in seconds. If the request handler does not respond by this deadline, - * the request is cancelled and the attempt is marked as a `DEADLINE_EXCEEDED` failure. - * The value must be between 15 and 1800. Defaults to 180. - */ - attemptDeadlineSeconds?: number | Expression | ResetValue; /** The number of retry attempts for a failed run. */ retryCount?: number | Expression | ResetValue; @@ -205,7 +197,17 @@ export function onSchedule( scheduleTrigger: initV2ScheduleTrigger(separatedOpts.schedule, globalOpts, separatedOpts.opts), }; - copyIfPresent(ep.scheduleTrigger, separatedOpts, "timeZone", "attemptDeadlineSeconds"); + copyIfPresent(ep.scheduleTrigger, separatedOpts, "timeZone"); + if (ep.timeoutSeconds) { + if (typeof ep.timeoutSeconds === "number") { + ep.scheduleTrigger.attemptDeadlineSeconds = Math.min(ep.timeoutSeconds, 1800); + } else { + // For Expressions, we can't easily cap it here, but Cloud Scheduler will reject it if it's too high. + // We'll pass it through and let the backend handle validation if possible, or just set it. + // Given the constraints of Expressions, we'll just set it to the expression. + ep.scheduleTrigger.attemptDeadlineSeconds = ep.timeoutSeconds; + } + } copyIfPresent( ep.scheduleTrigger.retryConfig, separatedOpts.retryConfig, From 01dd6d5b4d83831c0aad040969cc1a85e4cdc327 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 24 Nov 2025 11:38:22 -0800 Subject: [PATCH 2/5] feat: tie scheduled function attemptDeadline to timeoutSeconds --- spec/v2/providers/scheduler.spec.ts | 35 +++++++++++++++++++++++++---- src/v2/providers/scheduler.ts | 13 ++++------- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/spec/v2/providers/scheduler.spec.ts b/spec/v2/providers/scheduler.spec.ts index 983e2f761..ee14ac639 100644 --- a/spec/v2/providers/scheduler.spec.ts +++ b/spec/v2/providers/scheduler.spec.ts @@ -28,6 +28,7 @@ import { MINIMAL_V2_ENDPOINT } from "../../fixtures"; import { onInit } from "../../../src/v2/core"; import { MockRequest } from "../../fixtures/mockrequest"; import { runHandler } from "../../helper"; +import * as params from "../../../src/params"; const MINIMAL_SCHEDULE_TRIGGER: ManifestEndpoint["scheduleTrigger"] = { schedule: "", @@ -67,7 +68,6 @@ describe("schedule", () => { expect(schedule.getOpts(options)).to.deep.eq({ schedule: "* * * * *", timeZone: "utc", - attemptDeadlineSeconds: undefined, retryConfig: { retryCount: 3, maxRetrySeconds: 1, @@ -110,7 +110,7 @@ describe("schedule", () => { { schedule: "* * * * *", timeZone: "utc", - attemptDeadlineSeconds: 300, + timeoutSeconds: 300, retryCount: 3, maxRetrySeconds: 10, minBackoffSeconds: 11, @@ -127,6 +127,7 @@ describe("schedule", () => { platform: "gcfv2", labels: { key: "val" }, region: ["us-central1"], + timeoutSeconds: 300, scheduleTrigger: { schedule: "* * * * *", timeZone: "utc", @@ -157,13 +158,12 @@ describe("schedule", () => { () => console.log(1) ); - expect(schfn.__endpoint).to.deep.eq({ + expect(schfn.__endpoint).to.deep.equal({ platform: "gcfv2", labels: {}, scheduleTrigger: { schedule: "* * * * *", timeZone: undefined, - attemptDeadlineSeconds: undefined, retryConfig: { retryCount: undefined, maxRetrySeconds: undefined, @@ -181,6 +181,33 @@ describe("schedule", () => { ]); }); + it("should set attemptDeadlineSeconds from timeoutSeconds", () => { + const schfn = schedule.onSchedule( + { + schedule: "* * * * *", + timeoutSeconds: 3600, + }, + () => undefined + ); + + expect(schfn.__endpoint.timeoutSeconds).to.deep.eq(3600); + expect(schfn.__endpoint.scheduleTrigger?.attemptDeadlineSeconds).to.deep.eq(3600); + }); + + it("should set attemptDeadlineSeconds from Expression timeoutSeconds", () => { + const timeout = params.defineInt("TIMEOUT"); + const schfn = schedule.onSchedule( + { + schedule: "* * * * *", + timeoutSeconds: timeout, + }, + () => undefined + ); + + expect(schfn.__endpoint.timeoutSeconds).to.deep.eq(timeout); + expect(schfn.__endpoint.scheduleTrigger?.attemptDeadlineSeconds).to.deep.eq(timeout); + }); + it("should have a .run method", async () => { const testObj = { foo: "bar", diff --git a/src/v2/providers/scheduler.ts b/src/v2/providers/scheduler.ts index 3b0af111f..16fc0530c 100644 --- a/src/v2/providers/scheduler.ts +++ b/src/v2/providers/scheduler.ts @@ -42,7 +42,6 @@ import { withInit } from "../../common/onInit"; interface SeparatedOpts { schedule: string | Expression; timeZone?: timezone | Expression | ResetValue; - attemptDeadlineSeconds?: number | Expression | ResetValue; retryConfig?: { retryCount?: number | Expression | ResetValue; maxRetrySeconds?: number | Expression | ResetValue; @@ -64,7 +63,6 @@ export function getOpts(args: string | ScheduleOptions): SeparatedOpts { return { schedule: args.schedule, timeZone: args.timeZone, - attemptDeadlineSeconds: args.attemptDeadlineSeconds, retryConfig: { retryCount: args.retryCount, maxRetrySeconds: args.maxRetrySeconds, @@ -113,12 +111,6 @@ export interface ScheduleOptions extends options.GlobalOptions { /** The timezone that the schedule executes in. */ timeZone?: timezone | Expression | ResetValue; - /** - * The deadline for job attempts in seconds. If the request handler does not respond by this deadline, - * the request is cancelled and the attempt is marked as a `DEADLINE_EXCEEDED` failure. - * The value must be between 15 and 1800. Defaults to 180. - */ - attemptDeadlineSeconds?: number | Expression | ResetValue; /** The number of retry attempts for a failed run. */ retryCount?: number | Expression | ResetValue; @@ -205,7 +197,10 @@ export function onSchedule( scheduleTrigger: initV2ScheduleTrigger(separatedOpts.schedule, globalOpts, separatedOpts.opts), }; - copyIfPresent(ep.scheduleTrigger, separatedOpts, "timeZone", "attemptDeadlineSeconds"); + copyIfPresent(ep.scheduleTrigger, separatedOpts, "timeZone"); + if (ep.timeoutSeconds) { + ep.scheduleTrigger.attemptDeadlineSeconds = ep.timeoutSeconds; + } copyIfPresent( ep.scheduleTrigger.retryConfig, separatedOpts.retryConfig, From ffd04966b135c793b7e3881a8f7fc1e7461ed3b8 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 24 Nov 2025 12:35:11 -0800 Subject: [PATCH 3/5] run formatter. --- src/v2/providers/scheduler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/v2/providers/scheduler.ts b/src/v2/providers/scheduler.ts index 16fc0530c..e9a26c064 100644 --- a/src/v2/providers/scheduler.ts +++ b/src/v2/providers/scheduler.ts @@ -111,7 +111,6 @@ export interface ScheduleOptions extends options.GlobalOptions { /** The timezone that the schedule executes in. */ timeZone?: timezone | Expression | ResetValue; - /** The number of retry attempts for a failed run. */ retryCount?: number | Expression | ResetValue; From 3490554ffe7b635e507f031b85e2204f1305f6f0 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 24 Nov 2025 12:35:11 -0800 Subject: [PATCH 4/5] run formatter. --- spec/runtime/manifest.spec.ts | 1 - spec/v2/providers/scheduler.spec.ts | 2 -- src/runtime/manifest.ts | 11 ++--------- src/v2/providers/scheduler.ts | 4 ---- 4 files changed, 2 insertions(+), 16 deletions(-) diff --git a/spec/runtime/manifest.spec.ts b/spec/runtime/manifest.spec.ts index 8f520a210..7534ba2ee 100644 --- a/spec/runtime/manifest.spec.ts +++ b/spec/runtime/manifest.spec.ts @@ -286,7 +286,6 @@ describe("initScheduleTrigger", () => { expect(st).to.deep.eq({ schedule: "every 30 minutes", timeZone: RESET_VALUE, - attemptDeadlineSeconds: RESET_VALUE, retryConfig: { retryCount: RESET_VALUE, maxDoublings: RESET_VALUE, diff --git a/spec/v2/providers/scheduler.spec.ts b/spec/v2/providers/scheduler.spec.ts index cd5f17985..48e39ea67 100644 --- a/spec/v2/providers/scheduler.spec.ts +++ b/spec/v2/providers/scheduler.spec.ts @@ -32,7 +32,6 @@ import { runHandler } from "../../helper"; const MINIMAL_SCHEDULE_TRIGGER: ManifestEndpoint["scheduleTrigger"] = { schedule: "", timeZone: options.RESET_VALUE, - attemptDeadlineSeconds: options.RESET_VALUE, retryConfig: { retryCount: options.RESET_VALUE, maxRetrySeconds: options.RESET_VALUE, @@ -130,7 +129,6 @@ describe("schedule", () => { scheduleTrigger: { schedule: "* * * * *", timeZone: "utc", - attemptDeadlineSeconds: 300, retryConfig: { retryCount: 3, maxRetrySeconds: 10, diff --git a/src/runtime/manifest.ts b/src/runtime/manifest.ts index e2c66c0ba..a2b92dbeb 100644 --- a/src/runtime/manifest.ts +++ b/src/runtime/manifest.ts @@ -97,7 +97,6 @@ export interface ManifestEndpoint { scheduleTrigger?: { schedule: string | Expression; timeZone?: string | Expression | ResetValue; - attemptDeadlineSeconds?: number | Expression | ResetValue; retryConfig?: { retryCount?: number | Expression | ResetValue; maxRetrySeconds?: string | Expression | ResetValue; @@ -260,14 +259,12 @@ const RESETTABLE_V1_SCHEDULE_OPTIONS: Omit< const RESETTABLE_V2_SCHEDULE_OPTIONS: Omit< ResettableKeys, "maxRetryDuration" | "maxBackoffDuration" | "minBackoffDuration" -> & - Pick, "attemptDeadlineSeconds"> = { + > = { retryCount: null, maxDoublings: null, maxRetrySeconds: null, minBackoffSeconds: null, maxBackoffSeconds: null, - attemptDeadlineSeconds: null, }; function initScheduleTrigger( @@ -281,11 +278,7 @@ function initScheduleTrigger( }; if (opts.every((opt) => !opt?.preserveExternalChanges)) { for (const key of Object.keys(resetOptions)) { - if (key === "attemptDeadlineSeconds") { - scheduleTrigger[key] = RESET_VALUE; - } else { - scheduleTrigger.retryConfig[key] = RESET_VALUE; - } + scheduleTrigger.retryConfig[key] = RESET_VALUE; } scheduleTrigger = { ...scheduleTrigger, timeZone: RESET_VALUE }; } diff --git a/src/v2/providers/scheduler.ts b/src/v2/providers/scheduler.ts index 16fc0530c..1f8f33c31 100644 --- a/src/v2/providers/scheduler.ts +++ b/src/v2/providers/scheduler.ts @@ -111,7 +111,6 @@ export interface ScheduleOptions extends options.GlobalOptions { /** The timezone that the schedule executes in. */ timeZone?: timezone | Expression | ResetValue; - /** The number of retry attempts for a failed run. */ retryCount?: number | Expression | ResetValue; @@ -198,9 +197,6 @@ export function onSchedule( }; copyIfPresent(ep.scheduleTrigger, separatedOpts, "timeZone"); - if (ep.timeoutSeconds) { - ep.scheduleTrigger.attemptDeadlineSeconds = ep.timeoutSeconds; - } copyIfPresent( ep.scheduleTrigger.retryConfig, separatedOpts.retryConfig, From ab333859b1d19af7c88116997c45488782471f74 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 26 Nov 2025 16:42:47 -0800 Subject: [PATCH 5/5] nit: fix formatting --- src/runtime/manifest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/manifest.ts b/src/runtime/manifest.ts index a2b92dbeb..4d52d5eaf 100644 --- a/src/runtime/manifest.ts +++ b/src/runtime/manifest.ts @@ -259,7 +259,7 @@ const RESETTABLE_V1_SCHEDULE_OPTIONS: Omit< const RESETTABLE_V2_SCHEDULE_OPTIONS: Omit< ResettableKeys, "maxRetryDuration" | "maxBackoffDuration" | "minBackoffDuration" - > = { +> = { retryCount: null, maxDoublings: null, maxRetrySeconds: null,