Skip to content

Commit c17753e

Browse files
Copilotserhalp
andauthored
feat: implement functions timeout defaults and overrides in dev (#346)
--------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: serhalp <[email protected]> Co-authored-by: Philippe Serhal <[email protected]> Co-authored-by: Philippe Serhal <[email protected]>
1 parent 67034cc commit c17753e

File tree

7 files changed

+194
-4
lines changed

7 files changed

+194
-4
lines changed

packages/dev/src/main.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,6 @@ export class NetlifyDev {
555555
projectRoot: this.#projectRoot,
556556
settings: {},
557557
siteId: this.#siteID,
558-
timeouts: {},
559558
userFunctionsPath: userFunctionsPathExists ? userFunctionsPath : undefined,
560559
})
561560
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { describe, expect, test } from 'vitest'
2+
3+
import { SYNCHRONOUS_FUNCTION_TIMEOUT, BACKGROUND_FUNCTION_TIMEOUT } from '@netlify/functions'
4+
import { FunctionsRegistry } from './registry.js'
5+
6+
describe('FunctionsRegistry timeout configuration', () => {
7+
test('uses default timeouts when no config or override provided', () => {
8+
const registry = new FunctionsRegistry({
9+
config: {},
10+
destPath: '/tmp/test',
11+
projectRoot: '/tmp/project',
12+
settings: {},
13+
})
14+
15+
expect(registry.timeouts).toEqual({
16+
syncFunctions: SYNCHRONOUS_FUNCTION_TIMEOUT,
17+
backgroundFunctions: BACKGROUND_FUNCTION_TIMEOUT,
18+
})
19+
})
20+
21+
test('uses functions_timeout from siteInfo for sync functions only', () => {
22+
const registry = new FunctionsRegistry({
23+
config: {
24+
siteInfo: {
25+
functions_timeout: 60,
26+
},
27+
},
28+
destPath: '/tmp/test',
29+
projectRoot: '/tmp/project',
30+
settings: {},
31+
})
32+
33+
expect(registry.timeouts).toEqual({
34+
syncFunctions: 60,
35+
backgroundFunctions: BACKGROUND_FUNCTION_TIMEOUT,
36+
})
37+
})
38+
39+
test('uses functions_config.timeout from siteInfo for sync functions only', () => {
40+
const registry = new FunctionsRegistry({
41+
config: {
42+
siteInfo: {
43+
functions_config: {
44+
timeout: 45,
45+
},
46+
},
47+
},
48+
destPath: '/tmp/test',
49+
projectRoot: '/tmp/project',
50+
settings: {},
51+
})
52+
53+
expect(registry.timeouts).toEqual({
54+
syncFunctions: 45,
55+
backgroundFunctions: BACKGROUND_FUNCTION_TIMEOUT,
56+
})
57+
})
58+
59+
test('prefers functions_timeout over functions_config.timeout for sync functions', () => {
60+
const registry = new FunctionsRegistry({
61+
config: {
62+
siteInfo: {
63+
functions_timeout: 60,
64+
functions_config: {
65+
timeout: 45,
66+
},
67+
},
68+
},
69+
destPath: '/tmp/test',
70+
projectRoot: '/tmp/project',
71+
settings: {},
72+
})
73+
74+
expect(registry.timeouts).toEqual({
75+
syncFunctions: 60,
76+
backgroundFunctions: BACKGROUND_FUNCTION_TIMEOUT,
77+
})
78+
})
79+
80+
test('uses override timeouts when provided', () => {
81+
const registry = new FunctionsRegistry({
82+
config: {
83+
siteInfo: {
84+
functions_timeout: 60,
85+
},
86+
},
87+
destPath: '/tmp/test',
88+
projectRoot: '/tmp/project',
89+
settings: {},
90+
timeouts: {
91+
syncFunctions: 120,
92+
backgroundFunctions: 1800,
93+
},
94+
})
95+
96+
expect(registry.timeouts).toEqual({
97+
syncFunctions: 120,
98+
backgroundFunctions: 1800,
99+
})
100+
})
101+
102+
test('allows partial override of timeouts', () => {
103+
const registry = new FunctionsRegistry({
104+
config: {
105+
siteInfo: {
106+
functions_timeout: 60,
107+
},
108+
},
109+
destPath: '/tmp/test',
110+
projectRoot: '/tmp/project',
111+
settings: {},
112+
timeouts: {
113+
syncFunctions: 120,
114+
},
115+
})
116+
117+
expect(registry.timeouts).toEqual({
118+
syncFunctions: 120,
119+
backgroundFunctions: BACKGROUND_FUNCTION_TIMEOUT,
120+
})
121+
})
122+
123+
test('falls back to defaults when siteInfo is undefined', () => {
124+
const registry = new FunctionsRegistry({
125+
config: {
126+
siteInfo: undefined,
127+
},
128+
destPath: '/tmp/test',
129+
projectRoot: '/tmp/project',
130+
settings: {},
131+
})
132+
133+
expect(registry.timeouts).toEqual({
134+
syncFunctions: SYNCHRONOUS_FUNCTION_TIMEOUT,
135+
backgroundFunctions: BACKGROUND_FUNCTION_TIMEOUT,
136+
})
137+
})
138+
139+
test('falls back to defaults when config is empty object', () => {
140+
const registry = new FunctionsRegistry({
141+
config: {},
142+
destPath: '/tmp/test',
143+
projectRoot: '/tmp/project',
144+
settings: {},
145+
})
146+
147+
expect(registry.timeouts).toEqual({
148+
syncFunctions: SYNCHRONOUS_FUNCTION_TIMEOUT,
149+
backgroundFunctions: BACKGROUND_FUNCTION_TIMEOUT,
150+
})
151+
})
152+
})

packages/functions/dev/src/registry.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { env } from 'node:process'
55

66
import type { EnvironmentContext as BlobsContext } from '@netlify/blobs'
77
import { DevEventHandler, watchDebounced } from '@netlify/dev-utils'
8+
import { SYNCHRONOUS_FUNCTION_TIMEOUT, BACKGROUND_FUNCTION_TIMEOUT } from '@netlify/functions'
89
import { ListedFunction, listFunctions, Manifest } from '@netlify/zip-it-and-ship-it'
910
import extractZip from 'extract-zip'
1011

@@ -37,7 +38,7 @@ export interface FunctionRegistryOptions {
3738
manifest?: Manifest
3839
projectRoot: string
3940
settings: any
40-
timeouts: any
41+
timeouts?: { syncFunctions?: number; backgroundFunctions?: number }
4142
watch?: boolean
4243
}
4344

@@ -100,7 +101,16 @@ export class FunctionsRegistry {
100101
this.handleEvent = eventHandler ?? (() => {})
101102
this.internalFunctionsPath = internalFunctionsPath
102103
this.projectRoot = projectRoot
103-
this.timeouts = timeouts
104+
105+
// Calculate timeouts from config if not provided as override
106+
const siteTimeout = config?.siteInfo?.functions_timeout ?? config?.siteInfo?.functions_config?.timeout
107+
this.timeouts = {
108+
syncFunctions: timeouts?.syncFunctions ?? siteTimeout ?? SYNCHRONOUS_FUNCTION_TIMEOUT,
109+
// NOTE: This isn't documented, but the generically named "functions timeout" config fields only
110+
// apply to synchronous Netlify Functions.
111+
backgroundFunctions: timeouts?.backgroundFunctions ?? BACKGROUND_FUNCTION_TIMEOUT,
112+
}
113+
104114
this.settings = settings
105115
this.watch = watch === true
106116

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { describe, expect, test } from 'vitest'
2+
3+
import { BACKGROUND_FUNCTION_TIMEOUT, SYNCHRONOUS_FUNCTION_TIMEOUT } from './consts.js'
4+
5+
describe('Function timeout constants', () => {
6+
test('exports correct timeout values', () => {
7+
expect(SYNCHRONOUS_FUNCTION_TIMEOUT).toBe(30)
8+
expect(BACKGROUND_FUNCTION_TIMEOUT).toBe(900)
9+
})
10+
})

packages/functions/prod/src/lib/consts.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,21 @@ const HTTP_STATUS_METHOD_NOT_ALLOWED = 405
33
const HTTP_STATUS_OK = 200
44
const METADATA_VERSION = 1
55

6-
export { BUILDER_FUNCTIONS_FLAG, HTTP_STATUS_METHOD_NOT_ALLOWED, HTTP_STATUS_OK, METADATA_VERSION }
6+
/**
7+
* Default timeout for synchronous functions in seconds
8+
*/
9+
const SYNCHRONOUS_FUNCTION_TIMEOUT = 30
10+
11+
/**
12+
* Default timeout for background functions in seconds
13+
*/
14+
const BACKGROUND_FUNCTION_TIMEOUT = 900
15+
16+
export {
17+
BUILDER_FUNCTIONS_FLAG,
18+
HTTP_STATUS_METHOD_NOT_ALLOWED,
19+
HTTP_STATUS_OK,
20+
METADATA_VERSION,
21+
SYNCHRONOUS_FUNCTION_TIMEOUT,
22+
BACKGROUND_FUNCTION_TIMEOUT,
23+
}

packages/functions/prod/src/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export { builder } from './lib/builder.js'
1010
export { purgeCache } from './lib/purge_cache.js'
1111
export { schedule } from './lib/schedule.js'
1212
export { stream } from './lib/stream.js'
13+
export { SYNCHRONOUS_FUNCTION_TIMEOUT, BACKGROUND_FUNCTION_TIMEOUT } from './lib/consts.js'
1314
export * from './function/index.js'

packages/types/src/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export type { Context } from './lib/context/context.js'
22
export type { Cookie } from './lib/context/cookies.js'
33
export type { EnvironmentVariables } from './lib/environment-variables.js'
44
export type { NetlifyGlobal } from './lib/globals.js'
5+
export type { Site } from './lib/context/site.js'

0 commit comments

Comments
 (0)