@sentio/runtime 2.59.0-rc.31 → 2.59.0-rc.33
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/lib/{chunk-X2VTMTYL.js → chunk-QWKQ7U4H.js} +1 -4
- package/lib/{chunk-X2VTMTYL.js.map → chunk-QWKQ7U4H.js.map} +1 -1
- package/lib/index.js +1 -1
- package/lib/processor-runner.js +38 -13
- package/lib/processor-runner.js.map +1 -1
- package/lib/service-worker.d.ts +3 -3
- package/lib/service-worker.js +64 -11
- package/lib/service-worker.js.map +1 -1
- package/package.json +1 -1
- package/src/db-context.ts +0 -3
- package/src/processor-runner.ts +2 -1
- package/src/service-manager.ts +48 -16
- package/src/service-worker.ts +68 -10
package/lib/service-worker.d.ts
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
import * as _sentio_protos from '@sentio/protos';
|
2
1
|
import { DataBinding } from '@sentio/protos';
|
3
2
|
import { MessagePort } from 'worker_threads';
|
4
3
|
|
5
|
-
declare function export_default({ request, processId, workerPort }: {
|
4
|
+
declare function export_default({ request, processId, workerPort, partition }: {
|
6
5
|
request: DataBinding;
|
7
6
|
processId: number;
|
8
7
|
workerPort?: MessagePort;
|
9
|
-
|
8
|
+
partition?: boolean;
|
9
|
+
}): Promise<unknown>;
|
10
10
|
|
11
11
|
export { export_default as default };
|
package/lib/service-worker.js
CHANGED
@@ -10,7 +10,7 @@ import {
|
|
10
10
|
recordRuntimeInfo,
|
11
11
|
require_lib3 as require_lib,
|
12
12
|
require_lib4 as require_lib2
|
13
|
-
} from "./chunk-
|
13
|
+
} from "./chunk-QWKQ7U4H.js";
|
14
14
|
import {
|
15
15
|
PluginManager,
|
16
16
|
ProcessConfigResponse,
|
@@ -33,6 +33,8 @@ process.on("uncaughtException", (err) => {
|
|
33
33
|
}
|
34
34
|
console.error("Unhandled Rejection, please checking if await is properly", reason);
|
35
35
|
unhandled = reason;
|
36
|
+
}).on("exit", () => {
|
37
|
+
console.info("Worker thread exiting, threadId:", threadId);
|
36
38
|
});
|
37
39
|
async function getConfig(request, context) {
|
38
40
|
if (!started) {
|
@@ -70,7 +72,8 @@ async function start(request, options) {
|
|
70
72
|
async function service_worker_default({
|
71
73
|
request,
|
72
74
|
processId,
|
73
|
-
workerPort
|
75
|
+
workerPort,
|
76
|
+
partition
|
74
77
|
}) {
|
75
78
|
const { startRequest, configRequest, options } = Piscina.workerData;
|
76
79
|
if (!started) {
|
@@ -79,16 +82,17 @@ async function service_worker_default({
|
|
79
82
|
configureEndpoints(options);
|
80
83
|
if (startRequest) {
|
81
84
|
await start(startRequest, options);
|
82
|
-
console.debug("worker started, template instance:", startRequest.templateInstances?.length);
|
85
|
+
console.debug("worker", threadId, " started, template instance:", startRequest.templateInstances?.length);
|
83
86
|
}
|
84
87
|
if (configRequest) {
|
85
88
|
await getConfig(configRequest);
|
86
|
-
console.debug("worker configured");
|
89
|
+
console.debug("worker", threadId, " configured");
|
87
90
|
}
|
88
91
|
}
|
89
92
|
if (unhandled) {
|
90
93
|
const err = unhandled;
|
91
94
|
unhandled = void 0;
|
95
|
+
console.error("Unhandled exception/rejection in previous request:", err);
|
92
96
|
throw new import_nice_grpc_error_details.RichServerError(
|
93
97
|
import_nice_grpc.Status.UNAVAILABLE,
|
94
98
|
"Unhandled exception/rejection in previous request: " + errorString(err),
|
@@ -100,13 +104,62 @@ async function service_worker_default({
|
|
100
104
|
]
|
101
105
|
);
|
102
106
|
}
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
107
|
+
console.debug("Worker", threadId, "starting request ", processId);
|
108
|
+
const now = Date.now();
|
109
|
+
const timeoutPromise = new Promise((resolve, reject) => {
|
110
|
+
setTimeout(
|
111
|
+
() => {
|
112
|
+
reject(
|
113
|
+
new import_nice_grpc_error_details.RichServerError(
|
114
|
+
import_nice_grpc.Status.DEADLINE_EXCEEDED,
|
115
|
+
`Request ${processId} timed out after ${options["request-timeout"]} ms`
|
116
|
+
)
|
117
|
+
);
|
118
|
+
},
|
119
|
+
(options["process-timeout"] ?? 60) * 1e3
|
120
|
+
);
|
121
|
+
});
|
122
|
+
let resultPromise;
|
123
|
+
if (partition) {
|
124
|
+
resultPromise = PluginManager.INSTANCE.partition(request);
|
125
|
+
} else {
|
126
|
+
resultPromise = PluginManager.INSTANCE.processBinding(
|
127
|
+
request,
|
128
|
+
void 0,
|
129
|
+
workerPort ? new WorkerStoreContext(workerPort, processId) : void 0
|
130
|
+
).then((result) => {
|
131
|
+
recordRuntimeInfo(result, request.handlerType);
|
132
|
+
return result;
|
133
|
+
});
|
134
|
+
}
|
135
|
+
try {
|
136
|
+
const result = await Promise.race([resultPromise, timeoutPromise]);
|
137
|
+
console.debug(
|
138
|
+
"worker",
|
139
|
+
threadId,
|
140
|
+
`finished ${partition ? "partition" : "process"} request`,
|
141
|
+
processId,
|
142
|
+
"took",
|
143
|
+
Date.now() - now
|
144
|
+
);
|
145
|
+
return result;
|
146
|
+
} catch (e) {
|
147
|
+
console.error("worker", threadId, `failed ${partition ? "partition" : "process"} request`, processId, "error:", e);
|
148
|
+
if (e instanceof import_nice_grpc_error_details.RichServerError) {
|
149
|
+
throw e;
|
150
|
+
} else {
|
151
|
+
throw new import_nice_grpc_error_details.RichServerError(
|
152
|
+
import_nice_grpc.Status.INTERNAL,
|
153
|
+
`Worker ${threadId} failed to process request ${processId}: ` + errorString(e),
|
154
|
+
[
|
155
|
+
import_nice_grpc_error_details.DebugInfo.fromPartial({
|
156
|
+
detail: errorString(e),
|
157
|
+
stackEntries: e.stack?.split("\n")
|
158
|
+
})
|
159
|
+
]
|
160
|
+
);
|
161
|
+
}
|
162
|
+
}
|
110
163
|
}
|
111
164
|
var WorkerStoreContext = class extends AbstractStoreContext {
|
112
165
|
constructor(port, processId) {
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../src/service-worker.ts"],"sourcesContent":["import {\n DataBinding,\n DBResponse,\n DeepPartial,\n Empty,\n ProcessConfigRequest,\n ProcessConfigResponse,\n ProcessStreamResponse,\n StartRequest\n} from '@sentio/protos'\nimport { CallContext, ServerError, Status } from 'nice-grpc'\nimport { PluginManager } from './plugin.js'\nimport { errorString } from './utils.js'\nimport { freezeGlobalConfig } from './global-config.js'\nimport { DebugInfo, RichServerError } from 'nice-grpc-error-details'\nimport { recordRuntimeInfo } from './service.js'\nimport { BroadcastChannel, MessagePort, threadId } from 'worker_threads'\nimport { Piscina } from 'piscina'\nimport { configureEndpoints } from './endpoints.js'\nimport { setupLogger } from './logger.js'\nimport { AbstractStoreContext } from './db-context.js'\n\nlet started = false\n\nlet unhandled: Error | undefined\n\nprocess\n .on('uncaughtException', (err) => {\n console.error('Uncaught Exception, please checking if await is properly used', err)\n unhandled = err\n })\n .on('unhandledRejection', (reason, p) => {\n // @ts-ignore ignore invalid ens error\n if (reason?.message.startsWith('invalid ENS name (disallowed character: \"*\"')) {\n return\n }\n console.error('Unhandled Rejection, please checking if await is properly', reason)\n unhandled = reason as Error\n // shutdownServers(1)\n })\n\nasync function getConfig(request: ProcessConfigRequest, context?: CallContext): Promise<ProcessConfigResponse> {\n if (!started) {\n throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')\n }\n\n const newConfig = ProcessConfigResponse.fromPartial({})\n await PluginManager.INSTANCE.configure(newConfig)\n return newConfig\n}\n\nconst loader = async (options: any) => {\n if (options.target) {\n const m = await import(options.target)\n console.debug('Module loaded, path:', options.target, 'module:', m)\n return m\n }\n}\n\nconst configureChannel = new BroadcastChannel('configure_channel')\nconfigureChannel.onmessage = (request: ProcessConfigRequest) => {\n getConfig(request)\n}\n\nasync function start(request: StartRequest, options: any): Promise<Empty> {\n if (started) {\n return {}\n }\n freezeGlobalConfig()\n\n try {\n await loader(options)\n } catch (e) {\n throw new ServerError(Status.INVALID_ARGUMENT, 'Failed to load processor: ' + errorString(e))\n }\n\n await PluginManager.INSTANCE.start(request)\n started = true\n return {}\n}\n\nexport default async function ({\n request,\n processId,\n workerPort\n}: {\n request: DataBinding\n processId: number\n workerPort?: MessagePort\n}) {\n const { startRequest, configRequest, options } = Piscina.workerData\n if (!started) {\n const logLevel = process.env['LOG_LEVEL']?.toUpperCase()\n setupLogger(options['log-format'] === 'json', logLevel === 'debug' ? true : options.debug, threadId)\n\n configureEndpoints(options)\n\n if (startRequest) {\n await start(startRequest, options)\n console.debug('worker started, template instance:', startRequest.templateInstances?.length)\n }\n\n if (configRequest) {\n await getConfig(configRequest)\n console.debug('worker configured')\n }\n }\n\n if (unhandled) {\n const err = unhandled\n unhandled = undefined\n throw new RichServerError(\n Status.UNAVAILABLE,\n 'Unhandled exception/rejection in previous request: ' + errorString(err),\n [\n DebugInfo.fromPartial({\n detail: err.message,\n stackEntries: err.stack?.split('\\n')\n })\n ]\n )\n }\n\n const
|
1
|
+
{"version":3,"sources":["../src/service-worker.ts"],"sourcesContent":["import {\n DataBinding,\n DBResponse,\n DeepPartial,\n Empty,\n ProcessConfigRequest,\n ProcessConfigResponse,\n ProcessResult,\n ProcessStreamResponse,\n ProcessStreamResponse_Partitions,\n StartRequest\n} from '@sentio/protos'\nimport { CallContext, ServerError, Status } from 'nice-grpc'\nimport { PluginManager } from './plugin.js'\nimport { errorString } from './utils.js'\nimport { freezeGlobalConfig } from './global-config.js'\nimport { DebugInfo, RichServerError } from 'nice-grpc-error-details'\nimport { recordRuntimeInfo } from './service.js'\nimport { BroadcastChannel, MessagePort, threadId } from 'worker_threads'\nimport { Piscina } from 'piscina'\nimport { configureEndpoints } from './endpoints.js'\nimport { setupLogger } from './logger.js'\nimport { AbstractStoreContext } from './db-context.js'\n\nlet started = false\n\nlet unhandled: Error | undefined\n\nprocess\n .on('uncaughtException', (err) => {\n console.error('Uncaught Exception, please checking if await is properly used', err)\n unhandled = err\n })\n .on('unhandledRejection', (reason, p) => {\n // @ts-ignore ignore invalid ens error\n if (reason?.message.startsWith('invalid ENS name (disallowed character: \"*\"')) {\n return\n }\n console.error('Unhandled Rejection, please checking if await is properly', reason)\n unhandled = reason as Error\n // shutdownServers(1)\n })\n .on('exit', () => {\n console.info('Worker thread exiting, threadId:', threadId)\n })\n\nasync function getConfig(request: ProcessConfigRequest, context?: CallContext): Promise<ProcessConfigResponse> {\n if (!started) {\n throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')\n }\n\n const newConfig = ProcessConfigResponse.fromPartial({})\n await PluginManager.INSTANCE.configure(newConfig)\n return newConfig\n}\n\nconst loader = async (options: any) => {\n if (options.target) {\n const m = await import(options.target)\n console.debug('Module loaded, path:', options.target, 'module:', m)\n return m\n }\n}\n\nconst configureChannel = new BroadcastChannel('configure_channel')\nconfigureChannel.onmessage = (request: ProcessConfigRequest) => {\n getConfig(request)\n}\n\nasync function start(request: StartRequest, options: any): Promise<Empty> {\n if (started) {\n return {}\n }\n freezeGlobalConfig()\n\n try {\n await loader(options)\n } catch (e) {\n throw new ServerError(Status.INVALID_ARGUMENT, 'Failed to load processor: ' + errorString(e))\n }\n\n await PluginManager.INSTANCE.start(request)\n started = true\n return {}\n}\n\nexport default async function ({\n request,\n processId,\n workerPort,\n partition\n}: {\n request: DataBinding\n processId: number\n workerPort?: MessagePort\n partition?: boolean\n}) {\n const { startRequest, configRequest, options } = Piscina.workerData\n if (!started) {\n const logLevel = process.env['LOG_LEVEL']?.toUpperCase()\n setupLogger(options['log-format'] === 'json', logLevel === 'debug' ? true : options.debug, threadId)\n\n configureEndpoints(options)\n\n if (startRequest) {\n await start(startRequest, options)\n console.debug('worker', threadId, ' started, template instance:', startRequest.templateInstances?.length)\n }\n\n if (configRequest) {\n await getConfig(configRequest)\n console.debug('worker', threadId, ' configured')\n }\n }\n\n if (unhandled) {\n const err = unhandled\n unhandled = undefined\n console.error('Unhandled exception/rejection in previous request:', err)\n throw new RichServerError(\n Status.UNAVAILABLE,\n 'Unhandled exception/rejection in previous request: ' + errorString(err),\n [\n DebugInfo.fromPartial({\n detail: err.message,\n stackEntries: err.stack?.split('\\n')\n })\n ]\n )\n }\n\n console.debug('Worker', threadId, 'starting request ', processId)\n const now = Date.now()\n const timeoutPromise = new Promise((resolve, reject) => {\n setTimeout(\n () => {\n reject(\n new RichServerError(\n Status.DEADLINE_EXCEEDED,\n `Request ${processId} timed out after ${options['request-timeout']} ms`\n )\n )\n },\n (options['process-timeout'] ?? 60) * 1000\n )\n })\n let resultPromise: Promise<ProcessResult | ProcessStreamResponse_Partitions>\n if (partition) {\n resultPromise = PluginManager.INSTANCE.partition(request)\n } else {\n resultPromise = PluginManager.INSTANCE.processBinding(\n request,\n undefined,\n workerPort ? new WorkerStoreContext(workerPort, processId) : undefined\n ).then((result) => {\n recordRuntimeInfo(result, request.handlerType)\n return result\n })\n }\n\n try {\n const result = await Promise.race([resultPromise, timeoutPromise])\n console.debug(\n 'worker',\n threadId,\n `finished ${partition ? 'partition' : 'process'} request`,\n processId,\n 'took',\n Date.now() - now\n )\n return result\n } catch (e) {\n console.error('worker', threadId, `failed ${partition ? 'partition' : 'process'} request`, processId, 'error:', e)\n if (e instanceof RichServerError) {\n throw e\n } else {\n throw new RichServerError(\n Status.INTERNAL,\n `Worker ${threadId} failed to process request ${processId}: ` + errorString(e),\n [\n DebugInfo.fromPartial({\n detail: errorString(e),\n stackEntries: e.stack?.split('\\n')\n })\n ]\n )\n }\n }\n}\n\nclass WorkerStoreContext extends AbstractStoreContext {\n constructor(\n readonly port: MessagePort,\n processId: number\n ) {\n super(processId)\n this.port.on('message', (resp: DBResponse) => {\n this.result(resp)\n })\n this.port.on('close', () => {\n this.close()\n })\n }\n\n doSend(req: DeepPartial<ProcessStreamResponse>): void {\n this.port.postMessage(req)\n }\n}\n;import(\"node:process\").then((p) => p.stdout.write(\"\"));"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
package/src/db-context.ts
CHANGED
@@ -95,9 +95,6 @@ export abstract class AbstractStoreContext implements IStoreContext {
|
|
95
95
|
|
96
96
|
return Promise.race(promises)
|
97
97
|
.then((result: DBResponse) => {
|
98
|
-
if (timeoutSecs) {
|
99
|
-
console.debug('db request', requestType, 'op', opId, ' took', Date.now() - start, 'ms')
|
100
|
-
}
|
101
98
|
request_times[requestType]?.add(Date.now() - start)
|
102
99
|
return result
|
103
100
|
})
|
package/src/processor-runner.ts
CHANGED
@@ -49,7 +49,8 @@ export const optionDefinitions = [
|
|
49
49
|
{ name: 'debug', type: Boolean, defaultValue: false },
|
50
50
|
{ name: 'otlp-debug', type: Boolean, defaultValue: false },
|
51
51
|
{ name: 'start-action-server', type: Boolean, defaultValue: false },
|
52
|
-
{ name: 'worker', type: Number, defaultValue: workerNum }
|
52
|
+
{ name: 'worker', type: Number, defaultValue: workerNum },
|
53
|
+
{ name: 'process-timeout', type: Number, defaultValue: 60 } // 60 seconds
|
53
54
|
]
|
54
55
|
|
55
56
|
const options = commandLineArgs(optionDefinitions, { partial: true })
|
package/src/service-manager.ts
CHANGED
@@ -22,7 +22,6 @@ import { processMetrics } from './metrics.js'
|
|
22
22
|
import { MessageChannel } from 'node:worker_threads'
|
23
23
|
import { ProcessorServiceImpl } from './service.js'
|
24
24
|
import { TemplateInstanceState } from './state.js'
|
25
|
-
import { PluginManager } from './plugin.js'
|
26
25
|
|
27
26
|
const { process_binding_count, process_binding_time, process_binding_error } = processMetrics
|
28
27
|
|
@@ -82,8 +81,8 @@ export class ServiceManager extends ProcessorServiceImpl {
|
|
82
81
|
try {
|
83
82
|
// console.debug('received request:', request)
|
84
83
|
if (request.binding) {
|
84
|
+
lastBinding = request.binding
|
85
85
|
process_binding_count.add(1)
|
86
|
-
|
87
86
|
// Adjust binding will make some request become invalid by setting UNKNOWN HandlerType
|
88
87
|
// for older SDK version, so we just return empty result for them here
|
89
88
|
if (request.binding.handlerType === HandlerType.UNKNOWN) {
|
@@ -93,15 +92,8 @@ export class ServiceManager extends ProcessorServiceImpl {
|
|
93
92
|
})
|
94
93
|
continue
|
95
94
|
}
|
96
|
-
|
97
95
|
if (this.enablePartition) {
|
98
|
-
|
99
|
-
subject.next({
|
100
|
-
processId: request.processId,
|
101
|
-
partitions
|
102
|
-
})
|
103
|
-
})
|
104
|
-
lastBinding = request.binding
|
96
|
+
this.doPartition(request.processId, request.binding, subject)
|
105
97
|
} else {
|
106
98
|
this.doProcess(request.processId, request.binding, subject)
|
107
99
|
}
|
@@ -127,12 +119,44 @@ export class ServiceManager extends ProcessorServiceImpl {
|
|
127
119
|
}
|
128
120
|
}
|
129
121
|
|
122
|
+
private async doPartition(
|
123
|
+
processId: number,
|
124
|
+
binding: DataBinding,
|
125
|
+
subject: Subject<DeepPartial<ProcessStreamResponse>>
|
126
|
+
) {
|
127
|
+
if (!this.pool) {
|
128
|
+
await this.initPool()
|
129
|
+
}
|
130
|
+
const dbContext = this.contexts.new(processId, subject)
|
131
|
+
const start = Date.now()
|
132
|
+
|
133
|
+
this.process(binding, processId, dbContext, true)
|
134
|
+
.then(async (result) => {
|
135
|
+
console.debug('partition', processId, 'finished, took:', Date.now() - start)
|
136
|
+
subject.next({
|
137
|
+
result,
|
138
|
+
processId: processId
|
139
|
+
})
|
140
|
+
})
|
141
|
+
.catch((e) => {
|
142
|
+
dbContext.error(processId, e)
|
143
|
+
process_binding_error.add(1)
|
144
|
+
console.error('partition', processId, 'failed, took:', Date.now() - start)
|
145
|
+
})
|
146
|
+
.finally(() => {
|
147
|
+
const cost = Date.now() - start
|
148
|
+
process_binding_time.add(cost)
|
149
|
+
this.contexts.delete(processId)
|
150
|
+
})
|
151
|
+
}
|
152
|
+
|
130
153
|
private doProcess(processId: number, binding: DataBinding, subject: Subject<DeepPartial<ProcessStreamResponse>>) {
|
131
154
|
const dbContext = this.contexts.new(processId, subject)
|
132
155
|
|
133
156
|
const start = Date.now()
|
134
|
-
this.process(binding, dbContext)
|
157
|
+
this.process(binding, processId, dbContext, false)
|
135
158
|
.then(async (result) => {
|
159
|
+
console.debug('process', processId, 'finished, took:', Date.now() - start)
|
136
160
|
subject.next({
|
137
161
|
result,
|
138
162
|
processId: processId
|
@@ -141,6 +165,7 @@ export class ServiceManager extends ProcessorServiceImpl {
|
|
141
165
|
.catch((e) => {
|
142
166
|
dbContext.error(processId, e)
|
143
167
|
process_binding_error.add(1)
|
168
|
+
console.error('process', processId, 'failed, took:', Date.now() - start)
|
144
169
|
})
|
145
170
|
.finally(() => {
|
146
171
|
const cost = Date.now() - start
|
@@ -149,13 +174,18 @@ export class ServiceManager extends ProcessorServiceImpl {
|
|
149
174
|
})
|
150
175
|
}
|
151
176
|
|
152
|
-
async process(
|
177
|
+
async process(
|
178
|
+
request: DataBinding,
|
179
|
+
processId: number,
|
180
|
+
dbContext?: ChannelStoreContext,
|
181
|
+
partition: boolean = false
|
182
|
+
): Promise<ProcessResult> {
|
153
183
|
if (!this.pool) {
|
154
184
|
await this.initPool()
|
155
185
|
}
|
156
186
|
|
157
187
|
return this.pool.run(
|
158
|
-
{ request, workerPort: dbContext?.workerPort },
|
188
|
+
{ request, workerPort: dbContext?.workerPort, partition, processId },
|
159
189
|
{ transferList: dbContext?.workerPort ? [dbContext?.workerPort] : [] }
|
160
190
|
)
|
161
191
|
}
|
@@ -181,8 +211,6 @@ export class ServiceManager extends ProcessorServiceImpl {
|
|
181
211
|
}
|
182
212
|
}
|
183
213
|
|
184
|
-
export type WorkerMessage = DBRequest & { processId: number }
|
185
|
-
|
186
214
|
class Contexts {
|
187
215
|
private contexts: Map<number, ChannelStoreContext> = new Map()
|
188
216
|
|
@@ -191,7 +219,11 @@ class Contexts {
|
|
191
219
|
}
|
192
220
|
|
193
221
|
new(processId: number, subject: Subject<DeepPartial<ProcessStreamResponse>>) {
|
194
|
-
|
222
|
+
let context = this.get(processId)
|
223
|
+
if (context) {
|
224
|
+
return context
|
225
|
+
}
|
226
|
+
context = new ChannelStoreContext(subject, processId)
|
195
227
|
this.contexts.set(processId, context)
|
196
228
|
return context
|
197
229
|
}
|
package/src/service-worker.ts
CHANGED
@@ -5,7 +5,9 @@ import {
|
|
5
5
|
Empty,
|
6
6
|
ProcessConfigRequest,
|
7
7
|
ProcessConfigResponse,
|
8
|
+
ProcessResult,
|
8
9
|
ProcessStreamResponse,
|
10
|
+
ProcessStreamResponse_Partitions,
|
9
11
|
StartRequest
|
10
12
|
} from '@sentio/protos'
|
11
13
|
import { CallContext, ServerError, Status } from 'nice-grpc'
|
@@ -38,6 +40,9 @@ process
|
|
38
40
|
unhandled = reason as Error
|
39
41
|
// shutdownServers(1)
|
40
42
|
})
|
43
|
+
.on('exit', () => {
|
44
|
+
console.info('Worker thread exiting, threadId:', threadId)
|
45
|
+
})
|
41
46
|
|
42
47
|
async function getConfig(request: ProcessConfigRequest, context?: CallContext): Promise<ProcessConfigResponse> {
|
43
48
|
if (!started) {
|
@@ -82,11 +87,13 @@ async function start(request: StartRequest, options: any): Promise<Empty> {
|
|
82
87
|
export default async function ({
|
83
88
|
request,
|
84
89
|
processId,
|
85
|
-
workerPort
|
90
|
+
workerPort,
|
91
|
+
partition
|
86
92
|
}: {
|
87
93
|
request: DataBinding
|
88
94
|
processId: number
|
89
95
|
workerPort?: MessagePort
|
96
|
+
partition?: boolean
|
90
97
|
}) {
|
91
98
|
const { startRequest, configRequest, options } = Piscina.workerData
|
92
99
|
if (!started) {
|
@@ -97,18 +104,19 @@ export default async function ({
|
|
97
104
|
|
98
105
|
if (startRequest) {
|
99
106
|
await start(startRequest, options)
|
100
|
-
console.debug('worker started, template instance:', startRequest.templateInstances?.length)
|
107
|
+
console.debug('worker', threadId, ' started, template instance:', startRequest.templateInstances?.length)
|
101
108
|
}
|
102
109
|
|
103
110
|
if (configRequest) {
|
104
111
|
await getConfig(configRequest)
|
105
|
-
console.debug('worker configured')
|
112
|
+
console.debug('worker', threadId, ' configured')
|
106
113
|
}
|
107
114
|
}
|
108
115
|
|
109
116
|
if (unhandled) {
|
110
117
|
const err = unhandled
|
111
118
|
unhandled = undefined
|
119
|
+
console.error('Unhandled exception/rejection in previous request:', err)
|
112
120
|
throw new RichServerError(
|
113
121
|
Status.UNAVAILABLE,
|
114
122
|
'Unhandled exception/rejection in previous request: ' + errorString(err),
|
@@ -121,13 +129,63 @@ export default async function ({
|
|
121
129
|
)
|
122
130
|
}
|
123
131
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
132
|
+
console.debug('Worker', threadId, 'starting request ', processId)
|
133
|
+
const now = Date.now()
|
134
|
+
const timeoutPromise = new Promise((resolve, reject) => {
|
135
|
+
setTimeout(
|
136
|
+
() => {
|
137
|
+
reject(
|
138
|
+
new RichServerError(
|
139
|
+
Status.DEADLINE_EXCEEDED,
|
140
|
+
`Request ${processId} timed out after ${options['request-timeout']} ms`
|
141
|
+
)
|
142
|
+
)
|
143
|
+
},
|
144
|
+
(options['process-timeout'] ?? 60) * 1000
|
145
|
+
)
|
146
|
+
})
|
147
|
+
let resultPromise: Promise<ProcessResult | ProcessStreamResponse_Partitions>
|
148
|
+
if (partition) {
|
149
|
+
resultPromise = PluginManager.INSTANCE.partition(request)
|
150
|
+
} else {
|
151
|
+
resultPromise = PluginManager.INSTANCE.processBinding(
|
152
|
+
request,
|
153
|
+
undefined,
|
154
|
+
workerPort ? new WorkerStoreContext(workerPort, processId) : undefined
|
155
|
+
).then((result) => {
|
156
|
+
recordRuntimeInfo(result, request.handlerType)
|
157
|
+
return result
|
158
|
+
})
|
159
|
+
}
|
160
|
+
|
161
|
+
try {
|
162
|
+
const result = await Promise.race([resultPromise, timeoutPromise])
|
163
|
+
console.debug(
|
164
|
+
'worker',
|
165
|
+
threadId,
|
166
|
+
`finished ${partition ? 'partition' : 'process'} request`,
|
167
|
+
processId,
|
168
|
+
'took',
|
169
|
+
Date.now() - now
|
170
|
+
)
|
171
|
+
return result
|
172
|
+
} catch (e) {
|
173
|
+
console.error('worker', threadId, `failed ${partition ? 'partition' : 'process'} request`, processId, 'error:', e)
|
174
|
+
if (e instanceof RichServerError) {
|
175
|
+
throw e
|
176
|
+
} else {
|
177
|
+
throw new RichServerError(
|
178
|
+
Status.INTERNAL,
|
179
|
+
`Worker ${threadId} failed to process request ${processId}: ` + errorString(e),
|
180
|
+
[
|
181
|
+
DebugInfo.fromPartial({
|
182
|
+
detail: errorString(e),
|
183
|
+
stackEntries: e.stack?.split('\n')
|
184
|
+
})
|
185
|
+
]
|
186
|
+
)
|
187
|
+
}
|
188
|
+
}
|
131
189
|
}
|
132
190
|
|
133
191
|
class WorkerStoreContext extends AbstractStoreContext {
|