worker fixes (#4059)

Describe what your pull request does. If you can, add GIFs or images
showing the before and after of your change.

### Change type

- [x] `bugfix`
- [ ] `improvement`
- [ ] `feature`
- [ ] `api`
- [ ] `other`

### Test plan

1. Create a shape...
2.

- [ ] Unit tests
- [ ] End to end tests

### Release notes

- Fixed a bug with...

---------

Co-authored-by: alex <alex@dytry.ch>
This commit is contained in:
David Sheldrick 2024-07-02 11:53:27 +01:00 committed by GitHub
parent f66763371c
commit adb84d97e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 97 additions and 17 deletions

View file

@ -8,7 +8,7 @@ export class BemoDO extends DurableObject<Environment> {
const sentry = createSentry(this.ctx, this.env, request) const sentry = createSentry(this.ctx, this.env, request)
console.error(e) console.error(e)
// eslint-disable-next-line deprecation/deprecation // eslint-disable-next-line deprecation/deprecation
sentry.captureException(e) sentry?.captureException(e)
} }
private readonly router = Router() private readonly router = Router()

View file

@ -27,7 +27,7 @@ export default class Worker extends WorkerEntrypoint<Environment> {
const sentry = createSentry(this.ctx, this.env, request) const sentry = createSentry(this.ctx, this.env, request)
console.error(error) console.error(error)
// eslint-disable-next-line deprecation/deprecation // eslint-disable-next-line deprecation/deprecation
sentry.captureException(error) sentry?.captureException(error)
return new Response('Something went wrong', { return new Response('Something went wrong', {
status: 500, status: 500,
statusText: 'Internal Server Error', statusText: 'Internal Server Error',

View file

@ -9,7 +9,7 @@
}, },
"main": "src/index.ts", "main": "src/index.ts",
"scripts": { "scripts": {
"dev": "cross-env NODE_ENV=development wrangler dev --log-level info --persist-to tmp-assets", "dev": "yarn run -T tsx ../../scripts/workers/dev.ts --persist-to tmp-assets",
"test-ci": "lazy inherit --passWithNoTests", "test-ci": "lazy inherit --passWithNoTests",
"test": "yarn run -T jest --passWithNoTests", "test": "yarn run -T jest --passWithNoTests",
"test-coverage": "lazy inherit --passWithNoTests", "test-coverage": "lazy inherit --passWithNoTests",

View file

@ -1,4 +1,4 @@
main = "src/index.ts" main = "src/worker.ts"
compatibility_date = "2022-09-22" compatibility_date = "2022-09-22"
[dev] [dev]
@ -34,7 +34,7 @@ preview_bucket_name = 'uploads-preview'
binding = 'UPLOADS' binding = 'UPLOADS'
bucket_name = 'uploads-preview' bucket_name = 'uploads-preview'
[[env.stating.r2_buckets]] [[env.staging.r2_buckets]]
binding = 'UPLOADS' binding = 'UPLOADS'
bucket_name = 'uploads-preview' bucket_name = 'uploads-preview'

View file

@ -222,7 +222,7 @@ export class TLDrawDurableObject {
} catch (err) { } catch (err) {
console.error(err) console.error(err)
// eslint-disable-next-line deprecation/deprecation // eslint-disable-next-line deprecation/deprecation
sentry.captureException(err) sentry?.captureException(err)
return new Response('Something went wrong', { return new Response('Something went wrong', {
status: 500, status: 500,
statusText: 'Internal Server Error', statusText: 'Internal Server Error',

View file

@ -62,7 +62,7 @@ const Worker = {
.catch((err) => { .catch((err) => {
console.error(err) console.error(err)
// eslint-disable-next-line deprecation/deprecation // eslint-disable-next-line deprecation/deprecation
sentry.captureException(err) sentry?.captureException(err)
return new Response('Something went wrong', { return new Response('Something went wrong', {
status: 500, status: 500,

View file

@ -9,11 +9,17 @@ interface Context {
interface SentryEnvironment { interface SentryEnvironment {
readonly SENTRY_DSN: string | undefined readonly SENTRY_DSN: string | undefined
readonly TLDRAW_ENV?: string | undefined
readonly WORKER_NAME: string | undefined readonly WORKER_NAME: string | undefined
readonly CF_VERSION_METADATA: WorkerVersionMetadata readonly CF_VERSION_METADATA: WorkerVersionMetadata
} }
export function createSentry(ctx: Context, env: SentryEnvironment, request?: Request) { export function createSentry(ctx: Context, env: SentryEnvironment, request?: Request) {
// TLDRAW_ENV is undefined in dev
if (!env.SENTRY_DSN && !env.TLDRAW_ENV) {
return null
}
const { SENTRY_DSN, WORKER_NAME, CF_VERSION_METADATA } = requiredEnv(env, { const { SENTRY_DSN, WORKER_NAME, CF_VERSION_METADATA } = requiredEnv(env, {
SENTRY_DSN: true, SENTRY_DSN: true,
WORKER_NAME: true, WORKER_NAME: true,

View file

@ -55,9 +55,11 @@
"dependencies": { "dependencies": {
"@octokit/rest": "^20.0.2", "@octokit/rest": "^20.0.2",
"@types/minimist": "^1.2.5", "@types/minimist": "^1.2.5",
"@types/proper-lockfile": "^4.1.4",
"@types/tmp": "^0.2.6", "@types/tmp": "^0.2.6",
"ignore": "^5.2.4", "ignore": "^5.2.4",
"minimist": "^1.2.8", "minimist": "^1.2.8",
"proper-lockfile": "^4.1.2",
"tar": "^7.0.1", "tar": "^7.0.1",
"tmp": "^0.2.3", "tmp": "^0.2.3",
"toml": "^3.0.0" "toml": "^3.0.0"

View file

@ -1,11 +1,20 @@
import { ChildProcessWithoutNullStreams, spawn } from 'child_process' import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
import kleur from 'kleur' import kleur from 'kleur'
import { lock } from 'proper-lockfile'
import stripAnsi from 'strip-ansi' import stripAnsi from 'strip-ansi'
// at the time of writing, workerd will regularly crash with a segfault const lockfileName = __dirname
// but the error is not caught by the process, so it will just hang
// this script wraps the process, tailing the logs and restarting the process /**
// if we encounter the string 'Segmentation fault' * a long time ago, workerd would regularly crash with a segfault but the error is not caught by the
* process, so it will just hang. this script wraps the process, tailing the logs and restarting the
* process if we encounter the string 'Segmentation fault'. we think this error is probably fixed
* now.
*
* there's a separate issue where spawning multiple workerd instances at once would cause them to
* fight and crash. we use a lockfile to start our wrokerd instances one at a time, waiting for the
* previous one to be ready before we start the next.
*/
class MiniflareMonitor { class MiniflareMonitor {
private process: ChildProcessWithoutNullStreams | null = null private process: ChildProcessWithoutNullStreams | null = null
@ -14,9 +23,10 @@ class MiniflareMonitor {
private args: string[] = [] private args: string[] = []
) {} ) {}
public start(): void { public async start(): Promise<void> {
this.stop() // Ensure any existing process is stopped await this.stop() // Ensure any existing process is stopped
console.log(`Starting wrangler...`) await this.lock()
await console.log(`Starting wrangler...`)
this.process = spawn(this.command, this.args, { this.process = spawn(this.command, this.args, {
env: { env: {
NODE_ENV: 'development', NODE_ENV: 'development',
@ -35,6 +45,11 @@ class MiniflareMonitor {
private handleOutput(output: string, err = false): void { private handleOutput(output: string, err = false): void {
if (!output) return if (!output) return
if (output.includes('Ready on') && this.isLocked()) {
this.release()
}
if (output.includes('Segmentation fault')) { if (output.includes('Segmentation fault')) {
console.error('Segmentation fault detected. Restarting Miniflare...') console.error('Segmentation fault detected. Restarting Miniflare...')
this.restart() this.restart()
@ -43,18 +58,45 @@ class MiniflareMonitor {
} }
} }
private restart(): void { private async restart(): Promise<void> {
console.log('Restarting wrangler...') console.log('Restarting wrangler...')
this.stop() await this.stop()
setTimeout(() => this.start(), 3000) // Restart after a short delay setTimeout(() => this.start(), 3000) // Restart after a short delay
} }
private stop(): void { private async stop(): Promise<void> {
if (this.isLocked()) await this.release()
if (this.process) { if (this.process) {
this.process.kill() this.process.kill()
this.process = null this.process = null
} }
} }
private _lockPromise?: Promise<() => Promise<void>>
private isLocked() {
return !!this._lockPromise
}
private async lock() {
if (this.isLocked()) throw new Error('Already locked')
console.log('Locking...')
this._lockPromise = lock(lockfileName, {
retries: {
minTimeout: 500,
retries: 10,
},
})
await this._lockPromise
console.log('Locked')
}
private async release() {
if (!this.isLocked()) throw new Error('Not locked')
console.log('Releasing...')
const lockPromise = this._lockPromise!
this._lockPromise = undefined
const release = await lockPromise
await release()
console.log('Released')
}
} }
class SizeReporter { class SizeReporter {
@ -119,6 +161,7 @@ new MiniflareMonitor('wrangler', [
'info', 'info',
'--var', '--var',
'IS_LOCAL:true', 'IS_LOCAL:true',
...process.argv.slice(2),
]).start() ]).start()
new SizeReporter().start() new SizeReporter().start()

View file

@ -6196,6 +6196,7 @@ __metadata:
"@types/is-ci": "npm:^3.0.0" "@types/is-ci": "npm:^3.0.0"
"@types/minimist": "npm:^1.2.5" "@types/minimist": "npm:^1.2.5"
"@types/node": "npm:~20.11" "@types/node": "npm:~20.11"
"@types/proper-lockfile": "npm:^4.1.4"
"@types/tmp": "npm:^0.2.6" "@types/tmp": "npm:^0.2.6"
"@typescript-eslint/utils": "npm:^5.59.0" "@typescript-eslint/utils": "npm:^5.59.0"
ast-types: "npm:^0.14.2" ast-types: "npm:^0.14.2"
@ -6210,6 +6211,7 @@ __metadata:
lazyrepo: "npm:0.0.0-alpha.27" lazyrepo: "npm:0.0.0-alpha.27"
minimist: "npm:^1.2.8" minimist: "npm:^1.2.8"
prettier: "npm:^3.0.3" prettier: "npm:^3.0.3"
proper-lockfile: "npm:^4.1.2"
recast: "npm:^0.22.0" recast: "npm:^0.22.0"
rimraf: "npm:^4.4.0" rimraf: "npm:^4.4.0"
semver: "npm:^7.3.8" semver: "npm:^7.3.8"
@ -6936,6 +6938,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/proper-lockfile@npm:^4.1.4":
version: 4.1.4
resolution: "@types/proper-lockfile@npm:4.1.4"
dependencies:
"@types/retry": "npm:*"
checksum: b0d1b8e84a563b2c5f869f7ff7542b1d83dec03d1c9d980847cbb189865f44b4a854673cdde59767e41bcb8c31932e613ac43822d358a6f8eede6b79ccfceb1d
languageName: node
linkType: hard
"@types/qrcode@npm:^1.5.0": "@types/qrcode@npm:^1.5.0":
version: 1.5.5 version: 1.5.5
resolution: "@types/qrcode@npm:1.5.5" resolution: "@types/qrcode@npm:1.5.5"
@ -7004,6 +7015,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/retry@npm:*":
version: 0.12.5
resolution: "@types/retry@npm:0.12.5"
checksum: 3fb6bf91835ca0eb2987567d6977585235a7567f8aeb38b34a8bb7bbee57ac050ed6f04b9998cda29701b8c893f5dfe315869bc54ac17e536c9235637fe351a2
languageName: node
linkType: hard
"@types/scheduler@npm:*": "@types/scheduler@npm:*":
version: 0.16.8 version: 0.16.8
resolution: "@types/scheduler@npm:0.16.8" resolution: "@types/scheduler@npm:0.16.8"
@ -19011,6 +19029,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"proper-lockfile@npm:^4.1.2":
version: 4.1.2
resolution: "proper-lockfile@npm:4.1.2"
dependencies:
graceful-fs: "npm:^4.2.4"
retry: "npm:^0.12.0"
signal-exit: "npm:^3.0.2"
checksum: 000a4875f543f591872b36ca94531af8a6463ddb0174f41c0b004d19e231d7445268b422ff1ea595e43d238655c702250cd3d27f408e7b9d97b56f1533ba26bf
languageName: node
linkType: hard
"property-information@npm:^6.0.0": "property-information@npm:^6.0.0":
version: 6.4.0 version: 6.4.0
resolution: "property-information@npm:6.4.0" resolution: "property-information@npm:6.4.0"