@sentio/runtime 2.57.12-rc.j → 2.57.13-rc.1
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-6XHWJ2VS.js +92 -0
- package/lib/chunk-6XHWJ2VS.js.map +1 -0
- package/lib/chunk-BPGFX5S5.js +10975 -0
- package/lib/chunk-BPGFX5S5.js.map +1 -0
- package/lib/{chunk-PDTC7P7O.js → chunk-NLEBXJPP.js} +24 -20
- package/lib/{chunk-PDTC7P7O.js.map → chunk-NLEBXJPP.js.map} +1 -1
- package/lib/{chunk-XR3EZ6FB.js → chunk-ZUTD563J.js} +60 -7
- package/lib/{chunk-XR3EZ6FB.js.map → chunk-ZUTD563J.js.map} +1 -1
- package/lib/index.d.ts +29 -9
- package/lib/index.js +12 -71
- package/lib/index.js.map +1 -1
- package/lib/processor-runner.d.ts +6 -6
- package/lib/processor-runner.js +330 -11308
- package/lib/processor-runner.js.map +1 -1
- package/lib/service-worker.d.ts +4 -1
- package/lib/service-worker.js +37 -10
- package/lib/service-worker.js.map +1 -1
- package/lib/test-processor.test.js.map +1 -1
- package/package.json +1 -1
- package/src/db-context.ts +39 -18
- package/src/logger.ts +3 -2
- package/src/plugin.ts +3 -3
- package/src/processor-runner.ts +3 -5
- package/src/service-manager.ts +110 -137
- package/src/service-worker.ts +52 -9
- package/src/service.ts +1 -1
- package/src/state.ts +19 -0
package/src/service-manager.ts
CHANGED
@@ -1,35 +1,27 @@
|
|
1
|
-
import { CallContext
|
2
|
-
import { DebugInfo, RichServerError } from 'nice-grpc-error-details'
|
3
|
-
import { from } from 'ix/Ix.asynciterable'
|
4
|
-
import { withAbort } from 'ix/Ix.asynciterable.operators'
|
1
|
+
import { CallContext } from 'nice-grpc'
|
5
2
|
import { Piscina } from 'piscina'
|
6
|
-
|
7
3
|
import {
|
8
4
|
DataBinding,
|
5
|
+
DBRequest,
|
6
|
+
DBResponse,
|
9
7
|
DeepPartial,
|
10
8
|
Empty,
|
11
9
|
HandlerType,
|
12
|
-
PreparedData,
|
13
|
-
PreprocessStreamRequest,
|
14
|
-
ProcessBindingResponse,
|
15
|
-
ProcessBindingsRequest,
|
16
10
|
ProcessConfigRequest,
|
17
11
|
ProcessConfigResponse,
|
18
|
-
ProcessorServiceImplementation,
|
19
12
|
ProcessResult,
|
20
13
|
ProcessStreamRequest,
|
21
14
|
ProcessStreamResponse,
|
22
15
|
StartRequest
|
23
16
|
} from '@sentio/protos'
|
24
17
|
|
25
|
-
import {
|
26
|
-
import { errorString, mergeProcessResults } from './utils.js'
|
27
|
-
import { GLOBAL_CONFIG } from './global-config.js'
|
28
|
-
|
29
|
-
import { StoreContext } from './db-context.js'
|
18
|
+
import { IStoreContext } from './db-context.js'
|
30
19
|
import { Subject } from 'rxjs'
|
31
20
|
|
32
21
|
import { processMetrics } from './metrics.js'
|
22
|
+
import { MessageChannel } from 'node:worker_threads'
|
23
|
+
import { ProcessorServiceImpl } from './service.js'
|
24
|
+
import { TemplateInstanceState } from './state.js'
|
33
25
|
|
34
26
|
const { process_binding_count, process_binding_time, process_binding_error } = processMetrics
|
35
27
|
|
@@ -37,152 +29,64 @@ const { process_binding_count, process_binding_time, process_binding_error } = p
|
|
37
29
|
return this.toString()
|
38
30
|
}
|
39
31
|
|
40
|
-
export class ServiceManager
|
41
|
-
private started = false
|
42
|
-
// When there is unhandled error, stop process and return unavailable error
|
43
|
-
unhandled: Error
|
44
|
-
// private processorConfig: ProcessConfigResponse
|
32
|
+
export class ServiceManager extends ProcessorServiceImpl {
|
45
33
|
private pool: Piscina<any, any>
|
46
|
-
|
47
34
|
private workerData: any = {}
|
48
35
|
|
49
36
|
constructor(
|
50
37
|
readonly options: any,
|
51
|
-
|
52
|
-
|
38
|
+
loader: () => Promise<any>,
|
39
|
+
shutdownHandler?: () => void
|
53
40
|
) {
|
41
|
+
super(loader, shutdownHandler)
|
54
42
|
this.workerData.options = options
|
55
43
|
}
|
56
44
|
|
57
|
-
async *preprocessBindingsStream(requests: AsyncIterable<PreprocessStreamRequest>, context: CallContext) {
|
58
|
-
throw new Error('not supported')
|
59
|
-
}
|
60
|
-
|
61
45
|
async getConfig(request: ProcessConfigRequest, context: CallContext): Promise<ProcessConfigResponse> {
|
62
|
-
|
63
|
-
throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
|
64
|
-
}
|
46
|
+
const newConfig = await super.getConfig(request, context)
|
65
47
|
|
66
|
-
//
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
await PluginManager.INSTANCE.configure(newConfig)
|
48
|
+
// check if templateInstances changed
|
49
|
+
if (newConfig.templateInstances?.length != this.workerData?.configRequest?.templateInstances?.length) {
|
50
|
+
this.workerData.startRequest = StartRequest.fromPartial({
|
51
|
+
templateInstances: newConfig.templateInstances
|
52
|
+
})
|
53
|
+
}
|
73
54
|
|
74
55
|
this.workerData.configRequest = request
|
56
|
+
|
57
|
+
// if pool is initialized, this will trigger restart of all workers
|
58
|
+
await this.initPool()
|
75
59
|
return newConfig
|
76
60
|
}
|
77
61
|
|
78
62
|
async start(request: StartRequest, context: CallContext): Promise<Empty> {
|
79
|
-
|
80
|
-
return {}
|
81
|
-
}
|
82
|
-
|
83
|
-
try {
|
84
|
-
await this.loader()
|
85
|
-
} catch (e) {
|
86
|
-
throw new ServerError(Status.INVALID_ARGUMENT, 'Failed to load processor: ' + errorString(e))
|
87
|
-
}
|
88
|
-
|
89
|
-
// also start the processor in main thread
|
90
|
-
await PluginManager.INSTANCE.start(request)
|
91
|
-
this.started = true
|
63
|
+
await super.start(request, context)
|
92
64
|
this.workerData.startRequest = request
|
93
65
|
return {}
|
94
66
|
}
|
95
67
|
|
96
68
|
async stop(request: Empty, context: CallContext): Promise<Empty> {
|
97
|
-
await this.pool
|
98
|
-
|
99
|
-
console.log('Server Shutting down in 5 seconds')
|
100
|
-
setTimeout(this.shutdownHandler, 5000)
|
101
|
-
}
|
102
|
-
return {}
|
69
|
+
await this.pool?.destroy()
|
70
|
+
return await super.stop(request, context)
|
103
71
|
}
|
104
72
|
|
105
|
-
async
|
106
|
-
const promises = []
|
107
|
-
for (const binding of request.bindings) {
|
108
|
-
const promise = this.processBinding(binding)
|
109
|
-
if (GLOBAL_CONFIG.execution.sequential) {
|
110
|
-
await promise
|
111
|
-
}
|
112
|
-
promises.push(promise)
|
113
|
-
}
|
114
|
-
let promise
|
115
|
-
try {
|
116
|
-
promise = await Promise.all(promises)
|
117
|
-
processMetrics.process_binding_count.add(request.bindings.length)
|
118
|
-
} catch (e) {
|
119
|
-
processMetrics.process_binding_error.add(request.bindings.length)
|
120
|
-
throw e
|
121
|
-
}
|
122
|
-
const result = mergeProcessResults(promise)
|
123
|
-
|
124
|
-
// let updated = false
|
125
|
-
// if (PluginManager.INSTANCE.stateDiff(this.processorConfig)) {
|
126
|
-
// await this.configure()
|
127
|
-
// updated = true
|
128
|
-
// }
|
129
|
-
|
130
|
-
return {
|
131
|
-
result
|
132
|
-
}
|
133
|
-
}
|
134
|
-
|
135
|
-
async processBinding(request: DataBinding): Promise<ProcessResult> {
|
136
|
-
if (!this.started) {
|
137
|
-
throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
|
138
|
-
}
|
73
|
+
async process(request: DataBinding, dbContext?: ChannelStoreContext): Promise<ProcessResult> {
|
139
74
|
if (!this.pool) {
|
140
|
-
this.
|
141
|
-
maxThreads: this.options.worker,
|
142
|
-
minThreads: this.options.worker,
|
143
|
-
filename: new URL('./service-worker.js', import.meta.url).href.replaceAll('runtime/src', 'runtime/lib'),
|
144
|
-
argv: process.argv,
|
145
|
-
workerData: this.workerData
|
146
|
-
})
|
147
|
-
}
|
148
|
-
if (this.unhandled) {
|
149
|
-
throw new RichServerError(
|
150
|
-
Status.UNAVAILABLE,
|
151
|
-
'Unhandled exception/rejection in previous request: ' + errorString(this.unhandled),
|
152
|
-
[
|
153
|
-
DebugInfo.fromPartial({
|
154
|
-
detail: this.unhandled.message,
|
155
|
-
stackEntries: this.unhandled.stack?.split('\n')
|
156
|
-
})
|
157
|
-
]
|
158
|
-
)
|
75
|
+
await this.initPool()
|
159
76
|
}
|
160
77
|
|
161
|
-
return this.pool.run(
|
78
|
+
return this.pool.run(
|
79
|
+
{ request, workerPort: dbContext?.workerPort },
|
80
|
+
{ transferList: dbContext?.workerPort ? [dbContext?.workerPort] : [] }
|
81
|
+
)
|
162
82
|
}
|
163
83
|
|
164
|
-
|
165
|
-
if (!this.started) {
|
166
|
-
throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
|
167
|
-
}
|
84
|
+
private readonly contexts = new Contexts()
|
168
85
|
|
169
|
-
|
170
|
-
this.handleRequests(requests, subject)
|
171
|
-
.then(() => {
|
172
|
-
subject.complete()
|
173
|
-
})
|
174
|
-
.catch((e) => {
|
175
|
-
console.error(e)
|
176
|
-
subject.error(e)
|
177
|
-
})
|
178
|
-
yield* from(subject).pipe(withAbort(context.signal))
|
179
|
-
}
|
180
|
-
|
181
|
-
private async handleRequests(
|
86
|
+
protected async handleRequests(
|
182
87
|
requests: AsyncIterable<ProcessStreamRequest>,
|
183
88
|
subject: Subject<DeepPartial<ProcessStreamResponse>>
|
184
89
|
) {
|
185
|
-
const contexts = new Contexts()
|
186
90
|
for await (const request of requests) {
|
187
91
|
try {
|
188
92
|
// console.debug('received request:', request)
|
@@ -200,13 +104,12 @@ export class ServiceManager implements ProcessorServiceImplementation {
|
|
200
104
|
}
|
201
105
|
|
202
106
|
const binding = request.binding
|
203
|
-
|
204
|
-
const dbContext = contexts.new(request.processId, subject)
|
107
|
+
|
108
|
+
const dbContext = this.contexts.new(request.processId, subject)
|
109
|
+
|
205
110
|
const start = Date.now()
|
206
|
-
await this.
|
111
|
+
await this.process(binding, dbContext)
|
207
112
|
.then(async (result) => {
|
208
|
-
// await all pending db requests
|
209
|
-
// await dbContext.awaitPendings()
|
210
113
|
subject.next({
|
211
114
|
result,
|
212
115
|
processId: request.processId
|
@@ -219,11 +122,11 @@ export class ServiceManager implements ProcessorServiceImplementation {
|
|
219
122
|
.finally(() => {
|
220
123
|
const cost = Date.now() - start
|
221
124
|
process_binding_time.add(cost)
|
222
|
-
contexts.delete(request.processId)
|
125
|
+
this.contexts.delete(request.processId)
|
223
126
|
})
|
224
127
|
}
|
225
128
|
if (request.dbResult) {
|
226
|
-
const dbContext = contexts.get(request.processId)
|
129
|
+
const dbContext = this.contexts.get(request.processId)
|
227
130
|
try {
|
228
131
|
dbContext?.result(request.dbResult)
|
229
132
|
} catch (e) {
|
@@ -236,17 +139,38 @@ export class ServiceManager implements ProcessorServiceImplementation {
|
|
236
139
|
}
|
237
140
|
}
|
238
141
|
}
|
142
|
+
|
143
|
+
private async initPool() {
|
144
|
+
if (this.pool) {
|
145
|
+
await this.pool.close()
|
146
|
+
}
|
147
|
+
this.pool = new Piscina({
|
148
|
+
maxThreads: this.options.worker,
|
149
|
+
minThreads: this.options.worker,
|
150
|
+
filename: new URL('./service-worker.js', import.meta.url).href.replaceAll('runtime/src', 'runtime/lib'),
|
151
|
+
argv: process.argv,
|
152
|
+
workerData: this.workerData
|
153
|
+
})
|
154
|
+
this.pool.on('message', (msg) => {
|
155
|
+
if (msg.event == 'add_template_instance') {
|
156
|
+
// sync the template state from worker to the main thread
|
157
|
+
TemplateInstanceState.INSTANCE.addValue(msg.value)
|
158
|
+
}
|
159
|
+
})
|
160
|
+
}
|
239
161
|
}
|
240
162
|
|
163
|
+
export type WorkerMessage = DBRequest & { processId: number }
|
164
|
+
|
241
165
|
class Contexts {
|
242
|
-
private contexts: Map<number,
|
166
|
+
private contexts: Map<number, ChannelStoreContext> = new Map()
|
243
167
|
|
244
168
|
get(processId: number) {
|
245
169
|
return this.contexts.get(processId)
|
246
170
|
}
|
247
171
|
|
248
172
|
new(processId: number, subject: Subject<DeepPartial<ProcessStreamResponse>>) {
|
249
|
-
const context = new
|
173
|
+
const context = new ChannelStoreContext(subject, processId)
|
250
174
|
this.contexts.set(processId, context)
|
251
175
|
return context
|
252
176
|
}
|
@@ -257,3 +181,52 @@ class Contexts {
|
|
257
181
|
this.contexts.delete(processId)
|
258
182
|
}
|
259
183
|
}
|
184
|
+
|
185
|
+
export class ChannelStoreContext implements IStoreContext {
|
186
|
+
channel = new MessageChannel()
|
187
|
+
|
188
|
+
constructor(
|
189
|
+
readonly subject: Subject<DeepPartial<ProcessStreamResponse>>,
|
190
|
+
readonly processId: number
|
191
|
+
) {
|
192
|
+
this.mainPort.on('message', (req: ProcessStreamRequest) => {
|
193
|
+
subject.next({
|
194
|
+
...req,
|
195
|
+
processId: processId
|
196
|
+
})
|
197
|
+
})
|
198
|
+
}
|
199
|
+
|
200
|
+
sendRequest(request: DeepPartial<Omit<DBRequest, 'opId'>>, timeoutSecs?: number): Promise<DBResponse> {
|
201
|
+
throw new Error('should not be used on main thread')
|
202
|
+
}
|
203
|
+
|
204
|
+
get workerPort() {
|
205
|
+
return this.channel.port2
|
206
|
+
}
|
207
|
+
|
208
|
+
get mainPort() {
|
209
|
+
return this.channel.port1
|
210
|
+
}
|
211
|
+
|
212
|
+
result(dbResult: DBResponse) {
|
213
|
+
this.mainPort.postMessage(dbResult)
|
214
|
+
}
|
215
|
+
|
216
|
+
close(): void {
|
217
|
+
this.mainPort.close()
|
218
|
+
}
|
219
|
+
|
220
|
+
error(processId: number, e: any): void {
|
221
|
+
console.error('process error', processId, e)
|
222
|
+
const errorResult = ProcessResult.create({
|
223
|
+
states: {
|
224
|
+
error: e?.toString()
|
225
|
+
}
|
226
|
+
})
|
227
|
+
this.subject.next({
|
228
|
+
result: errorResult,
|
229
|
+
processId
|
230
|
+
})
|
231
|
+
}
|
232
|
+
}
|
package/src/service-worker.ts
CHANGED
@@ -1,13 +1,24 @@
|
|
1
|
-
import {
|
1
|
+
import {
|
2
|
+
DataBinding,
|
3
|
+
DBResponse,
|
4
|
+
DeepPartial,
|
5
|
+
Empty,
|
6
|
+
ProcessConfigRequest,
|
7
|
+
ProcessConfigResponse,
|
8
|
+
ProcessStreamResponse,
|
9
|
+
StartRequest
|
10
|
+
} from '@sentio/protos'
|
2
11
|
import { CallContext, ServerError, Status } from 'nice-grpc'
|
3
12
|
import { PluginManager } from './plugin.js'
|
4
13
|
import { errorString } from './utils.js'
|
5
14
|
import { freezeGlobalConfig } from './global-config.js'
|
6
15
|
import { DebugInfo, RichServerError } from 'nice-grpc-error-details'
|
7
16
|
import { recordRuntimeInfo } from './service.js'
|
8
|
-
import { BroadcastChannel } from 'worker_threads'
|
17
|
+
import { BroadcastChannel, MessagePort, threadId } from 'worker_threads'
|
9
18
|
import { Piscina } from 'piscina'
|
10
19
|
import { configureEndpoints } from './endpoints.js'
|
20
|
+
import { setupLogger } from './logger.js'
|
21
|
+
import { AbstractStoreContext } from './db-context.js'
|
11
22
|
|
12
23
|
let started = false
|
13
24
|
|
@@ -68,14 +79,25 @@ async function start(request: StartRequest, options: any): Promise<Empty> {
|
|
68
79
|
return {}
|
69
80
|
}
|
70
81
|
|
71
|
-
export default async function ({
|
82
|
+
export default async function ({
|
83
|
+
request,
|
84
|
+
processId,
|
85
|
+
workerPort
|
86
|
+
}: {
|
87
|
+
request: DataBinding
|
88
|
+
processId: number
|
89
|
+
workerPort?: MessagePort
|
90
|
+
}) {
|
72
91
|
const { startRequest, configRequest, options } = Piscina.workerData
|
73
92
|
if (!started) {
|
93
|
+
const logLevel = process.env['LOG_LEVEL']?.toUpperCase()
|
94
|
+
setupLogger(options['log-format'] === 'json', logLevel === 'debug' ? true : options.debug, threadId)
|
95
|
+
|
74
96
|
configureEndpoints(options)
|
75
97
|
|
76
98
|
if (startRequest) {
|
77
99
|
await start(startRequest, options)
|
78
|
-
console.debug('worker started')
|
100
|
+
console.debug('worker started, template instance:', startRequest.templateInstances?.length)
|
79
101
|
}
|
80
102
|
|
81
103
|
if (configRequest) {
|
@@ -85,13 +107,15 @@ export default async function ({ request }: { request: DataBinding }) {
|
|
85
107
|
}
|
86
108
|
|
87
109
|
if (unhandled) {
|
110
|
+
const err = unhandled
|
111
|
+
unhandled = undefined
|
88
112
|
throw new RichServerError(
|
89
113
|
Status.UNAVAILABLE,
|
90
|
-
'Unhandled exception/rejection in previous request: ' + errorString(
|
114
|
+
'Unhandled exception/rejection in previous request: ' + errorString(err),
|
91
115
|
[
|
92
116
|
DebugInfo.fromPartial({
|
93
|
-
detail:
|
94
|
-
stackEntries:
|
117
|
+
detail: err.message,
|
118
|
+
stackEntries: err.stack?.split('\n')
|
95
119
|
})
|
96
120
|
]
|
97
121
|
)
|
@@ -99,9 +123,28 @@ export default async function ({ request }: { request: DataBinding }) {
|
|
99
123
|
|
100
124
|
const result = await PluginManager.INSTANCE.processBinding(
|
101
125
|
request,
|
102
|
-
undefined
|
103
|
-
|
126
|
+
undefined,
|
127
|
+
workerPort ? new WorkerStoreContext(workerPort, processId) : undefined
|
104
128
|
)
|
105
129
|
recordRuntimeInfo(result, request.handlerType)
|
106
130
|
return result
|
107
131
|
}
|
132
|
+
|
133
|
+
class WorkerStoreContext extends AbstractStoreContext {
|
134
|
+
constructor(
|
135
|
+
readonly port: MessagePort,
|
136
|
+
processId: number
|
137
|
+
) {
|
138
|
+
super(processId)
|
139
|
+
this.port.on('message', (resp: DBResponse) => {
|
140
|
+
this.result(resp)
|
141
|
+
})
|
142
|
+
this.port.on('close', () => {
|
143
|
+
this.close()
|
144
|
+
})
|
145
|
+
}
|
146
|
+
|
147
|
+
doSend(req: DeepPartial<ProcessStreamResponse>): void {
|
148
|
+
this.port.postMessage(req)
|
149
|
+
}
|
150
|
+
}
|
package/src/service.ts
CHANGED
@@ -413,7 +413,7 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
|
|
413
413
|
yield* from(subject).pipe(withAbort(context.signal))
|
414
414
|
}
|
415
415
|
|
416
|
-
|
416
|
+
protected async handleRequests(
|
417
417
|
requests: AsyncIterable<ProcessStreamRequest>,
|
418
418
|
subject: Subject<DeepPartial<ProcessStreamResponse>>
|
419
419
|
) {
|
package/src/state.ts
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
import { TemplateInstance } from '@sentio/protos'
|
2
|
+
import { isMainThread, parentPort, threadId } from 'node:worker_threads'
|
3
|
+
|
1
4
|
export class State {
|
2
5
|
stateMap = new Map<string, any>()
|
3
6
|
|
@@ -81,3 +84,19 @@ export abstract class ListStateStorage<T> extends StateStorage<T[]> {
|
|
81
84
|
return value
|
82
85
|
}
|
83
86
|
}
|
87
|
+
|
88
|
+
export class TemplateInstanceState extends ListStateStorage<TemplateInstance> {
|
89
|
+
static INSTANCE = new TemplateInstanceState()
|
90
|
+
|
91
|
+
constructor() {
|
92
|
+
super()
|
93
|
+
}
|
94
|
+
|
95
|
+
override addValue(value: TemplateInstance): TemplateInstance {
|
96
|
+
if (!isMainThread) {
|
97
|
+
// I'm worker thread, should notice the main thread
|
98
|
+
parentPort?.postMessage({ event: 'add_template_instance', value, from: threadId })
|
99
|
+
}
|
100
|
+
return super.addValue(value)
|
101
|
+
}
|
102
|
+
}
|