diff --git a/.changes/wait-server-stops.md b/.changes/wait-server-stops.md new file mode 100644 index 00000000..967d6841 --- /dev/null +++ b/.changes/wait-server-stops.md @@ -0,0 +1,5 @@ +--- +"@simulacrum/server": patch +--- + +Fix #127. Wait until simulation server is fully stopped in `destroySimulation` request diff --git a/packages/server/src/effect.ts b/packages/server/src/effect.ts index 2e1b0aba..dc7469cb 100644 --- a/packages/server/src/effect.ts +++ b/packages/server/src/effect.ts @@ -9,7 +9,7 @@ export function map(slice: Slice>, effect: Effect): Oper return function* (scope) { let effects = new Map(); - function synchronize(record: Record) { + function* synchronize(record: Record) { let keep = new Set(); // the checks for non-null `record` and `value` @@ -31,12 +31,12 @@ export function map(slice: Slice>, effect: Effect): Oper for (let [key, effect] of effects.entries()) { if (!keep.has(key)) { effects.delete(key); - effect.halt(); + yield effect.halt(); } } } - synchronize(slice.get()); + yield synchronize(slice.get()); yield slice.forEach(synchronize); }; diff --git a/packages/server/src/http.ts b/packages/server/src/http.ts index da9316aa..2a34c492 100644 --- a/packages/server/src/http.ts +++ b/packages/server/src/http.ts @@ -1,7 +1,7 @@ import type { ServerOptions as SSLOptions } from 'https'; import type { AddressInfo } from 'net'; import type { Service } from './interfaces'; -import { Operation, once, Resource, spawn, label } from 'effection'; +import { Operation, once, Resource, spawn, label, createFuture } from 'effection'; import { Request, Response, Application, RequestHandler } from 'express'; import { createServer as createHttpsServer } from 'https'; import { Server as HTTPServer, createServer as createHttpServer } from 'http'; @@ -63,7 +63,9 @@ export function createServer(app: Application, options: ServerOptions): Resource try { yield; } finally { - server.close(); + let { future, resolve, reject } = createFuture(); + server.close((err) => err ? reject(err) : resolve()); + yield future; } }); diff --git a/packages/server/src/interfaces.ts b/packages/server/src/interfaces.ts index 4cdf1d17..eb809e5c 100644 --- a/packages/server/src/interfaces.ts +++ b/packages/server/src/interfaces.ts @@ -84,6 +84,26 @@ export type SimulationState = services: []; store: StoreState; error: Error + } | + { + id: string; + status: 'destroying'; + simulator: string; + options: SimulationOptions; + scenarios: Record; + services: []; + store: StoreState; + error: Error; + } | + { + id: string; + status: 'halted'; + simulator: string; + options: SimulationOptions; + scenarios: Record; + services: []; + store: StoreState; + error: Error; } export type ScenarioState = diff --git a/packages/server/src/schema/resolvers.ts b/packages/server/src/schema/resolvers.ts index 37386726..d63b9c10 100644 --- a/packages/server/src/schema/resolvers.ts +++ b/packages/server/src/schema/resolvers.ts @@ -48,10 +48,12 @@ export const createSimulation: Resolver = { - async resolve({ id }, { atom }) { +export const destroySimulation: Resolver<{ id: string }, boolean> = { + async resolve({ id }, { atom, scope }) { let simulation = atom.slice("simulations", id); if (simulation.get()) { + simulation.slice("status").set("destroying"); + await scope.run(simulation.filter(({ status }) => status == "halted").expect()); simulation.remove(); return true; } else { diff --git a/packages/server/src/simulation.ts b/packages/server/src/simulation.ts index 489412e7..069c29fe 100644 --- a/packages/server/src/simulation.ts +++ b/packages/server/src/simulation.ts @@ -1,4 +1,5 @@ import { spawn, label } from 'effection'; +import { Slice } from '@effection/atom'; import { assert } from 'assert-ts'; import { Effect, map } from './effect'; import express, { raw } from 'express'; @@ -7,8 +8,8 @@ import { createServer, Server } from './http'; import { createFaker } from './faker'; import { middlewareHandlerIsOperation, isRequestHandler } from './guards/guards'; -export function simulation(simulators: Record): Effect { - return slice => function*(scope) { +function createSimulation (slice: Slice, simulators: Record) { + return spawn(function* (scope) { let simulatorName = slice.get().simulator; yield label({ name: 'simulation', simulator: simulatorName }); try { @@ -127,6 +128,14 @@ export function simulation(simulators: Record): Effect): Effect { + return function* (slice) { + let simulationTask = yield createSimulation(slice, simulators); + yield slice.filter(({ status }) => status == "destroying").expect(); + yield simulationTask.halt(); + slice.slice("status").set("halted"); }; }