Skip to content

Commit dc36793

Browse files
authored
test(functions): background deno run in relay and clean up networks (#2739)
Signed-off-by: Ferhat Elmas <elmas.ferhat@gmail.com>
1 parent cb76828 commit dc36793

3 files changed

Lines changed: 75 additions & 78 deletions

File tree

‎packages/core/functions-js/test/relay/container.ts‎

Lines changed: 65 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,30 @@
11
import * as fs from 'fs'
22
import { nanoid } from 'nanoid'
33
import { sign } from 'jsonwebtoken'
4-
import { GenericContainer, Network, StartedTestContainer, Wait } from 'testcontainers'
5-
import { ExecResult } from 'testcontainers/dist/docker/types'
4+
import {
5+
GenericContainer,
6+
Network,
7+
StartedNetwork,
8+
StartedTestContainer,
9+
Wait,
10+
} from 'testcontainers'
611
import { attach, log } from '../utils/jest-custom-reporter'
712

8-
/**
9-
* A Relay contains a running relay container that has a unique ID and two promises:
10-
* one for the `deno cache` and one for the `deno run` the required function
11-
*/
1213
export class Relay {
1314
container: StartedTestContainer
15+
network: StartedNetwork
1416
id: string
15-
execCache: Promise<ExecResult>
16-
execRun: Promise<ExecResult>
17-
constructor(
18-
container: StartedTestContainer,
19-
id: string,
20-
execCache: Promise<ExecResult>,
21-
execRun: Promise<ExecResult>
22-
) {
17+
constructor(container: StartedTestContainer, network: StartedNetwork, id: string) {
2318
this.container = container
19+
this.network = network
2420
this.id = id
25-
this.execCache = execCache
26-
this.execRun = execRun
2721
}
2822

29-
/**
30-
* Safely stops the relay container and cleans up exec processes
31-
*/
3223
async stop(): Promise<void> {
3324
try {
34-
// Try to stop the container - this will trigger cleanup of exec instances
3525
await this.container.stop({ timeout: 5000 })
36-
} catch (error: any) {
37-
// Ignore "no such exec" errors during cleanup - they're harmless
38-
if (!error?.message?.includes('no such exec')) {
39-
throw error
40-
}
41-
// Container is already stopped or exec instances are gone, which is fine
26+
} finally {
27+
await this.network.stop()
4228
}
4329
}
4430
}
@@ -86,56 +72,64 @@ export async function runRelay(
8672

8773
// start relay and function
8874
log(`start relay ${slug + '-' + id}`)
89-
const startedRelay = await relay.start()
90-
const execCache = startedRelay.exec(['deno', 'cache', `/home/deno/${slug}/index.ts`])
91-
const execRun = startedRelay.exec([
92-
'deno',
93-
'run',
94-
'--allow-all',
95-
'--watch',
96-
'--unstable',
97-
`/home/deno/${slug}/index.ts`,
98-
])
75+
const startedRelay = await relay.start().catch(async (error) => {
76+
await network.stop().catch((stopError) => log(`failed to stop network ${id}: ${stopError}`))
77+
throw error
78+
})
79+
const started = new Relay(startedRelay, network, id)
80+
const functionPath = `/home/deno/${slug}/index.ts`
81+
try {
82+
for (const command of [
83+
['deno', 'cache', functionPath],
84+
[
85+
'sh',
86+
'-c',
87+
'deno run --allow-all --watch --unstable "$1" > /tmp/function.log 2>&1 < /dev/null &',
88+
'sh',
89+
functionPath,
90+
],
91+
]) {
92+
const { exitCode, output } = await startedRelay.exec(command)
93+
if (exitCode !== 0) {
94+
throw new Error(`Failed to start function ${slug}: ${output}`)
95+
}
96+
}
9997

100-
// wait till function is running
101-
log(`check function is healthy: ${slug + '-' + id}`)
102-
for (let ctr = 0; ctr < 60; ctr++) {
103-
try {
104-
const healthCheck = await fetch(
105-
`http://localhost:${startedRelay.getMappedPort(8081)}/${slug}`,
106-
{
107-
method: 'POST',
108-
headers: {
109-
Authorization: `Bearer ${sign({ name: 'check' }, jwtSecret)}`,
110-
},
98+
// wait till function is running
99+
log(`check function is healthy: ${slug + '-' + id}`)
100+
for (let ctr = 0; ctr < 60; ctr++) {
101+
try {
102+
const healthCheck = await fetch(
103+
`http://localhost:${startedRelay.getMappedPort(8081)}/${slug}`,
104+
{
105+
method: 'POST',
106+
headers: {
107+
Authorization: `Bearer ${sign({ name: 'check' }, jwtSecret)}`,
108+
},
109+
}
110+
)
111+
if (healthCheck.ok) {
112+
log(`function started to serve: ${slug + '-' + id}`)
113+
// Add a small delay after health check passes to ensure full readiness
114+
await new Promise((resolve) => setTimeout(resolve, 1000))
115+
return started
111116
}
112-
)
113-
if (healthCheck.ok || healthCheck.status === 101) {
114-
log(`function started to serve: ${slug + '-' + id}`)
115-
// Add a small delay after health check passes to ensure full readiness
116-
await new Promise((resolve) => setTimeout(resolve, 1000))
117-
return new Relay(startedRelay, id, execCache, execRun)
118-
}
119-
} catch (error) {
120-
// Native fetch throws an error when it encounters HTTP 101 (WebSocket upgrade)
121-
// If we get a TypeError (which fetch throws for protocol errors like 101),
122-
// we consider the function ready to serve
123-
if (error instanceof TypeError) {
124-
// This likely means we got a 101 response that native fetch couldn't handle
125-
// The server is responding, so consider it ready
126-
log(`function started to serve (detected via error): ${slug + '-' + id}`)
127-
await new Promise((resolve) => setTimeout(resolve, 1000))
128-
return new Relay(startedRelay, id, execCache, execRun)
117+
} catch {
118+
// relay not reachable yet (connection refused/reset), retry
129119
}
130-
// For other errors (connection refused, etc.), continue retrying
120+
await new Promise((resolve) => setTimeout(resolve, 500))
131121
}
132-
await new Promise((resolve) => setTimeout(resolve, 500))
133-
}
134122

135-
// if function hasn't started, stop container and throw
136-
log(`function failed to start: ${slug + '-' + id}`)
137-
startedRelay.stop()
138-
throw new Error("function haven'start correctly")
123+
// if function hasn't started, throw with its log
124+
log(`function failed to start: ${slug + '-' + id}`)
125+
const { output } = await startedRelay.exec(['cat', '/tmp/function.log'])
126+
throw new Error(`function hasn't started correctly: ${output}`)
127+
} catch (error) {
128+
await started
129+
.stop()
130+
.catch((stopError) => log(`failed to stop relay ${slug + '-' + id}: ${stopError}`))
131+
throw error
132+
}
139133
}
140134

141135
/**

‎pnpm-lock.yaml‎

Lines changed: 8 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎pnpm-workspace.yaml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ overrides:
3434
# (GHSA-r28c-9q8g-f849, fixed in 8.5.18); next pins the old 8.4.31
3535
# transitively in the realtime example.
3636
postcss: '>=8.5.18'
37+
source-map-js: '>=1.2.2'
3738
# Avoid the sourceMappingURL file-read advisory (GHSA-4x5r-pxfx-6jf8) in
3839
# @babel/core <=7.29.0; reached via next>styled-jsx in the realtime example.
3940
# (No 7.29.1 was ever published; 7.29.7 is the first fixed 7.x release.)
@@ -52,6 +53,7 @@ minimumReleaseAge: 10080 # 7 days
5253
minimumReleaseAgeExclude:
5354
- '@supabase/*'
5455
- supabase # first-party Supabase CLI
56+
- source-map-js@1.2.2
5557
blockExoticSubdeps: true
5658

5759
# Whitelist of packages allowed to run install scripts (preinstall/install/postinstall).

0 commit comments

Comments
 (0)