@sentio/runtime 3.0.2-rc.2 → 3.1.0-rc.2

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.
@@ -1,10 +0,0 @@
1
- import { ProcessStreamRequest } from '@sentio/protos';
2
- import { MessagePort } from 'worker_threads';
3
-
4
- declare function export_default({ processId, request: firstRequest, workerPort }: {
5
- processId: number;
6
- request: ProcessStreamRequest;
7
- workerPort: MessagePort;
8
- }): Promise<void>;
9
-
10
- export { export_default as default };
@@ -1,134 +0,0 @@
1
- import { createRequire as createRequireShim } from 'module'; const require = createRequireShim(import.meta.url);
2
- import {
3
- setupLogger
4
- } from "./chunk-PCB4OKW7.js";
5
- import {
6
- ProcessorServiceImpl,
7
- configureEndpoints,
8
- errorString,
9
- freezeGlobalConfig,
10
- require_cjs,
11
- require_lib2 as require_lib,
12
- require_lib3 as require_lib2
13
- } from "./chunk-KVGBPLGJ.js";
14
- import "./chunk-ROBPWJIE.js";
15
- import "./chunk-I45UXGDM.js";
16
- import {
17
- __toESM
18
- } from "./chunk-KVSDPGUI.js";
19
-
20
- // src/service-worker.ts
21
- var import_nice_grpc = __toESM(require_lib(), 1);
22
- var import_nice_grpc_error_details = __toESM(require_lib2(), 1);
23
- import { threadId } from "worker_threads";
24
- import { Piscina } from "piscina";
25
- var import_rxjs = __toESM(require_cjs(), 1);
26
- var started = false;
27
- var unhandled;
28
- process.on("uncaughtException", (err) => {
29
- console.error("Uncaught Exception, please checking if await is properly used", err);
30
- unhandled = err;
31
- }).on("unhandledRejection", (reason, p) => {
32
- if (reason?.message.startsWith('invalid ENS name (disallowed character: "*"')) {
33
- return;
34
- }
35
- console.error("Unhandled Rejection, please checking if await is properly", reason);
36
- unhandled = reason;
37
- }).on("exit", () => {
38
- console.info("Worker thread exiting, threadId:", threadId);
39
- });
40
- var service;
41
- var loader = async (options) => {
42
- if (options.target) {
43
- const m = await import(options.target);
44
- console.debug("Module loaded, path:", options.target, "module:", m);
45
- return m;
46
- }
47
- };
48
- var emptyCallContext = {};
49
- async function start(request, options) {
50
- if (started) {
51
- return {};
52
- }
53
- freezeGlobalConfig();
54
- try {
55
- service = new ProcessorServiceImpl(() => loader(options), options);
56
- } catch (e) {
57
- throw new import_nice_grpc.ServerError(import_nice_grpc.Status.INVALID_ARGUMENT, "Failed to load processor: " + errorString(e));
58
- }
59
- await service.start(request, emptyCallContext);
60
- started = true;
61
- return {};
62
- }
63
- async function service_worker_default({
64
- processId,
65
- request: firstRequest,
66
- workerPort
67
- }) {
68
- const { startRequest, configRequest, options } = Piscina.workerData;
69
- if (!started) {
70
- const logLevel = process.env["LOG_LEVEL"]?.toUpperCase();
71
- setupLogger(options.logFormat === "json", logLevel === "debug" ? true : options.debug, threadId);
72
- configureEndpoints(options);
73
- if (startRequest) {
74
- await start(startRequest, options);
75
- console.debug("worker", threadId, " started, template instance:", startRequest.templateInstances?.length);
76
- }
77
- if (configRequest) {
78
- await service?.getConfig(configRequest, emptyCallContext);
79
- console.debug("worker", threadId, " configured");
80
- }
81
- }
82
- if (unhandled) {
83
- const err = unhandled;
84
- unhandled = void 0;
85
- console.error("Unhandled exception/rejection in previous request:", err);
86
- throw new import_nice_grpc_error_details.RichServerError(
87
- import_nice_grpc.Status.UNAVAILABLE,
88
- "Unhandled exception/rejection in previous request: " + errorString(err),
89
- [
90
- import_nice_grpc_error_details.DebugInfo.fromPartial({
91
- detail: err.message,
92
- stackEntries: err.stack?.split("\n")
93
- })
94
- ]
95
- );
96
- }
97
- const timeout = (options.workerTimeout || 0) * 1e3;
98
- const enablePartition = options.enablePartition || false;
99
- await new Promise((resolve, reject) => {
100
- const subject = new import_rxjs.Subject();
101
- let timeoutId = void 0;
102
- subject.subscribe((resp) => {
103
- console.debug("Worker", threadId, "send response:", resp.result ? "result" : "dbResult");
104
- workerPort.postMessage(resp);
105
- if (resp.result) {
106
- if (timeoutId) clearTimeout(timeoutId);
107
- resolve();
108
- workerPort.close();
109
- }
110
- });
111
- workerPort.on("message", (msg) => {
112
- const request = msg;
113
- console.debug("Worker", threadId, "received request:", request.start ? "start" : "dbResult");
114
- service?.handleRequest(request, firstRequest.binding, subject);
115
- if (enablePartition && request.start && timeout > 0) {
116
- timeoutId = setTimeout(async () => {
117
- reject(new import_nice_grpc_error_details.RichServerError(import_nice_grpc.Status.DEADLINE_EXCEEDED, "Worker timeout exceeded"));
118
- }, timeout);
119
- }
120
- });
121
- console.debug("Worker", threadId, "handle request: binding");
122
- service?.handleRequest(firstRequest, firstRequest.binding, subject);
123
- if (!enablePartition && timeout > 0) {
124
- timeoutId = setTimeout(() => {
125
- reject(new import_nice_grpc_error_details.RichServerError(import_nice_grpc.Status.DEADLINE_EXCEEDED, "Worker timeout exceeded"));
126
- }, timeout);
127
- }
128
- });
129
- }
130
- import("node:process").then((p) => p.stdout.write(""));
131
- export {
132
- service_worker_default as default
133
- };
134
- //# sourceMappingURL=service-worker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/service-worker.ts"],"sourcesContent":["import { DeepPartial, Empty, ProcessStreamRequest, ProcessStreamResponse, StartRequest } from '@sentio/protos'\nimport { CallContext, ServerError, Status } from 'nice-grpc'\nimport { errorString } from './utils.js'\nimport { freezeGlobalConfig } from './global-config.js'\nimport { DebugInfo, RichServerError } from 'nice-grpc-error-details'\nimport { ProcessorServiceImpl } from './service.js'\nimport { MessagePort, threadId } from 'worker_threads'\nimport { Piscina } from 'piscina'\nimport { configureEndpoints } from './endpoints.js'\nimport { setupLogger } from './logger.js'\nimport { Subject } from 'rxjs'\nimport { ProcessorRuntimeOptions } from 'processor-runner-program.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\nlet service: ProcessorServiceImpl | undefined\n\nconst loader = async (options: ProcessorRuntimeOptions) => {\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 emptyCallContext = <CallContext>{}\n\nasync function start(request: StartRequest, options: ProcessorRuntimeOptions): Promise<Empty> {\n if (started) {\n return {}\n }\n freezeGlobalConfig()\n\n try {\n service = new ProcessorServiceImpl(() => loader(options), options)\n } catch (e) {\n throw new ServerError(Status.INVALID_ARGUMENT, 'Failed to load processor: ' + errorString(e))\n }\n\n await service.start(request, emptyCallContext)\n started = true\n return {}\n}\n\nexport default async function ({\n processId,\n request: firstRequest,\n workerPort\n}: {\n processId: number\n request: ProcessStreamRequest\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.logFormat === '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 service?.getConfig(configRequest, emptyCallContext)\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 const timeout = (options.workerTimeout || 0) * 1000 // convert to milliseconds\n const enablePartition = options.enablePartition || false\n await new Promise<void>((resolve, reject) => {\n const subject = new Subject<DeepPartial<ProcessStreamResponse>>()\n let timeoutId: NodeJS.Timeout | undefined = undefined\n subject.subscribe((resp: ProcessStreamResponse) => {\n console.debug('Worker', threadId, 'send response:', resp.result ? 'result' : 'dbResult')\n workerPort.postMessage(resp)\n // receive the response from the processor , close and resolve the promise\n if (resp.result) {\n if (timeoutId) clearTimeout(timeoutId)\n resolve()\n workerPort.close()\n }\n })\n workerPort.on('message', (msg: ProcessStreamRequest) => {\n const request = msg as ProcessStreamRequest\n console.debug('Worker', threadId, 'received request:', request.start ? 'start' : 'dbResult')\n service?.handleRequest(request, firstRequest.binding, subject)\n if (enablePartition && request.start && timeout > 0) {\n timeoutId = setTimeout(async () => {\n reject(new RichServerError(Status.DEADLINE_EXCEEDED, 'Worker timeout exceeded'))\n }, timeout)\n }\n })\n console.debug('Worker', threadId, 'handle request: binding')\n service?.handleRequest(firstRequest, firstRequest.binding, subject)\n if (!enablePartition && timeout > 0) {\n timeoutId = setTimeout(() => {\n reject(new RichServerError(Status.DEADLINE_EXCEEDED, 'Worker timeout exceeded'))\n }, timeout)\n }\n })\n}\n;import(\"node:process\").then((p) => p.stdout.write(\"\"));"],"mappings":"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","names":[]}
@@ -1,193 +0,0 @@
1
- import { CallContext } from 'nice-grpc'
2
- import { Piscina } from 'piscina'
3
- import {
4
- DeepPartial,
5
- Empty,
6
- ProcessConfigRequest,
7
- ProcessConfigResponse,
8
- ProcessResult,
9
- ProcessStreamRequest,
10
- ProcessStreamResponse,
11
- ProcessStreamResponse_Partitions,
12
- StartRequest
13
- } from '@sentio/protos'
14
- import { Subject } from 'rxjs'
15
-
16
- import { MessageChannel } from 'node:worker_threads'
17
- import { ProcessorServiceImpl } from './service.js'
18
- import { ProcessorRuntimeOptions } from 'processor-runner-program.js'
19
- ;(BigInt.prototype as any).toJSON = function () {
20
- return this.toString()
21
- }
22
-
23
- export class ServiceManager extends ProcessorServiceImpl {
24
- private pool: Piscina<any, any>
25
- private workerData: any = {}
26
-
27
- constructor(
28
- loader: () => Promise<any>,
29
- readonly options: ProcessorRuntimeOptions,
30
- shutdownHandler?: () => void
31
- ) {
32
- super(loader, options, shutdownHandler)
33
- this.workerData.options = options
34
- }
35
-
36
- async getConfig(request: ProcessConfigRequest, context: CallContext): Promise<ProcessConfigResponse> {
37
- const newConfig = await super.getConfig(request, context)
38
-
39
- // check if templateInstances changed
40
- if (newConfig.templateInstances?.length != this.workerData?.configRequest?.templateInstances?.length) {
41
- this.workerData.startRequest = StartRequest.fromPartial({
42
- templateInstances: newConfig.templateInstances
43
- })
44
- }
45
-
46
- this.workerData.configRequest = request
47
-
48
- // if pool is initialized, this will trigger restart of all workers
49
- await this.initPool()
50
- return newConfig
51
- }
52
-
53
- async start(request: StartRequest, context: CallContext): Promise<Empty> {
54
- await super.start(request, context)
55
- this.workerData.startRequest = request
56
- return {}
57
- }
58
-
59
- async stop(request: Empty, context: CallContext): Promise<Empty> {
60
- await this.pool?.destroy()
61
- return await super.stop(request, context)
62
- }
63
-
64
- private readonly contexts = new Contexts()
65
-
66
- protected async handleRequests(
67
- requests: AsyncIterable<ProcessStreamRequest>,
68
- subject: Subject<DeepPartial<ProcessStreamResponse>>
69
- ) {
70
- if (!this.pool) {
71
- await this.initPool()
72
- }
73
- for await (const request of requests) {
74
- this.handleSingleRequest(request, subject)
75
- }
76
- }
77
-
78
- async handleSingleRequest(request: ProcessStreamRequest, subject: Subject<DeepPartial<ProcessStreamResponse>>) {
79
- const processId = request.processId
80
- if (request.binding) {
81
- const context = this.contexts.new(processId)
82
- context.mainPort.on('message', (resp: ProcessStreamResponse) => {
83
- subject.next(resp)
84
- if (resp.result) {
85
- // last response
86
- this.contexts.delete(processId)
87
- }
88
- })
89
- try {
90
- await this.pool.run(
91
- { request, workerPort: context.workerPort, processId },
92
- { transferList: [context.workerPort] }
93
- )
94
- } catch (err) {
95
- console.error('Error processing request:', err)
96
- subject.error(err)
97
- }
98
- } else {
99
- const context = this.contexts.get(processId)
100
- if (!context) {
101
- console.error('No context found for processId:', processId)
102
- throw new Error(`No context found for processId: ${processId}`)
103
- }
104
- context.sendRequest(request)
105
- }
106
- }
107
-
108
- async process(processId: number, context: ChannelContext): Promise<ProcessResult | ProcessStreamResponse_Partitions> {
109
- if (!this.pool) {
110
- await this.initPool()
111
- }
112
-
113
- return this.pool.run(
114
- { workerPort: context?.workerPort, processId },
115
- { transferList: context?.workerPort ? [context?.workerPort] : [] }
116
- )
117
- }
118
-
119
- private async initPool() {
120
- if (this.pool) {
121
- await this.pool.close()
122
- }
123
-
124
- if (this.enablePartition) {
125
- const concurrent = parseInt(process.env['PROCESS_CONCURRENCY'] || '0')
126
- if (this.options.worker! < concurrent) {
127
- console.warn(
128
- `When partition is enabled, the worker count must >= 'PROCESS_CONCURRENCY', will set worker count to ${concurrent})`
129
- )
130
- this.options.worker = concurrent
131
- }
132
- }
133
-
134
- console.info('Initializing worker pool with worker count:', this.options.worker)
135
- this.pool = new Piscina({
136
- maxThreads: this.options.worker,
137
- minThreads: this.options.worker,
138
- filename: new URL('./service-worker.js', import.meta.url).href.replaceAll('runtime/src', 'runtime/lib'),
139
- argv: process.argv,
140
- workerData: this.workerData
141
- })
142
- }
143
- }
144
-
145
- class Contexts {
146
- private contexts: Map<number, ChannelContext> = new Map()
147
-
148
- get(processId: number) {
149
- return this.contexts.get(processId)
150
- }
151
-
152
- new(processId: number) {
153
- let context = this.get(processId)
154
- if (context) {
155
- return context
156
- }
157
- context = new ChannelContext(processId)
158
- this.contexts.set(processId, context)
159
- return context
160
- }
161
-
162
- delete(processId: number) {
163
- const context = this.get(processId)
164
- context?.close()
165
- this.contexts.delete(processId)
166
- }
167
-
168
- has(processId: number) {
169
- return this.contexts.has(processId)
170
- }
171
- }
172
-
173
- export class ChannelContext {
174
- channel = new MessageChannel()
175
-
176
- constructor(readonly processId: number) {}
177
-
178
- sendRequest(request: ProcessStreamRequest) {
179
- this.mainPort.postMessage(request)
180
- }
181
-
182
- get workerPort() {
183
- return this.channel.port2
184
- }
185
-
186
- get mainPort() {
187
- return this.channel.port1
188
- }
189
-
190
- close(): void {
191
- this.mainPort.close()
192
- }
193
- }
@@ -1,140 +0,0 @@
1
- import { DeepPartial, Empty, ProcessStreamRequest, ProcessStreamResponse, StartRequest } from '@sentio/protos'
2
- import { CallContext, ServerError, Status } from 'nice-grpc'
3
- import { errorString } from './utils.js'
4
- import { freezeGlobalConfig } from './global-config.js'
5
- import { DebugInfo, RichServerError } from 'nice-grpc-error-details'
6
- import { ProcessorServiceImpl } from './service.js'
7
- import { MessagePort, threadId } from 'worker_threads'
8
- import { Piscina } from 'piscina'
9
- import { configureEndpoints } from './endpoints.js'
10
- import { setupLogger } from './logger.js'
11
- import { Subject } from 'rxjs'
12
- import { ProcessorRuntimeOptions } from 'processor-runner-program.js'
13
-
14
- let started = false
15
-
16
- let unhandled: Error | undefined
17
-
18
- process
19
- .on('uncaughtException', (err) => {
20
- console.error('Uncaught Exception, please checking if await is properly used', err)
21
- unhandled = err
22
- })
23
- .on('unhandledRejection', (reason, p) => {
24
- // @ts-ignore ignore invalid ens error
25
- if (reason?.message.startsWith('invalid ENS name (disallowed character: "*"')) {
26
- return
27
- }
28
- console.error('Unhandled Rejection, please checking if await is properly', reason)
29
- unhandled = reason as Error
30
- // shutdownServers(1)
31
- })
32
- .on('exit', () => {
33
- console.info('Worker thread exiting, threadId:', threadId)
34
- })
35
-
36
- let service: ProcessorServiceImpl | undefined
37
-
38
- const loader = async (options: ProcessorRuntimeOptions) => {
39
- if (options.target) {
40
- const m = await import(options.target)
41
- console.debug('Module loaded, path:', options.target, 'module:', m)
42
- return m
43
- }
44
- }
45
-
46
- const emptyCallContext = <CallContext>{}
47
-
48
- async function start(request: StartRequest, options: ProcessorRuntimeOptions): Promise<Empty> {
49
- if (started) {
50
- return {}
51
- }
52
- freezeGlobalConfig()
53
-
54
- try {
55
- service = new ProcessorServiceImpl(() => loader(options), options)
56
- } catch (e) {
57
- throw new ServerError(Status.INVALID_ARGUMENT, 'Failed to load processor: ' + errorString(e))
58
- }
59
-
60
- await service.start(request, emptyCallContext)
61
- started = true
62
- return {}
63
- }
64
-
65
- export default async function ({
66
- processId,
67
- request: firstRequest,
68
- workerPort
69
- }: {
70
- processId: number
71
- request: ProcessStreamRequest
72
- workerPort: MessagePort
73
- }) {
74
- const { startRequest, configRequest, options } = Piscina.workerData
75
- if (!started) {
76
- const logLevel = process.env['LOG_LEVEL']?.toUpperCase()
77
- setupLogger(options.logFormat === 'json', logLevel === 'debug' ? true : options.debug, threadId)
78
-
79
- configureEndpoints(options)
80
-
81
- if (startRequest) {
82
- await start(startRequest, options)
83
- console.debug('worker', threadId, ' started, template instance:', startRequest.templateInstances?.length)
84
- }
85
-
86
- if (configRequest) {
87
- await service?.getConfig(configRequest, emptyCallContext)
88
- console.debug('worker', threadId, ' configured')
89
- }
90
- }
91
-
92
- if (unhandled) {
93
- const err = unhandled
94
- unhandled = undefined
95
- console.error('Unhandled exception/rejection in previous request:', err)
96
- throw new RichServerError(
97
- Status.UNAVAILABLE,
98
- 'Unhandled exception/rejection in previous request: ' + errorString(err),
99
- [
100
- DebugInfo.fromPartial({
101
- detail: err.message,
102
- stackEntries: err.stack?.split('\n')
103
- })
104
- ]
105
- )
106
- }
107
- const timeout = (options.workerTimeout || 0) * 1000 // convert to milliseconds
108
- const enablePartition = options.enablePartition || false
109
- await new Promise<void>((resolve, reject) => {
110
- const subject = new Subject<DeepPartial<ProcessStreamResponse>>()
111
- let timeoutId: NodeJS.Timeout | undefined = undefined
112
- subject.subscribe((resp: ProcessStreamResponse) => {
113
- console.debug('Worker', threadId, 'send response:', resp.result ? 'result' : 'dbResult')
114
- workerPort.postMessage(resp)
115
- // receive the response from the processor , close and resolve the promise
116
- if (resp.result) {
117
- if (timeoutId) clearTimeout(timeoutId)
118
- resolve()
119
- workerPort.close()
120
- }
121
- })
122
- workerPort.on('message', (msg: ProcessStreamRequest) => {
123
- const request = msg as ProcessStreamRequest
124
- console.debug('Worker', threadId, 'received request:', request.start ? 'start' : 'dbResult')
125
- service?.handleRequest(request, firstRequest.binding, subject)
126
- if (enablePartition && request.start && timeout > 0) {
127
- timeoutId = setTimeout(async () => {
128
- reject(new RichServerError(Status.DEADLINE_EXCEEDED, 'Worker timeout exceeded'))
129
- }, timeout)
130
- }
131
- })
132
- console.debug('Worker', threadId, 'handle request: binding')
133
- service?.handleRequest(firstRequest, firstRequest.binding, subject)
134
- if (!enablePartition && timeout > 0) {
135
- timeoutId = setTimeout(() => {
136
- reject(new RichServerError(Status.DEADLINE_EXCEEDED, 'Worker timeout exceeded'))
137
- }, timeout)
138
- }
139
- })
140
- }