Skip to content

Commit 35e9c35

Browse files
committed
fix(migrate): don't kill the SE process on Windows immediately
Windows doesn't have signals, so calling `this.child?.kill()` immediately terminates the child process without running the graceful shutdown code. We should trigger the graceful shutdown by closing stdin instead. Depends on <prisma/prisma-engines#5486>. Closes: https://linear.app/prisma-company/issue/ORM-1093/schema-engine-termination-on-windows
1 parent 4c2b06f commit 35e9c35

16 files changed

Lines changed: 93 additions & 67 deletions

packages/migrate/src/Migrate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ export class Migrate {
6262
return new Migrate({ engine, schemaContext, ...rest })
6363
}
6464

65-
public stop(): void {
66-
this.engine.stop()
65+
public async stop(): Promise<void> {
66+
await this.engine.stop()
6767
}
6868

6969
public getPrismaSchema(): MigrateTypes.SchemasContainer {

packages/migrate/src/SchemaEngine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,5 +125,5 @@ export interface SchemaEngine {
125125
/**
126126
* Stop the engine.
127127
*/
128-
stop(): void
128+
stop(): Promise<void>
129129
}

packages/migrate/src/SchemaEngineCLI.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export class SchemaEngineCLI implements SchemaEngine {
201201
} finally {
202202
// stop the engine after either a successful or failed introspection, to emulate how the
203203
// introspection engine used to work.
204-
this.stop()
204+
await this.stop()
205205
}
206206
}
207207

@@ -263,12 +263,37 @@ export class SchemaEngineCLI implements SchemaEngine {
263263
return this.runCommand(this.getRPCPayload('introspectSql', args))
264264
}
265265

266-
public stop(): void {
267-
if (this.child) {
268-
this.child.stdin?.end()
269-
this.child.kill()
270-
this.isRunning = false
266+
public async stop(): Promise<void> {
267+
if (!this.child) {
268+
return
271269
}
270+
271+
this.isRunning = false
272+
273+
// Close stdin to tell the schema engine to stop the RPC server and exit.
274+
this.child.stdin?.end()
275+
276+
// Terminate the child process after a timeout if it's still running. On
277+
// Unix, this will send a SIGTERM signal to the process, which the schema
278+
// engine will handle and initiate a graceful shutdown. Since it has its
279+
// own timeout mechanism, we shouldn't wait before sending the signal,
280+
// otherwise these timeouts will add up. On Windows, there are no signals
281+
// and the kill method will terminate the process immediately, similar to
282+
// SIGKILL on Unix, so we should wait to give the schema engine a chance
283+
// to gracefully shut down in response to EOF in stdin.
284+
const timer = setTimeout(
285+
() => {
286+
this.child?.kill()
287+
},
288+
process.platform === 'win32' ? 4000 : 0,
289+
).unref()
290+
291+
return new Promise((resolve) => {
292+
this.child!.on('exit', () => {
293+
clearTimeout(timer)
294+
resolve()
295+
})
296+
})
272297
}
273298

274299
private rejectAll(err: any): void {

packages/migrate/src/SchemaEngineWasm.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,10 @@ export class SchemaEngineWasm implements SchemaEngine {
300300
/**
301301
* Stop the engine.
302302
*/
303-
public stop(): void {
303+
public stop(): Promise<void> {
304304
this.isRunning = false
305305
this.engine.free()
306+
return Promise.resolve()
306307
}
307308
}
308309

packages/migrate/src/__tests__/DbPull/postgresql-views.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ describeMatrix(postgresOnly, 'postgresql-views', () => {
8383
})
8484

8585
expect(introspectionResult.views).toEqual(null)
86-
engine.stop()
86+
await engine.stop()
8787
})
8888
})
8989

@@ -105,7 +105,7 @@ describeMatrix(postgresOnly, 'postgresql-views', () => {
105105
})
106106

107107
expect(introspectionResult.views).toEqual([])
108-
engine.stop()
108+
await engine.stop()
109109

110110
const listWithoutViews = await ctx.fs.listAsync('views')
111111
expect(listWithoutViews).toEqual(undefined)
@@ -132,7 +132,7 @@ describeMatrix(postgresOnly, 'postgresql-views', () => {
132132
})
133133

134134
expect(introspectionResult.views).toEqual([])
135-
engine.stop()
135+
await engine.stop()
136136

137137
const listWithoutViews = await ctx.fs.listAsync('views')
138138
expect(listWithoutViews).toEqual(undefined)
@@ -159,7 +159,7 @@ describeMatrix(postgresOnly, 'postgresql-views', () => {
159159
})
160160

161161
expect(introspectionResult.views).toEqual([])
162-
engine.stop()
162+
await engine.stop()
163163

164164
const listWithoutViews = await ctx.fs.listAsync('views')
165165
expect(listWithoutViews).toEqual(['README.md'])

packages/migrate/src/__tests__/rpc.test.ts

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ describe('applyMigrations', () => {
3030
"appliedMigrationNames": [],
3131
}
3232
`)
33-
migrate.stop()
33+
await migrate.stop()
3434
})
3535

3636
it('should fail on existing brownfield db', async () => {
@@ -49,7 +49,7 @@ describe('applyMigrations', () => {
4949
The database schema is not empty. Read more about how to baseline an existing production database: https://pris.ly/d/migrate-baseline
5050
"
5151
`)
52-
migrate.stop()
52+
await migrate.stop()
5353
})
5454
})
5555

@@ -69,7 +69,7 @@ describe('createDatabase', () => {
6969
"databaseName": "dev.db",
7070
}
7171
`)
72-
migrate.stop()
72+
await migrate.stop()
7373
})
7474

7575
it('should succeed - Schema - postgresql', async () => {
@@ -89,7 +89,7 @@ describe('createDatabase', () => {
8989
Database \`tests\` already exists on the database server
9090
"
9191
`)
92-
migrate.stop()
92+
await migrate.stop()
9393
})
9494
})
9595

@@ -110,7 +110,7 @@ describe('createMigration', () => {
110110
"generatedMigrationName": "20201231000000_my_migration",
111111
}
112112
`)
113-
migrate.stop()
113+
await migrate.stop()
114114
})
115115

116116
it('draft should succeed - existing-db-1-migration', async () => {
@@ -129,7 +129,7 @@ describe('createMigration', () => {
129129
"generatedMigrationName": "20201231000000_draft_123",
130130
}
131131
`)
132-
migrate.stop()
132+
await migrate.stop()
133133
})
134134
})
135135

@@ -148,7 +148,7 @@ describe('dbExecute', () => {
148148
})
149149

150150
await expect(result).resolves.toMatchInlineSnapshot(`null`)
151-
migrate.stop()
151+
await migrate.stop()
152152
})
153153
})
154154

@@ -170,7 +170,7 @@ describe('devDiagnostic', () => {
170170
}
171171
`)
172172

173-
migrate.stop()
173+
await migrate.stop()
174174
})
175175

176176
it('reset because drift', async () => {
@@ -203,7 +203,7 @@ describe('devDiagnostic', () => {
203203
}
204204
`)
205205

206-
migrate.stop()
206+
await migrate.stop()
207207
})
208208
})
209209

@@ -227,7 +227,7 @@ describe('diagnoseMigrationHistory', () => {
227227
"history": null,
228228
}
229229
`)
230-
migrate.stop()
230+
await migrate.stop()
231231
})
232232

233233
it(' optInToShadowDatabase false should succeed - existing-db-1-migration', async () => {
@@ -249,7 +249,7 @@ describe('diagnoseMigrationHistory', () => {
249249
"history": null,
250250
}
251251
`)
252-
migrate.stop()
252+
await migrate.stop()
253253
})
254254
})
255255

@@ -266,7 +266,7 @@ describe('ensureConnectionValidity', () => {
266266
})
267267

268268
await expect(result).resolves.toMatchInlineSnapshot(`{}`)
269-
migrate.stop()
269+
await migrate.stop()
270270
})
271271

272272
it('should succeed when database exists - PostgreSQL', async () => {
@@ -282,7 +282,7 @@ describe('ensureConnectionValidity', () => {
282282
})
283283

284284
await expect(result).resolves.toMatchInlineSnapshot(`{}`)
285-
migrate.stop()
285+
await migrate.stop()
286286
})
287287

288288
it('should fail when database does not exist - SQLite', async () => {
@@ -301,7 +301,7 @@ describe('ensureConnectionValidity', () => {
301301
Database \`dev.db\` does not exist
302302
"
303303
`)
304-
migrate.stop()
304+
await migrate.stop()
305305
})
306306

307307
it('should fail when server does not exist - PostgreSQL', async () => {
@@ -322,7 +322,7 @@ describe('ensureConnectionValidity', () => {
322322
Please make sure your database server is running at \`server-does-not-exist:5432\`.
323323
"
324324
`)
325-
migrate.stop()
325+
await migrate.stop()
326326
// It was flaky on CI (but rare)
327327
// higher timeout might help
328328
}, 10_000)
@@ -348,7 +348,7 @@ describe('evaluateDataLoss', () => {
348348
"warnings": [],
349349
}
350350
`)
351-
migrate.stop()
351+
await migrate.stop()
352352
})
353353

354354
// migration is already applied so should be empty
@@ -370,7 +370,7 @@ describe('evaluateDataLoss', () => {
370370
"warnings": [],
371371
}
372372
`)
373-
migrate.stop()
373+
await migrate.stop()
374374
})
375375
})
376376

@@ -382,7 +382,7 @@ describe('getDatabaseVersion - PostgreSQL', () => {
382382
const migrate = await Migrate.setup({ migrationsDirPath, schemaContext })
383383
const result = migrate.engine.getDatabaseVersion()
384384
await expect(result).resolves.toContain('PostgreSQL')
385-
migrate.stop()
385+
await migrate.stop()
386386
})
387387

388388
it('[SchemaPath] should succeed', async () => {
@@ -396,7 +396,7 @@ describe('getDatabaseVersion - PostgreSQL', () => {
396396
},
397397
})
398398
await expect(result).resolves.toContain('PostgreSQL')
399-
migrate.stop()
399+
await migrate.stop()
400400
})
401401

402402
it('[ConnectionString] should succeed', async () => {
@@ -408,7 +408,7 @@ describe('getDatabaseVersion - PostgreSQL', () => {
408408
},
409409
})
410410
await expect(result).resolves.toContain('PostgreSQL')
411-
migrate.stop()
411+
await migrate.stop()
412412
})
413413
})
414414

@@ -432,7 +432,7 @@ describe('markMigrationRolledBack', () => {
432432
"
433433
`)
434434

435-
migrate.stop()
435+
await migrate.stop()
436436
})
437437

438438
it('existing-db-1-migration', async () => {
@@ -493,7 +493,7 @@ describe('markMigrationRolledBack', () => {
493493
"
494494
`)
495495

496-
migrate.stop()
496+
await migrate.stop()
497497
})
498498
})
499499

@@ -523,7 +523,7 @@ describe('markMigrationApplied', () => {
523523

524524
await expect(resultMarkApplied).resolves.toMatchInlineSnapshot(`{}`)
525525

526-
migrate.stop()
526+
await migrate.stop()
527527
})
528528
})
529529

@@ -547,7 +547,7 @@ describe('schemaPush', () => {
547547
"warnings": [],
548548
}
549549
`)
550-
migrate.stop()
550+
await migrate.stop()
551551
})
552552

553553
it('should succeed without warning', async () => {
@@ -567,7 +567,7 @@ describe('schemaPush', () => {
567567
"warnings": [],
568568
}
569569
`)
570-
migrate.stop()
570+
await migrate.stop()
571571
})
572572

573573
it('should return executedSteps 0 with warning if dataloss detected', async () => {
@@ -589,7 +589,7 @@ describe('schemaPush', () => {
589589
],
590590
}
591591
`)
592-
migrate.stop()
592+
await migrate.stop()
593593
})
594594

595595
it('force should accept dataloss', async () => {
@@ -611,7 +611,7 @@ describe('schemaPush', () => {
611611
],
612612
}
613613
`)
614-
migrate.stop()
614+
await migrate.stop()
615615
})
616616
})
617617

packages/migrate/src/commands/DbExecute.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ See \`${green(getCommandWithExecutor('prisma db execute -h'))}\``,
207207
datasourceType,
208208
})
209209
} finally {
210-
migrate.stop()
210+
await migrate.stop()
211211
}
212212

213213
return `Script executed successfully.`

0 commit comments

Comments
 (0)