@contember/engine-actions 2.2.0-alpha.4 → 2.2.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/development/index.cjs +0 -1
- package/dist/development/index.cjs.map +1 -1
- package/dist/development/index.js +0 -1
- package/dist/development/index.js.map +1 -1
- package/dist/development/src/graphql/http/ActionsWebsocketControllerFactory.cjs +68 -28
- package/dist/development/src/graphql/http/ActionsWebsocketControllerFactory.cjs.map +1 -1
- package/dist/development/src/graphql/http/ActionsWebsocketControllerFactory.js +68 -28
- package/dist/development/src/graphql/http/ActionsWebsocketControllerFactory.js.map +1 -1
- package/dist/production/index.cjs +0 -1
- package/dist/production/index.cjs.map +1 -1
- package/dist/production/index.js +0 -1
- package/dist/production/index.js.map +1 -1
- package/dist/production/src/graphql/http/ActionsWebsocketControllerFactory.cjs +68 -28
- package/dist/production/src/graphql/http/ActionsWebsocketControllerFactory.cjs.map +1 -1
- package/dist/production/src/graphql/http/ActionsWebsocketControllerFactory.js +68 -28
- package/dist/production/src/graphql/http/ActionsWebsocketControllerFactory.js.map +1 -1
- package/dist/tests/cases/unit/actionsWebsocketController.test.d.ts +2 -0
- package/dist/tests/cases/unit/actionsWebsocketController.test.d.ts.map +1 -0
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/dist/types/graphql/http/ActionsWebsocketControllerFactory.d.ts +7 -5
- package/dist/types/graphql/http/ActionsWebsocketControllerFactory.d.ts.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -16
- package/src/graphql/http/ActionsWebsocketControllerFactory.ts +73 -30
- package/src/index.ts +0 -1
- package/tests/cases/unit/actionsWebsocketController.test.ts +270 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../packages/engine-actions/src/index.ts"],"sourcesContent":["import { Plugin } from '@contember/engine-plugins'\nimport { migrationsGroup } from './migrations/index.js'\nimport { ActionsExecutionContainerHookFactory } from './ActionsExecutionContainerHookFactory.js'\nimport { ListenerStoreProvider } from './ListenerStoreProvider.js'\nimport { MigrationGroup } from '@contember/database-migrations'\nimport { MasterContainerBuilder, MasterContainerHook } from '@contember/engine-http'\nimport { ActionsApiMiddlewareFactory } from './graphql/http/ActionsApiMiddlewareFactory.js'\nimport { ActionsGraphQLHandlerFactory } from './graphql/http/ActionsGraphQLHandlerFactory.js'\nimport { ResolversFactory } from './graphql/resolvers/ResolversFactory.js'\nimport {\n\tEventsQueryResolver,\n\tProcessBatchMutationResolver,\n\tRetryEventMutationResolver,\n\tSetVariablesMutationResolver,\n\tStopEventMutationResolver,\n} from './graphql/resolvers/index.js'\nimport { EventDispatcher } from './dispatch/EventDispatcher.js'\nimport { EventsRepository } from './dispatch/EventsRepository.js'\nimport { TargetHandlerResolver } from './dispatch/TargetHandlerResolver.js'\nimport { WebhookTargetHandler } from './dispatch/WebhookTargetHandler.js'\nimport { ActionsContextResolver } from './graphql/http/ActionsContextResolver.js'\nimport { ActionsWebsocketControllerFactory } from './graphql/http/ActionsWebsocketControllerFactory.js'\nimport { DispatchWorkerSupervisorFactory } from './dispatch/DispatchWorkerSupervisor.js'\nimport { LazyDispatchWorker } from './dispatch/LazyDispatchWorker.js'\nimport { ProjectDispatcherFactory } from './dispatch/ProjectDispatcher.js'\nimport { VariablesQueryResolver } from './graphql/resolvers/query/VariablesQueryResolver.js'\nimport { VariablesManager } from './model/VariablesManager.js'\nimport { AccessEvaluator, Authorizator } from '@contember/authorization'\nimport { ActionsPermissionsFactory } from './authorization/index.js'\nimport { WebhookFetcherNative } from './dispatch/WebhookFetcher.js'\nimport { ActionsMetrics } from './ActionsMetrics.js'\n\nexport {\n\tTriggerHandler,\n\tTriggerIndirectChangesFetcher,\n\tTriggerListenersFactory,\n\tTriggerListenersStore,\n\tTriggerPayloadManager,\n} from './triggers/index.js'\nexport { ActionsExecutionContainerHookFactory } from './ActionsExecutionContainerHookFactory.js'\nexport { ListenerStoreProvider } from './ListenerStoreProvider.js'\n\nexport default class ActionsPlugin implements Plugin {\n\tname = 'contember/actions'\n\n\t/**\n\t * Metrics singleton, created in {@link getMasterContainerHook} (where the Prometheus registry is\n\t * available) and read back here in {@link getExecutionContainerHook}, which runs in the per-request\n\t * content container with no access to master-level services.\n\t */\n\tprivate actionsMetrics: ActionsMetrics | undefined\n\n\tgetSystemMigrations(): MigrationGroup {\n\t\treturn migrationsGroup\n\t}\n\n\tgetExecutionContainerHook() {\n\t\tconst hookFactory = new ActionsExecutionContainerHookFactory(new ListenerStoreProvider(), () => this.actionsMetrics)\n\t\treturn hookFactory.create()\n\t}\n\n\tgetMasterContainerHook() {\n\t\tconst hook: MasterContainerHook = (builder: MasterContainerBuilder) => {\n\t\t\treturn builder\n\t\t\t\t.addService('actions_metrics', ({ promRegistry }) => {\n\t\t\t\t\tconst metrics = new ActionsMetrics(promRegistry)\n\t\t\t\t\t// expose to getExecutionContainerHook (runs in a different container scope)\n\t\t\t\t\tthis.actionsMetrics = metrics\n\t\t\t\t\treturn metrics\n\t\t\t\t})\n\t\t\t\t.addService('actions_variableManager', ({ env }) => {\n\t\t\t\t\treturn new VariablesManager(env)\n\t\t\t\t})\n\t\t\t\t.addService('actions_eventRepository', () => {\n\t\t\t\t\treturn new EventsRepository()\n\t\t\t\t})\n\t\t\t\t.addService('actions_eventDispatcher', ({ actions_eventRepository, actions_variableManager }) => {\n\t\t\t\t\tconst webhookTargetHandler = new WebhookTargetHandler(new WebhookFetcherNative())\n\t\t\t\t\tconst targetHandlerResolver = new TargetHandlerResolver(webhookTargetHandler)\n\t\t\t\t\treturn new EventDispatcher(actions_eventRepository, actions_variableManager, targetHandlerResolver)\n\t\t\t\t})\n\t\t\t\t.addService('actions_dispatchWorkerSupervisorFactory', ({ actions_eventDispatcher, actions_metrics }) => {\n\t\t\t\t\tconst projectDispatcherFactory = new ProjectDispatcherFactory(actions_eventDispatcher, actions_metrics)\n\t\t\t\t\treturn new DispatchWorkerSupervisorFactory(projectDispatcherFactory)\n\t\t\t\t})\n\t\t\t\t.addService('actions_authorizator', () => {\n\t\t\t\t\treturn new Authorizator.Default(new AccessEvaluator.PermissionEvaluator(new ActionsPermissionsFactory().create()))\n\t\t\t\t})\n\t\t\t\t.setupService(\n\t\t\t\t\t'application',\n\t\t\t\t\t(\n\t\t\t\t\t\tit,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tprojectContextResolver,\n\t\t\t\t\t\t\tdebugMode,\n\t\t\t\t\t\t\tactions_eventRepository,\n\t\t\t\t\t\t\tactions_eventDispatcher,\n\t\t\t\t\t\t\tactions_dispatchWorkerSupervisorFactory,\n\t\t\t\t\t\t\tactions_variableManager,\n\t\t\t\t\t\t\tactions_authorizator,\n\t\t\t\t\t\t\tpanelApiMount,\n\t\t\t\t\t\t},\n\t\t\t\t\t) => {\n\t\t\t\t\t\tconst handlerFactory = new ActionsGraphQLHandlerFactory()\n\t\t\t\t\t\tconst eventsQueryResolver = new EventsQueryResolver()\n\t\t\t\t\t\tconst processBatchMutationResolver = new ProcessBatchMutationResolver(actions_eventDispatcher)\n\t\t\t\t\t\tconst variablesQueryResolver = new VariablesQueryResolver(actions_variableManager)\n\t\t\t\t\t\tconst setVariablesMutationResolver = new SetVariablesMutationResolver(actions_variableManager)\n\t\t\t\t\t\tconst resolversFactory = new ResolversFactory(\n\t\t\t\t\t\t\teventsQueryResolver,\n\t\t\t\t\t\t\tprocessBatchMutationResolver,\n\t\t\t\t\t\t\tvariablesQueryResolver,\n\t\t\t\t\t\t\tsetVariablesMutationResolver,\n\t\t\t\t\t\t\tnew RetryEventMutationResolver(actions_eventRepository),\n\t\t\t\t\t\t\tnew StopEventMutationResolver(actions_eventRepository),\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\tconst actionsContextResolver = new ActionsContextResolver(debugMode, projectContextResolver)\n\t\t\t\t\t\tconst actionsMiddlewareFactory = new ActionsApiMiddlewareFactory(\n\t\t\t\t\t\t\tactionsContextResolver,\n\t\t\t\t\t\t\thandlerFactory.create(resolversFactory),\n\t\t\t\t\t\t\tactions_authorizator,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst actionsWebsocketMiddlewareFactory = new ActionsWebsocketControllerFactory(\n\t\t\t\t\t\t\tdebugMode,\n\t\t\t\t\t\t\
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../packages/engine-actions/src/index.ts"],"sourcesContent":["import { Plugin } from '@contember/engine-plugins'\nimport { migrationsGroup } from './migrations/index.js'\nimport { ActionsExecutionContainerHookFactory } from './ActionsExecutionContainerHookFactory.js'\nimport { ListenerStoreProvider } from './ListenerStoreProvider.js'\nimport { MigrationGroup } from '@contember/database-migrations'\nimport { MasterContainerBuilder, MasterContainerHook } from '@contember/engine-http'\nimport { ActionsApiMiddlewareFactory } from './graphql/http/ActionsApiMiddlewareFactory.js'\nimport { ActionsGraphQLHandlerFactory } from './graphql/http/ActionsGraphQLHandlerFactory.js'\nimport { ResolversFactory } from './graphql/resolvers/ResolversFactory.js'\nimport {\n\tEventsQueryResolver,\n\tProcessBatchMutationResolver,\n\tRetryEventMutationResolver,\n\tSetVariablesMutationResolver,\n\tStopEventMutationResolver,\n} from './graphql/resolvers/index.js'\nimport { EventDispatcher } from './dispatch/EventDispatcher.js'\nimport { EventsRepository } from './dispatch/EventsRepository.js'\nimport { TargetHandlerResolver } from './dispatch/TargetHandlerResolver.js'\nimport { WebhookTargetHandler } from './dispatch/WebhookTargetHandler.js'\nimport { ActionsContextResolver } from './graphql/http/ActionsContextResolver.js'\nimport { ActionsWebsocketControllerFactory } from './graphql/http/ActionsWebsocketControllerFactory.js'\nimport { DispatchWorkerSupervisorFactory } from './dispatch/DispatchWorkerSupervisor.js'\nimport { LazyDispatchWorker } from './dispatch/LazyDispatchWorker.js'\nimport { ProjectDispatcherFactory } from './dispatch/ProjectDispatcher.js'\nimport { VariablesQueryResolver } from './graphql/resolvers/query/VariablesQueryResolver.js'\nimport { VariablesManager } from './model/VariablesManager.js'\nimport { AccessEvaluator, Authorizator } from '@contember/authorization'\nimport { ActionsPermissionsFactory } from './authorization/index.js'\nimport { WebhookFetcherNative } from './dispatch/WebhookFetcher.js'\nimport { ActionsMetrics } from './ActionsMetrics.js'\n\nexport {\n\tTriggerHandler,\n\tTriggerIndirectChangesFetcher,\n\tTriggerListenersFactory,\n\tTriggerListenersStore,\n\tTriggerPayloadManager,\n} from './triggers/index.js'\nexport { ActionsExecutionContainerHookFactory } from './ActionsExecutionContainerHookFactory.js'\nexport { ListenerStoreProvider } from './ListenerStoreProvider.js'\n\nexport default class ActionsPlugin implements Plugin {\n\tname = 'contember/actions'\n\n\t/**\n\t * Metrics singleton, created in {@link getMasterContainerHook} (where the Prometheus registry is\n\t * available) and read back here in {@link getExecutionContainerHook}, which runs in the per-request\n\t * content container with no access to master-level services.\n\t */\n\tprivate actionsMetrics: ActionsMetrics | undefined\n\n\tgetSystemMigrations(): MigrationGroup {\n\t\treturn migrationsGroup\n\t}\n\n\tgetExecutionContainerHook() {\n\t\tconst hookFactory = new ActionsExecutionContainerHookFactory(new ListenerStoreProvider(), () => this.actionsMetrics)\n\t\treturn hookFactory.create()\n\t}\n\n\tgetMasterContainerHook() {\n\t\tconst hook: MasterContainerHook = (builder: MasterContainerBuilder) => {\n\t\t\treturn builder\n\t\t\t\t.addService('actions_metrics', ({ promRegistry }) => {\n\t\t\t\t\tconst metrics = new ActionsMetrics(promRegistry)\n\t\t\t\t\t// expose to getExecutionContainerHook (runs in a different container scope)\n\t\t\t\t\tthis.actionsMetrics = metrics\n\t\t\t\t\treturn metrics\n\t\t\t\t})\n\t\t\t\t.addService('actions_variableManager', ({ env }) => {\n\t\t\t\t\treturn new VariablesManager(env)\n\t\t\t\t})\n\t\t\t\t.addService('actions_eventRepository', () => {\n\t\t\t\t\treturn new EventsRepository()\n\t\t\t\t})\n\t\t\t\t.addService('actions_eventDispatcher', ({ actions_eventRepository, actions_variableManager }) => {\n\t\t\t\t\tconst webhookTargetHandler = new WebhookTargetHandler(new WebhookFetcherNative())\n\t\t\t\t\tconst targetHandlerResolver = new TargetHandlerResolver(webhookTargetHandler)\n\t\t\t\t\treturn new EventDispatcher(actions_eventRepository, actions_variableManager, targetHandlerResolver)\n\t\t\t\t})\n\t\t\t\t.addService('actions_dispatchWorkerSupervisorFactory', ({ actions_eventDispatcher, actions_metrics }) => {\n\t\t\t\t\tconst projectDispatcherFactory = new ProjectDispatcherFactory(actions_eventDispatcher, actions_metrics)\n\t\t\t\t\treturn new DispatchWorkerSupervisorFactory(projectDispatcherFactory)\n\t\t\t\t})\n\t\t\t\t.addService('actions_authorizator', () => {\n\t\t\t\t\treturn new Authorizator.Default(new AccessEvaluator.PermissionEvaluator(new ActionsPermissionsFactory().create()))\n\t\t\t\t})\n\t\t\t\t.setupService(\n\t\t\t\t\t'application',\n\t\t\t\t\t(\n\t\t\t\t\t\tit,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tprojectContextResolver,\n\t\t\t\t\t\t\tdebugMode,\n\t\t\t\t\t\t\tactions_eventRepository,\n\t\t\t\t\t\t\tactions_eventDispatcher,\n\t\t\t\t\t\t\tactions_dispatchWorkerSupervisorFactory,\n\t\t\t\t\t\t\tactions_variableManager,\n\t\t\t\t\t\t\tactions_authorizator,\n\t\t\t\t\t\t\tpanelApiMount,\n\t\t\t\t\t\t},\n\t\t\t\t\t) => {\n\t\t\t\t\t\tconst handlerFactory = new ActionsGraphQLHandlerFactory()\n\t\t\t\t\t\tconst eventsQueryResolver = new EventsQueryResolver()\n\t\t\t\t\t\tconst processBatchMutationResolver = new ProcessBatchMutationResolver(actions_eventDispatcher)\n\t\t\t\t\t\tconst variablesQueryResolver = new VariablesQueryResolver(actions_variableManager)\n\t\t\t\t\t\tconst setVariablesMutationResolver = new SetVariablesMutationResolver(actions_variableManager)\n\t\t\t\t\t\tconst resolversFactory = new ResolversFactory(\n\t\t\t\t\t\t\teventsQueryResolver,\n\t\t\t\t\t\t\tprocessBatchMutationResolver,\n\t\t\t\t\t\t\tvariablesQueryResolver,\n\t\t\t\t\t\t\tsetVariablesMutationResolver,\n\t\t\t\t\t\t\tnew RetryEventMutationResolver(actions_eventRepository),\n\t\t\t\t\t\t\tnew StopEventMutationResolver(actions_eventRepository),\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\tconst actionsContextResolver = new ActionsContextResolver(debugMode, projectContextResolver)\n\t\t\t\t\t\tconst actionsMiddlewareFactory = new ActionsApiMiddlewareFactory(\n\t\t\t\t\t\t\tactionsContextResolver,\n\t\t\t\t\t\t\thandlerFactory.create(resolversFactory),\n\t\t\t\t\t\t\tactions_authorizator,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst actionsWebsocketMiddlewareFactory = new ActionsWebsocketControllerFactory(\n\t\t\t\t\t\t\tdebugMode,\n\t\t\t\t\t\t\tactions_dispatchWorkerSupervisorFactory,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tit.addRoute('actions', '/actions/:projectSlug', actionsMiddlewareFactory.create())\n\t\t\t\t\t\tit.addWebsocketRoute('actions', '/actions/_worker', actionsWebsocketMiddlewareFactory.create())\n\t\t\t\t\t\t// Does nothing unless the panel is served; when it is, the panel's access gate applies\n\t\t\t\t\t\t// and the UI learns the actions module has an API to talk to.\n\t\t\t\t\t\tpanelApiMount.mount(it, {\n\t\t\t\t\t\t\tname: 'actions',\n\t\t\t\t\t\t\tpath: 'actions/:projectSlug',\n\t\t\t\t\t\t\tcontroller: actionsMiddlewareFactory.create(),\n\t\t\t\t\t\t})\n\t\t\t\t\t},\n\t\t\t\t)\n\t\t\t\t.setupService(\n\t\t\t\t\t'applicationWorkers',\n\t\t\t\t\t(it, { actions_dispatchWorkerSupervisorFactory, projectGroupContainer, projectGroupContainerResolver, serverConfig }) => {\n\t\t\t\t\t\tif (!(serverConfig as any).projectGroup) {\n\t\t\t\t\t\t\tit.registerWorker(this.name, actions_dispatchWorkerSupervisorFactory.create(projectGroupContainer))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tit.registerWorker(this.name, new LazyDispatchWorker(projectGroupContainerResolver, actions_dispatchWorkerSupervisorFactory))\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t) as unknown as MasterContainerBuilder\n\t\t}\n\t\treturn hook\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -52,9 +52,8 @@ const OutgoingMessage = Typesafe__namespace.discriminatedUnion(
|
|
|
52
52
|
}
|
|
53
53
|
);
|
|
54
54
|
class ActionsWebsocketControllerFactory {
|
|
55
|
-
constructor(debug,
|
|
55
|
+
constructor(debug, dispatchWorkerSupervisorFactory) {
|
|
56
56
|
this.debug = debug;
|
|
57
|
-
this.actionsContextResolver = actionsContextResolver;
|
|
58
57
|
this.dispatchWorkerSupervisorFactory = dispatchWorkerSupervisorFactory;
|
|
59
58
|
}
|
|
60
59
|
create() {
|
|
@@ -73,23 +72,42 @@ class ActionsWebsocketControllerFactory {
|
|
|
73
72
|
ws.send(JSON.stringify(message));
|
|
74
73
|
};
|
|
75
74
|
let workers = [];
|
|
75
|
+
let stopAllPromise;
|
|
76
|
+
let connectionClosing = false;
|
|
76
77
|
const abortListener = async () => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
connectionClosing = true;
|
|
79
|
+
try {
|
|
80
|
+
send({ type: "message", message: "shutting down" });
|
|
81
|
+
await stopAll();
|
|
82
|
+
} catch (error) {
|
|
83
|
+
logger.error(error, { message: "Websocket worker cleanup failed" });
|
|
84
|
+
} finally {
|
|
85
|
+
try {
|
|
86
|
+
send({ type: "message", message: "closing connection" });
|
|
87
|
+
} finally {
|
|
88
|
+
ws.close(1012);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
81
91
|
};
|
|
82
92
|
ctx.abortSignal.addEventListener("abort", abortListener);
|
|
83
|
-
const stopAll =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
const stopAll = () => {
|
|
94
|
+
stopAllPromise ?? (stopAllPromise = (async () => {
|
|
95
|
+
const results = await Promise.allSettled(workers.map(async (it) => {
|
|
96
|
+
await (await it.running).end();
|
|
97
|
+
workers = workers.filter((worker) => worker !== it);
|
|
98
|
+
send({
|
|
99
|
+
type: "workedStopped",
|
|
100
|
+
workerId: it.id
|
|
101
|
+
});
|
|
102
|
+
}));
|
|
103
|
+
const errors = results.flatMap((it) => it.status === "rejected" ? [it.reason] : []);
|
|
104
|
+
if (errors.length > 0) {
|
|
105
|
+
throw new AggregateError(errors, "Worker cleanup failed");
|
|
106
|
+
}
|
|
107
|
+
})().finally(() => {
|
|
108
|
+
stopAllPromise = void 0;
|
|
109
|
+
}));
|
|
110
|
+
return stopAllPromise;
|
|
93
111
|
};
|
|
94
112
|
send({ type: "ready" });
|
|
95
113
|
ws.addEventListener("message", async (message) => {
|
|
@@ -113,38 +131,60 @@ class ActionsWebsocketControllerFactory {
|
|
|
113
131
|
}
|
|
114
132
|
switch (data.type) {
|
|
115
133
|
case "startWorker":
|
|
134
|
+
if (connectionClosing || stopAllPromise !== void 0) {
|
|
135
|
+
send({ type: "error", message: "Workers are stopping" });
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
116
138
|
const workerId = Math.random().toString().substring(2);
|
|
117
139
|
const dispatchSupervisor = this.dispatchWorkerSupervisorFactory.create(projectGroup);
|
|
118
|
-
|
|
119
|
-
|
|
140
|
+
const running = Promise.resolve().then(
|
|
141
|
+
() => dispatchSupervisor.run({
|
|
120
142
|
logger,
|
|
121
143
|
onError: () => {
|
|
122
144
|
send({ type: "workerCrashed", workerId });
|
|
123
145
|
}
|
|
124
|
-
})
|
|
125
|
-
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
const worker = { id: workerId, running };
|
|
149
|
+
workers.push(worker);
|
|
150
|
+
try {
|
|
126
151
|
await running;
|
|
127
|
-
send({ type: "workerStarted", workerId });
|
|
128
152
|
} catch (e) {
|
|
153
|
+
workers = workers.filter((it) => it !== worker);
|
|
129
154
|
logger.error(e, { message: "Worker failed to start" });
|
|
130
155
|
send({ type: "workerFailedToStart", workerId });
|
|
156
|
+
break;
|
|
131
157
|
}
|
|
158
|
+
send({ type: "workerStarted", workerId });
|
|
132
159
|
break;
|
|
133
160
|
case "stopAllWorkers":
|
|
134
161
|
send({ type: "message", message: "stopping" });
|
|
135
|
-
|
|
136
|
-
|
|
162
|
+
try {
|
|
163
|
+
await stopAll();
|
|
164
|
+
send({ type: "message", message: "all stopped" });
|
|
165
|
+
} catch (error) {
|
|
166
|
+
logger.error(error, { message: "Websocket worker cleanup failed" });
|
|
167
|
+
send({ type: "error", message: "Worker cleanup failed" });
|
|
168
|
+
}
|
|
137
169
|
break;
|
|
138
170
|
}
|
|
139
171
|
});
|
|
140
172
|
const pingHandle = setInterval(() => {
|
|
141
173
|
ws.ping();
|
|
142
174
|
}, 5e3);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
175
|
+
ctx.waitUntil(
|
|
176
|
+
new Promise((resolve) => {
|
|
177
|
+
ws.addEventListener("close", () => {
|
|
178
|
+
connectionClosing = true;
|
|
179
|
+
ctx.abortSignal.removeEventListener("abort", abortListener);
|
|
180
|
+
clearInterval(pingHandle);
|
|
181
|
+
void stopAll().then(resolve, (error) => {
|
|
182
|
+
logger.error(error, { message: "Websocket worker cleanup failed" });
|
|
183
|
+
resolve();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
})
|
|
187
|
+
);
|
|
148
188
|
};
|
|
149
189
|
}
|
|
150
190
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionsWebsocketControllerFactory.cjs","sources":["../../../../../../packages/engine-actions/src/graphql/http/ActionsWebsocketControllerFactory.ts"],"sourcesContent":["import { HttpErrorResponse, WebSocketController } from '@contember/engine-http'\nimport { ActionsContextResolver } from './ActionsContextResolver.js'\nimport { DispatchWorkerSupervisorFactory } from '../../dispatch/DispatchWorkerSupervisor.js'\nimport * as Typesafe from '@contember/typesafe'\nimport { TenantRole } from '@contember/engine-tenant-api'\nimport { Running } from '@contember/engine-common'\n\nconst IncomingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\tstartWorker: Typesafe.object({}),\n\t\tstopAllWorkers: Typesafe.object({}),\n\t},\n)\ntype IncomingMessage = ReturnType<typeof IncomingMessage>\nconst OutgoingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\terror: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tmessage: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tready: Typesafe.object({}),\n\n\t\tworkerStarted: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkedStopped: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerFailedToStart: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerCrashed: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t},\n)\n\ntype OutgoingMessage = ReturnType<typeof OutgoingMessage>\n\nexport class ActionsWebsocketControllerFactory {\n\tconstructor(\n\t\tprivate readonly debug: boolean,\n\t\tprivate readonly actionsContextResolver: ActionsContextResolver,\n\t\tprivate readonly dispatchWorkerSupervisorFactory: DispatchWorkerSupervisorFactory,\n\t) {\n\t}\n\n\tcreate(): WebSocketController {\n\t\treturn async ctx => {\n\t\t\tconst { ws, logger, authResult, projectGroup } = ctx\n\t\t\tif (!authResult) {\n\t\t\t\tthrow new HttpErrorResponse(401, 'Authentication required')\n\t\t\t}\n\t\t\tif (!authResult.roles.some(it => it === TenantRole.SUPER_ADMIN || it === TenantRole.PROJECT_ADMIN)) {\n\t\t\t\tthrow new HttpErrorResponse(403, 'Not allowed to run actions worker')\n\t\t\t}\n\n\t\t\tconst send = (message: OutgoingMessage) => {\n\t\t\t\tif (this.debug) {\n\t\t\t\t\tOutgoingMessage(message)\n\t\t\t\t}\n\t\t\t\tws.send(JSON.stringify(message))\n\t\t\t}\n\n\t\t\tlet workers: { id: string; running: Promise<Running> }[] = []\n\t\t\tconst abortListener = async () => {\n\t\t\t\tsend({ type: 'message', message: 'shutting down' })\n\t\t\t\tawait stopAll()\n\t\t\t\tsend({ type: 'message', message: 'closing connection' })\n\t\t\t\tws.close(1012) // Service Restart\n\t\t\t}\n\t\t\tctx.abortSignal.addEventListener('abort', abortListener)\n\n\t\t\tconst stopAll = async () => {\n\t\t\t\tconst currentWorkers = workers.map(async it => {\n\t\t\t\t\tawait (await it.running).end()\n\t\t\t\t\tsend({\n\t\t\t\t\t\ttype: 'workedStopped',\n\t\t\t\t\t\tworkerId: it.id,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\tworkers = []\n\t\t\t\tawait Promise.all(currentWorkers)\n\t\t\t}\n\t\t\tsend({ type: 'ready' })\n\n\t\t\tws.addEventListener('message', async message => {\n\t\t\t\tlet json\n\t\t\t\ttry {\n\t\t\t\t\tjson = JSON.parse(message.data.toString())\n\t\t\t\t} catch {\n\t\t\t\t\tsend({ type: 'error', message: 'Invalid JSON' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlet data: IncomingMessage\n\t\t\t\ttry {\n\t\t\t\t\tdata = IncomingMessage(json)\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (e instanceof Typesafe.ParseError) {\n\t\t\t\t\t\tsend({ type: 'error', message: e.message })\n\t\t\t\t\t\treturn\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow e\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tswitch (data.type) {\n\t\t\t\t\tcase 'startWorker':\n\t\t\t\t\t\tconst workerId = Math.random().toString().substring(2)\n\t\t\t\t\t\tconst dispatchSupervisor = this.dispatchWorkerSupervisorFactory.create(projectGroup)\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst running = dispatchSupervisor.run({\n\t\t\t\t\t\t\t\tlogger,\n\t\t\t\t\t\t\t\tonError: () => {\n\t\t\t\t\t\t\t\t\tsend({ type: 'workerCrashed', workerId })\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tworkers.push({ id: workerId, running })\n\t\t\t\t\t\t\tawait running\n\t\t\t\t\t\t\tsend({ type: 'workerStarted', workerId })\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tlogger.error(e, { message: 'Worker failed to start' })\n\t\t\t\t\t\t\tsend({ type: 'workerFailedToStart', workerId })\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'stopAllWorkers':\n\t\t\t\t\t\tsend({ type: 'message', message: 'stopping' })\n\t\t\t\t\t\tawait stopAll()\n\t\t\t\t\t\tsend({ type: 'message', message: 'all stopped' })\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst pingHandle = setInterval(() => {\n\t\t\t\tws.ping()\n\t\t\t}, 5000)\n\n\t\t\tws.addEventListener('close', async () => {\n\t\t\t\tctx.abortSignal.removeEventListener('abort', abortListener)\n\t\t\t\tclearInterval(pingHandle)\n\t\t\t\tawait stopAll()\n\t\t\t})\n\t\t}\n\t}\n}\n"],"names":["Typesafe","HttpErrorResponse","TenantRole"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ActionsWebsocketControllerFactory.cjs","sources":["../../../../../../packages/engine-actions/src/graphql/http/ActionsWebsocketControllerFactory.ts"],"sourcesContent":["import { HttpErrorResponse, ProjectGroupContainer, WebSocketController } from '@contember/engine-http'\nimport * as Typesafe from '@contember/typesafe'\nimport { TenantRole } from '@contember/engine-tenant-api'\nimport { Runnable, Running } from '@contember/engine-common'\n\nconst IncomingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\tstartWorker: Typesafe.object({}),\n\t\tstopAllWorkers: Typesafe.object({}),\n\t},\n)\ntype IncomingMessage = ReturnType<typeof IncomingMessage>\nconst OutgoingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\terror: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tmessage: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tready: Typesafe.object({}),\n\n\t\tworkerStarted: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkedStopped: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerFailedToStart: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerCrashed: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t},\n)\n\ntype OutgoingMessage = ReturnType<typeof OutgoingMessage>\n\ntype DispatchWorkerFactory = {\n\tcreate(projectGroup: ProjectGroupContainer): Runnable\n}\n\nexport class ActionsWebsocketControllerFactory {\n\tconstructor(\n\t\tprivate readonly debug: boolean,\n\t\tprivate readonly dispatchWorkerSupervisorFactory: DispatchWorkerFactory,\n\t) {\n\t}\n\n\tcreate(): WebSocketController {\n\t\treturn async ctx => {\n\t\t\tconst { ws, logger, authResult, projectGroup } = ctx\n\t\t\tif (!authResult) {\n\t\t\t\tthrow new HttpErrorResponse(401, 'Authentication required')\n\t\t\t}\n\t\t\tif (!authResult.roles.some(it => it === TenantRole.SUPER_ADMIN || it === TenantRole.PROJECT_ADMIN)) {\n\t\t\t\tthrow new HttpErrorResponse(403, 'Not allowed to run actions worker')\n\t\t\t}\n\n\t\t\tconst send = (message: OutgoingMessage) => {\n\t\t\t\tif (this.debug) {\n\t\t\t\t\tOutgoingMessage(message)\n\t\t\t\t}\n\t\t\t\tws.send(JSON.stringify(message))\n\t\t\t}\n\n\t\t\tlet workers: { id: string; running: Promise<Running> }[] = []\n\t\t\tlet stopAllPromise: Promise<void> | undefined\n\t\t\tlet connectionClosing = false\n\t\t\tconst abortListener = async () => {\n\t\t\t\tconnectionClosing = true\n\t\t\t\ttry {\n\t\t\t\t\tsend({ type: 'message', message: 'shutting down' })\n\t\t\t\t\tawait stopAll()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger.error(error, { message: 'Websocket worker cleanup failed' })\n\t\t\t\t} finally {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tsend({ type: 'message', message: 'closing connection' })\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tws.close(1012) // Service Restart\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tctx.abortSignal.addEventListener('abort', abortListener)\n\n\t\t\tconst stopAll = (): Promise<void> => {\n\t\t\t\tstopAllPromise ??= (async () => {\n\t\t\t\t\t// a worker stays tracked until its own end() resolves, so a failed cleanup is not forgotten\n\t\t\t\t\tconst results = await Promise.allSettled(workers.map(async it => {\n\t\t\t\t\t\tawait (await it.running).end()\n\t\t\t\t\t\tworkers = workers.filter(worker => worker !== it)\n\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\ttype: 'workedStopped',\n\t\t\t\t\t\t\tworkerId: it.id,\n\t\t\t\t\t\t})\n\t\t\t\t\t}))\n\t\t\t\t\tconst errors = results.flatMap(it => it.status === 'rejected' ? [it.reason] : [])\n\t\t\t\t\tif (errors.length > 0) {\n\t\t\t\t\t\tthrow new AggregateError(errors, 'Worker cleanup failed')\n\t\t\t\t\t}\n\t\t\t\t})().finally(() => {\n\t\t\t\t\tstopAllPromise = undefined\n\t\t\t\t})\n\t\t\t\treturn stopAllPromise\n\t\t\t}\n\t\t\tsend({ type: 'ready' })\n\n\t\t\tws.addEventListener('message', async message => {\n\t\t\t\tlet json\n\t\t\t\ttry {\n\t\t\t\t\tjson = JSON.parse(message.data.toString())\n\t\t\t\t} catch {\n\t\t\t\t\tsend({ type: 'error', message: 'Invalid JSON' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlet data: IncomingMessage\n\t\t\t\ttry {\n\t\t\t\t\tdata = IncomingMessage(json)\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (e instanceof Typesafe.ParseError) {\n\t\t\t\t\t\tsend({ type: 'error', message: e.message })\n\t\t\t\t\t\treturn\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow e\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tswitch (data.type) {\n\t\t\t\t\tcase 'startWorker':\n\t\t\t\t\t\tif (connectionClosing || stopAllPromise !== undefined) {\n\t\t\t\t\t\t\tsend({ type: 'error', message: 'Workers are stopping' })\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst workerId = Math.random().toString().substring(2)\n\t\t\t\t\t\tconst dispatchSupervisor = this.dispatchWorkerSupervisorFactory.create(projectGroup)\n\t\t\t\t\t\tconst running = Promise.resolve().then(() =>\n\t\t\t\t\t\t\tdispatchSupervisor.run({\n\t\t\t\t\t\t\t\tlogger,\n\t\t\t\t\t\t\t\tonError: () => {\n\t\t\t\t\t\t\t\t\tsend({ type: 'workerCrashed', workerId })\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst worker = { id: workerId, running }\n\t\t\t\t\t\tworkers.push(worker)\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait running\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tworkers = workers.filter(it => it !== worker)\n\t\t\t\t\t\t\tlogger.error(e, { message: 'Worker failed to start' })\n\t\t\t\t\t\t\tsend({ type: 'workerFailedToStart', workerId })\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsend({ type: 'workerStarted', workerId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'stopAllWorkers':\n\t\t\t\t\t\tsend({ type: 'message', message: 'stopping' })\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait stopAll()\n\t\t\t\t\t\t\tsend({ type: 'message', message: 'all stopped' })\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tlogger.error(error, { message: 'Websocket worker cleanup failed' })\n\t\t\t\t\t\t\tsend({ type: 'error', message: 'Worker cleanup failed' })\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst pingHandle = setInterval(() => {\n\t\t\t\tws.ping()\n\t\t\t}, 5000)\n\n\t\t\tctx.waitUntil(\n\t\t\t\tnew Promise<void>(resolve => {\n\t\t\t\t\tws.addEventListener('close', () => {\n\t\t\t\t\t\tconnectionClosing = true\n\t\t\t\t\t\tctx.abortSignal.removeEventListener('abort', abortListener)\n\t\t\t\t\t\tclearInterval(pingHandle)\n\t\t\t\t\t\tvoid stopAll().then(resolve, error => {\n\t\t\t\t\t\t\tlogger.error(error, { message: 'Websocket worker cleanup failed' })\n\t\t\t\t\t\t\tresolve()\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\t}\n}\n"],"names":["Typesafe","HttpErrorResponse","TenantRole"],"mappings":"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"}
|
|
@@ -33,9 +33,8 @@ const OutgoingMessage = Typesafe.discriminatedUnion(
|
|
|
33
33
|
}
|
|
34
34
|
);
|
|
35
35
|
class ActionsWebsocketControllerFactory {
|
|
36
|
-
constructor(debug,
|
|
36
|
+
constructor(debug, dispatchWorkerSupervisorFactory) {
|
|
37
37
|
this.debug = debug;
|
|
38
|
-
this.actionsContextResolver = actionsContextResolver;
|
|
39
38
|
this.dispatchWorkerSupervisorFactory = dispatchWorkerSupervisorFactory;
|
|
40
39
|
}
|
|
41
40
|
create() {
|
|
@@ -54,23 +53,42 @@ class ActionsWebsocketControllerFactory {
|
|
|
54
53
|
ws.send(JSON.stringify(message));
|
|
55
54
|
};
|
|
56
55
|
let workers = [];
|
|
56
|
+
let stopAllPromise;
|
|
57
|
+
let connectionClosing = false;
|
|
57
58
|
const abortListener = async () => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
connectionClosing = true;
|
|
60
|
+
try {
|
|
61
|
+
send({ type: "message", message: "shutting down" });
|
|
62
|
+
await stopAll();
|
|
63
|
+
} catch (error) {
|
|
64
|
+
logger.error(error, { message: "Websocket worker cleanup failed" });
|
|
65
|
+
} finally {
|
|
66
|
+
try {
|
|
67
|
+
send({ type: "message", message: "closing connection" });
|
|
68
|
+
} finally {
|
|
69
|
+
ws.close(1012);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
62
72
|
};
|
|
63
73
|
ctx.abortSignal.addEventListener("abort", abortListener);
|
|
64
|
-
const stopAll =
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
const stopAll = () => {
|
|
75
|
+
stopAllPromise ?? (stopAllPromise = (async () => {
|
|
76
|
+
const results = await Promise.allSettled(workers.map(async (it) => {
|
|
77
|
+
await (await it.running).end();
|
|
78
|
+
workers = workers.filter((worker) => worker !== it);
|
|
79
|
+
send({
|
|
80
|
+
type: "workedStopped",
|
|
81
|
+
workerId: it.id
|
|
82
|
+
});
|
|
83
|
+
}));
|
|
84
|
+
const errors = results.flatMap((it) => it.status === "rejected" ? [it.reason] : []);
|
|
85
|
+
if (errors.length > 0) {
|
|
86
|
+
throw new AggregateError(errors, "Worker cleanup failed");
|
|
87
|
+
}
|
|
88
|
+
})().finally(() => {
|
|
89
|
+
stopAllPromise = void 0;
|
|
90
|
+
}));
|
|
91
|
+
return stopAllPromise;
|
|
74
92
|
};
|
|
75
93
|
send({ type: "ready" });
|
|
76
94
|
ws.addEventListener("message", async (message) => {
|
|
@@ -94,38 +112,60 @@ class ActionsWebsocketControllerFactory {
|
|
|
94
112
|
}
|
|
95
113
|
switch (data.type) {
|
|
96
114
|
case "startWorker":
|
|
115
|
+
if (connectionClosing || stopAllPromise !== void 0) {
|
|
116
|
+
send({ type: "error", message: "Workers are stopping" });
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
97
119
|
const workerId = Math.random().toString().substring(2);
|
|
98
120
|
const dispatchSupervisor = this.dispatchWorkerSupervisorFactory.create(projectGroup);
|
|
99
|
-
|
|
100
|
-
|
|
121
|
+
const running = Promise.resolve().then(
|
|
122
|
+
() => dispatchSupervisor.run({
|
|
101
123
|
logger,
|
|
102
124
|
onError: () => {
|
|
103
125
|
send({ type: "workerCrashed", workerId });
|
|
104
126
|
}
|
|
105
|
-
})
|
|
106
|
-
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
const worker = { id: workerId, running };
|
|
130
|
+
workers.push(worker);
|
|
131
|
+
try {
|
|
107
132
|
await running;
|
|
108
|
-
send({ type: "workerStarted", workerId });
|
|
109
133
|
} catch (e) {
|
|
134
|
+
workers = workers.filter((it) => it !== worker);
|
|
110
135
|
logger.error(e, { message: "Worker failed to start" });
|
|
111
136
|
send({ type: "workerFailedToStart", workerId });
|
|
137
|
+
break;
|
|
112
138
|
}
|
|
139
|
+
send({ type: "workerStarted", workerId });
|
|
113
140
|
break;
|
|
114
141
|
case "stopAllWorkers":
|
|
115
142
|
send({ type: "message", message: "stopping" });
|
|
116
|
-
|
|
117
|
-
|
|
143
|
+
try {
|
|
144
|
+
await stopAll();
|
|
145
|
+
send({ type: "message", message: "all stopped" });
|
|
146
|
+
} catch (error) {
|
|
147
|
+
logger.error(error, { message: "Websocket worker cleanup failed" });
|
|
148
|
+
send({ type: "error", message: "Worker cleanup failed" });
|
|
149
|
+
}
|
|
118
150
|
break;
|
|
119
151
|
}
|
|
120
152
|
});
|
|
121
153
|
const pingHandle = setInterval(() => {
|
|
122
154
|
ws.ping();
|
|
123
155
|
}, 5e3);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
156
|
+
ctx.waitUntil(
|
|
157
|
+
new Promise((resolve) => {
|
|
158
|
+
ws.addEventListener("close", () => {
|
|
159
|
+
connectionClosing = true;
|
|
160
|
+
ctx.abortSignal.removeEventListener("abort", abortListener);
|
|
161
|
+
clearInterval(pingHandle);
|
|
162
|
+
void stopAll().then(resolve, (error) => {
|
|
163
|
+
logger.error(error, { message: "Websocket worker cleanup failed" });
|
|
164
|
+
resolve();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
})
|
|
168
|
+
);
|
|
129
169
|
};
|
|
130
170
|
}
|
|
131
171
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionsWebsocketControllerFactory.js","sources":["../../../../../../packages/engine-actions/src/graphql/http/ActionsWebsocketControllerFactory.ts"],"sourcesContent":["import { HttpErrorResponse, WebSocketController } from '@contember/engine-http'\nimport { ActionsContextResolver } from './ActionsContextResolver.js'\nimport { DispatchWorkerSupervisorFactory } from '../../dispatch/DispatchWorkerSupervisor.js'\nimport * as Typesafe from '@contember/typesafe'\nimport { TenantRole } from '@contember/engine-tenant-api'\nimport { Running } from '@contember/engine-common'\n\nconst IncomingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\tstartWorker: Typesafe.object({}),\n\t\tstopAllWorkers: Typesafe.object({}),\n\t},\n)\ntype IncomingMessage = ReturnType<typeof IncomingMessage>\nconst OutgoingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\terror: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tmessage: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tready: Typesafe.object({}),\n\n\t\tworkerStarted: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkedStopped: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerFailedToStart: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerCrashed: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t},\n)\n\ntype OutgoingMessage = ReturnType<typeof OutgoingMessage>\n\nexport class ActionsWebsocketControllerFactory {\n\tconstructor(\n\t\tprivate readonly debug: boolean,\n\t\tprivate readonly actionsContextResolver: ActionsContextResolver,\n\t\tprivate readonly dispatchWorkerSupervisorFactory: DispatchWorkerSupervisorFactory,\n\t) {\n\t}\n\n\tcreate(): WebSocketController {\n\t\treturn async ctx => {\n\t\t\tconst { ws, logger, authResult, projectGroup } = ctx\n\t\t\tif (!authResult) {\n\t\t\t\tthrow new HttpErrorResponse(401, 'Authentication required')\n\t\t\t}\n\t\t\tif (!authResult.roles.some(it => it === TenantRole.SUPER_ADMIN || it === TenantRole.PROJECT_ADMIN)) {\n\t\t\t\tthrow new HttpErrorResponse(403, 'Not allowed to run actions worker')\n\t\t\t}\n\n\t\t\tconst send = (message: OutgoingMessage) => {\n\t\t\t\tif (this.debug) {\n\t\t\t\t\tOutgoingMessage(message)\n\t\t\t\t}\n\t\t\t\tws.send(JSON.stringify(message))\n\t\t\t}\n\n\t\t\tlet workers: { id: string; running: Promise<Running> }[] = []\n\t\t\tconst abortListener = async () => {\n\t\t\t\tsend({ type: 'message', message: 'shutting down' })\n\t\t\t\tawait stopAll()\n\t\t\t\tsend({ type: 'message', message: 'closing connection' })\n\t\t\t\tws.close(1012) // Service Restart\n\t\t\t}\n\t\t\tctx.abortSignal.addEventListener('abort', abortListener)\n\n\t\t\tconst stopAll = async () => {\n\t\t\t\tconst currentWorkers = workers.map(async it => {\n\t\t\t\t\tawait (await it.running).end()\n\t\t\t\t\tsend({\n\t\t\t\t\t\ttype: 'workedStopped',\n\t\t\t\t\t\tworkerId: it.id,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\tworkers = []\n\t\t\t\tawait Promise.all(currentWorkers)\n\t\t\t}\n\t\t\tsend({ type: 'ready' })\n\n\t\t\tws.addEventListener('message', async message => {\n\t\t\t\tlet json\n\t\t\t\ttry {\n\t\t\t\t\tjson = JSON.parse(message.data.toString())\n\t\t\t\t} catch {\n\t\t\t\t\tsend({ type: 'error', message: 'Invalid JSON' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlet data: IncomingMessage\n\t\t\t\ttry {\n\t\t\t\t\tdata = IncomingMessage(json)\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (e instanceof Typesafe.ParseError) {\n\t\t\t\t\t\tsend({ type: 'error', message: e.message })\n\t\t\t\t\t\treturn\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow e\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tswitch (data.type) {\n\t\t\t\t\tcase 'startWorker':\n\t\t\t\t\t\tconst workerId = Math.random().toString().substring(2)\n\t\t\t\t\t\tconst dispatchSupervisor = this.dispatchWorkerSupervisorFactory.create(projectGroup)\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst running = dispatchSupervisor.run({\n\t\t\t\t\t\t\t\tlogger,\n\t\t\t\t\t\t\t\tonError: () => {\n\t\t\t\t\t\t\t\t\tsend({ type: 'workerCrashed', workerId })\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tworkers.push({ id: workerId, running })\n\t\t\t\t\t\t\tawait running\n\t\t\t\t\t\t\tsend({ type: 'workerStarted', workerId })\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tlogger.error(e, { message: 'Worker failed to start' })\n\t\t\t\t\t\t\tsend({ type: 'workerFailedToStart', workerId })\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'stopAllWorkers':\n\t\t\t\t\t\tsend({ type: 'message', message: 'stopping' })\n\t\t\t\t\t\tawait stopAll()\n\t\t\t\t\t\tsend({ type: 'message', message: 'all stopped' })\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst pingHandle = setInterval(() => {\n\t\t\t\tws.ping()\n\t\t\t}, 5000)\n\n\t\t\tws.addEventListener('close', async () => {\n\t\t\t\tctx.abortSignal.removeEventListener('abort', abortListener)\n\t\t\t\tclearInterval(pingHandle)\n\t\t\t\tawait stopAll()\n\t\t\t})\n\t\t}\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ActionsWebsocketControllerFactory.js","sources":["../../../../../../packages/engine-actions/src/graphql/http/ActionsWebsocketControllerFactory.ts"],"sourcesContent":["import { HttpErrorResponse, ProjectGroupContainer, WebSocketController } from '@contember/engine-http'\nimport * as Typesafe from '@contember/typesafe'\nimport { TenantRole } from '@contember/engine-tenant-api'\nimport { Runnable, Running } from '@contember/engine-common'\n\nconst IncomingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\tstartWorker: Typesafe.object({}),\n\t\tstopAllWorkers: Typesafe.object({}),\n\t},\n)\ntype IncomingMessage = ReturnType<typeof IncomingMessage>\nconst OutgoingMessage = Typesafe.discriminatedUnion(\n\t'type',\n\t{\n\t\terror: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tmessage: Typesafe.object({\n\t\t\tmessage: Typesafe.string,\n\t\t}),\n\t\tready: Typesafe.object({}),\n\n\t\tworkerStarted: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkedStopped: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerFailedToStart: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t\tworkerCrashed: Typesafe.object({\n\t\t\tworkerId: Typesafe.string,\n\t\t}),\n\t},\n)\n\ntype OutgoingMessage = ReturnType<typeof OutgoingMessage>\n\ntype DispatchWorkerFactory = {\n\tcreate(projectGroup: ProjectGroupContainer): Runnable\n}\n\nexport class ActionsWebsocketControllerFactory {\n\tconstructor(\n\t\tprivate readonly debug: boolean,\n\t\tprivate readonly dispatchWorkerSupervisorFactory: DispatchWorkerFactory,\n\t) {\n\t}\n\n\tcreate(): WebSocketController {\n\t\treturn async ctx => {\n\t\t\tconst { ws, logger, authResult, projectGroup } = ctx\n\t\t\tif (!authResult) {\n\t\t\t\tthrow new HttpErrorResponse(401, 'Authentication required')\n\t\t\t}\n\t\t\tif (!authResult.roles.some(it => it === TenantRole.SUPER_ADMIN || it === TenantRole.PROJECT_ADMIN)) {\n\t\t\t\tthrow new HttpErrorResponse(403, 'Not allowed to run actions worker')\n\t\t\t}\n\n\t\t\tconst send = (message: OutgoingMessage) => {\n\t\t\t\tif (this.debug) {\n\t\t\t\t\tOutgoingMessage(message)\n\t\t\t\t}\n\t\t\t\tws.send(JSON.stringify(message))\n\t\t\t}\n\n\t\t\tlet workers: { id: string; running: Promise<Running> }[] = []\n\t\t\tlet stopAllPromise: Promise<void> | undefined\n\t\t\tlet connectionClosing = false\n\t\t\tconst abortListener = async () => {\n\t\t\t\tconnectionClosing = true\n\t\t\t\ttry {\n\t\t\t\t\tsend({ type: 'message', message: 'shutting down' })\n\t\t\t\t\tawait stopAll()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger.error(error, { message: 'Websocket worker cleanup failed' })\n\t\t\t\t} finally {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tsend({ type: 'message', message: 'closing connection' })\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tws.close(1012) // Service Restart\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tctx.abortSignal.addEventListener('abort', abortListener)\n\n\t\t\tconst stopAll = (): Promise<void> => {\n\t\t\t\tstopAllPromise ??= (async () => {\n\t\t\t\t\t// a worker stays tracked until its own end() resolves, so a failed cleanup is not forgotten\n\t\t\t\t\tconst results = await Promise.allSettled(workers.map(async it => {\n\t\t\t\t\t\tawait (await it.running).end()\n\t\t\t\t\t\tworkers = workers.filter(worker => worker !== it)\n\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\ttype: 'workedStopped',\n\t\t\t\t\t\t\tworkerId: it.id,\n\t\t\t\t\t\t})\n\t\t\t\t\t}))\n\t\t\t\t\tconst errors = results.flatMap(it => it.status === 'rejected' ? [it.reason] : [])\n\t\t\t\t\tif (errors.length > 0) {\n\t\t\t\t\t\tthrow new AggregateError(errors, 'Worker cleanup failed')\n\t\t\t\t\t}\n\t\t\t\t})().finally(() => {\n\t\t\t\t\tstopAllPromise = undefined\n\t\t\t\t})\n\t\t\t\treturn stopAllPromise\n\t\t\t}\n\t\t\tsend({ type: 'ready' })\n\n\t\t\tws.addEventListener('message', async message => {\n\t\t\t\tlet json\n\t\t\t\ttry {\n\t\t\t\t\tjson = JSON.parse(message.data.toString())\n\t\t\t\t} catch {\n\t\t\t\t\tsend({ type: 'error', message: 'Invalid JSON' })\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlet data: IncomingMessage\n\t\t\t\ttry {\n\t\t\t\t\tdata = IncomingMessage(json)\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (e instanceof Typesafe.ParseError) {\n\t\t\t\t\t\tsend({ type: 'error', message: e.message })\n\t\t\t\t\t\treturn\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow e\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tswitch (data.type) {\n\t\t\t\t\tcase 'startWorker':\n\t\t\t\t\t\tif (connectionClosing || stopAllPromise !== undefined) {\n\t\t\t\t\t\t\tsend({ type: 'error', message: 'Workers are stopping' })\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst workerId = Math.random().toString().substring(2)\n\t\t\t\t\t\tconst dispatchSupervisor = this.dispatchWorkerSupervisorFactory.create(projectGroup)\n\t\t\t\t\t\tconst running = Promise.resolve().then(() =>\n\t\t\t\t\t\t\tdispatchSupervisor.run({\n\t\t\t\t\t\t\t\tlogger,\n\t\t\t\t\t\t\t\tonError: () => {\n\t\t\t\t\t\t\t\t\tsend({ type: 'workerCrashed', workerId })\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t)\n\t\t\t\t\t\tconst worker = { id: workerId, running }\n\t\t\t\t\t\tworkers.push(worker)\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait running\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tworkers = workers.filter(it => it !== worker)\n\t\t\t\t\t\t\tlogger.error(e, { message: 'Worker failed to start' })\n\t\t\t\t\t\t\tsend({ type: 'workerFailedToStart', workerId })\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsend({ type: 'workerStarted', workerId })\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'stopAllWorkers':\n\t\t\t\t\t\tsend({ type: 'message', message: 'stopping' })\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait stopAll()\n\t\t\t\t\t\t\tsend({ type: 'message', message: 'all stopped' })\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tlogger.error(error, { message: 'Websocket worker cleanup failed' })\n\t\t\t\t\t\t\tsend({ type: 'error', message: 'Worker cleanup failed' })\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst pingHandle = setInterval(() => {\n\t\t\t\tws.ping()\n\t\t\t}, 5000)\n\n\t\t\tctx.waitUntil(\n\t\t\t\tnew Promise<void>(resolve => {\n\t\t\t\t\tws.addEventListener('close', () => {\n\t\t\t\t\t\tconnectionClosing = true\n\t\t\t\t\t\tctx.abortSignal.removeEventListener('abort', abortListener)\n\t\t\t\t\t\tclearInterval(pingHandle)\n\t\t\t\t\t\tvoid stopAll().then(resolve, error => {\n\t\t\t\t\t\t\tlogger.error(error, { message: 'Websocket worker cleanup failed' })\n\t\t\t\t\t\t\tresolve()\n\t\t\t\t\t\t})\n\t\t\t\t\t})\n\t\t\t\t}),\n\t\t\t)\n\t\t}\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actionsWebsocketController.test.d.ts","sourceRoot":"","sources":["../../../../tests/cases/unit/actionsWebsocketController.test.ts"],"names":[],"mappings":""}
|