@sentio/runtime 2.57.12 → 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-NLEBXJPP.js +59726 -0
- package/lib/chunk-NLEBXJPP.js.map +1 -0
- package/lib/chunk-ZUTD563J.js +23006 -0
- package/lib/chunk-ZUTD563J.js.map +1 -0
- package/lib/index.d.ts +31 -9
- package/lib/index.js +70 -1
- package/lib/index.js.map +1 -1
- package/lib/processor-runner.d.ts +33 -0
- package/lib/processor-runner.js +26559 -62
- package/lib/processor-runner.js.map +1 -1
- package/lib/service-worker.d.ts +11 -0
- package/lib/service-worker.js +130 -0
- package/lib/service-worker.js.map +1 -0
- package/lib/test-processor.test.d.ts +2 -0
- package/lib/test-processor.test.js.map +1 -0
- package/package.json +1 -1
- package/src/db-context.ts +39 -18
- package/src/endpoints.ts +35 -0
- package/src/logger.ts +3 -2
- package/src/plugin.ts +3 -3
- package/src/processor-runner.ts +15 -36
- package/src/service-manager.ts +232 -0
- package/src/service-worker.ts +150 -0
- package/src/service.ts +2 -2
- package/src/state.ts +19 -0
- package/src/tsup.config.ts +3 -2
- package/lib/chunk-VDRKULG2.js +0 -131
- package/lib/chunk-VDRKULG2.js.map +0 -1
@@ -0,0 +1,232 @@
|
|
1
|
+
import { CallContext } from 'nice-grpc'
|
2
|
+
import { Piscina } from 'piscina'
|
3
|
+
import {
|
4
|
+
DataBinding,
|
5
|
+
DBRequest,
|
6
|
+
DBResponse,
|
7
|
+
DeepPartial,
|
8
|
+
Empty,
|
9
|
+
HandlerType,
|
10
|
+
ProcessConfigRequest,
|
11
|
+
ProcessConfigResponse,
|
12
|
+
ProcessResult,
|
13
|
+
ProcessStreamRequest,
|
14
|
+
ProcessStreamResponse,
|
15
|
+
StartRequest
|
16
|
+
} from '@sentio/protos'
|
17
|
+
|
18
|
+
import { IStoreContext } from './db-context.js'
|
19
|
+
import { Subject } from 'rxjs'
|
20
|
+
|
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'
|
25
|
+
|
26
|
+
const { process_binding_count, process_binding_time, process_binding_error } = processMetrics
|
27
|
+
|
28
|
+
;(BigInt.prototype as any).toJSON = function () {
|
29
|
+
return this.toString()
|
30
|
+
}
|
31
|
+
|
32
|
+
export class ServiceManager extends ProcessorServiceImpl {
|
33
|
+
private pool: Piscina<any, any>
|
34
|
+
private workerData: any = {}
|
35
|
+
|
36
|
+
constructor(
|
37
|
+
readonly options: any,
|
38
|
+
loader: () => Promise<any>,
|
39
|
+
shutdownHandler?: () => void
|
40
|
+
) {
|
41
|
+
super(loader, shutdownHandler)
|
42
|
+
this.workerData.options = options
|
43
|
+
}
|
44
|
+
|
45
|
+
async getConfig(request: ProcessConfigRequest, context: CallContext): Promise<ProcessConfigResponse> {
|
46
|
+
const newConfig = await super.getConfig(request, context)
|
47
|
+
|
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
|
+
}
|
54
|
+
|
55
|
+
this.workerData.configRequest = request
|
56
|
+
|
57
|
+
// if pool is initialized, this will trigger restart of all workers
|
58
|
+
await this.initPool()
|
59
|
+
return newConfig
|
60
|
+
}
|
61
|
+
|
62
|
+
async start(request: StartRequest, context: CallContext): Promise<Empty> {
|
63
|
+
await super.start(request, context)
|
64
|
+
this.workerData.startRequest = request
|
65
|
+
return {}
|
66
|
+
}
|
67
|
+
|
68
|
+
async stop(request: Empty, context: CallContext): Promise<Empty> {
|
69
|
+
await this.pool?.destroy()
|
70
|
+
return await super.stop(request, context)
|
71
|
+
}
|
72
|
+
|
73
|
+
async process(request: DataBinding, dbContext?: ChannelStoreContext): Promise<ProcessResult> {
|
74
|
+
if (!this.pool) {
|
75
|
+
await this.initPool()
|
76
|
+
}
|
77
|
+
|
78
|
+
return this.pool.run(
|
79
|
+
{ request, workerPort: dbContext?.workerPort },
|
80
|
+
{ transferList: dbContext?.workerPort ? [dbContext?.workerPort] : [] }
|
81
|
+
)
|
82
|
+
}
|
83
|
+
|
84
|
+
private readonly contexts = new Contexts()
|
85
|
+
|
86
|
+
protected async handleRequests(
|
87
|
+
requests: AsyncIterable<ProcessStreamRequest>,
|
88
|
+
subject: Subject<DeepPartial<ProcessStreamResponse>>
|
89
|
+
) {
|
90
|
+
for await (const request of requests) {
|
91
|
+
try {
|
92
|
+
// console.debug('received request:', request)
|
93
|
+
if (request.binding) {
|
94
|
+
process_binding_count.add(1)
|
95
|
+
|
96
|
+
// Adjust binding will make some request become invalid by setting UNKNOWN HandlerType
|
97
|
+
// for older SDK version, so we just return empty result for them here
|
98
|
+
if (request.binding.handlerType === HandlerType.UNKNOWN) {
|
99
|
+
subject.next({
|
100
|
+
processId: request.processId,
|
101
|
+
result: ProcessResult.create()
|
102
|
+
})
|
103
|
+
continue
|
104
|
+
}
|
105
|
+
|
106
|
+
const binding = request.binding
|
107
|
+
|
108
|
+
const dbContext = this.contexts.new(request.processId, subject)
|
109
|
+
|
110
|
+
const start = Date.now()
|
111
|
+
await this.process(binding, dbContext)
|
112
|
+
.then(async (result) => {
|
113
|
+
subject.next({
|
114
|
+
result,
|
115
|
+
processId: request.processId
|
116
|
+
})
|
117
|
+
})
|
118
|
+
.catch((e) => {
|
119
|
+
dbContext.error(request.processId, e)
|
120
|
+
process_binding_error.add(1)
|
121
|
+
})
|
122
|
+
.finally(() => {
|
123
|
+
const cost = Date.now() - start
|
124
|
+
process_binding_time.add(cost)
|
125
|
+
this.contexts.delete(request.processId)
|
126
|
+
})
|
127
|
+
}
|
128
|
+
if (request.dbResult) {
|
129
|
+
const dbContext = this.contexts.get(request.processId)
|
130
|
+
try {
|
131
|
+
dbContext?.result(request.dbResult)
|
132
|
+
} catch (e) {
|
133
|
+
subject.error(new Error('db result error, process should stop'))
|
134
|
+
}
|
135
|
+
}
|
136
|
+
} catch (e) {
|
137
|
+
// should not happen
|
138
|
+
console.error('unexpect error during handle loop', e)
|
139
|
+
}
|
140
|
+
}
|
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
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
export type WorkerMessage = DBRequest & { processId: number }
|
164
|
+
|
165
|
+
class Contexts {
|
166
|
+
private contexts: Map<number, ChannelStoreContext> = new Map()
|
167
|
+
|
168
|
+
get(processId: number) {
|
169
|
+
return this.contexts.get(processId)
|
170
|
+
}
|
171
|
+
|
172
|
+
new(processId: number, subject: Subject<DeepPartial<ProcessStreamResponse>>) {
|
173
|
+
const context = new ChannelStoreContext(subject, processId)
|
174
|
+
this.contexts.set(processId, context)
|
175
|
+
return context
|
176
|
+
}
|
177
|
+
|
178
|
+
delete(processId: number) {
|
179
|
+
const context = this.get(processId)
|
180
|
+
context?.close()
|
181
|
+
this.contexts.delete(processId)
|
182
|
+
}
|
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
|
+
}
|
@@ -0,0 +1,150 @@
|
|
1
|
+
import {
|
2
|
+
DataBinding,
|
3
|
+
DBResponse,
|
4
|
+
DeepPartial,
|
5
|
+
Empty,
|
6
|
+
ProcessConfigRequest,
|
7
|
+
ProcessConfigResponse,
|
8
|
+
ProcessStreamResponse,
|
9
|
+
StartRequest
|
10
|
+
} from '@sentio/protos'
|
11
|
+
import { CallContext, ServerError, Status } from 'nice-grpc'
|
12
|
+
import { PluginManager } from './plugin.js'
|
13
|
+
import { errorString } from './utils.js'
|
14
|
+
import { freezeGlobalConfig } from './global-config.js'
|
15
|
+
import { DebugInfo, RichServerError } from 'nice-grpc-error-details'
|
16
|
+
import { recordRuntimeInfo } from './service.js'
|
17
|
+
import { BroadcastChannel, MessagePort, threadId } from 'worker_threads'
|
18
|
+
import { Piscina } from 'piscina'
|
19
|
+
import { configureEndpoints } from './endpoints.js'
|
20
|
+
import { setupLogger } from './logger.js'
|
21
|
+
import { AbstractStoreContext } from './db-context.js'
|
22
|
+
|
23
|
+
let started = false
|
24
|
+
|
25
|
+
let unhandled: Error | undefined
|
26
|
+
|
27
|
+
process
|
28
|
+
.on('uncaughtException', (err) => {
|
29
|
+
console.error('Uncaught Exception, please checking if await is properly used', err)
|
30
|
+
unhandled = err
|
31
|
+
})
|
32
|
+
.on('unhandledRejection', (reason, p) => {
|
33
|
+
// @ts-ignore ignore invalid ens error
|
34
|
+
if (reason?.message.startsWith('invalid ENS name (disallowed character: "*"')) {
|
35
|
+
return
|
36
|
+
}
|
37
|
+
console.error('Unhandled Rejection, please checking if await is properly', reason)
|
38
|
+
unhandled = reason as Error
|
39
|
+
// shutdownServers(1)
|
40
|
+
})
|
41
|
+
|
42
|
+
async function getConfig(request: ProcessConfigRequest, context?: CallContext): Promise<ProcessConfigResponse> {
|
43
|
+
if (!started) {
|
44
|
+
throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
|
45
|
+
}
|
46
|
+
|
47
|
+
const newConfig = ProcessConfigResponse.fromPartial({})
|
48
|
+
await PluginManager.INSTANCE.configure(newConfig)
|
49
|
+
return newConfig
|
50
|
+
}
|
51
|
+
|
52
|
+
const loader = async (options: any) => {
|
53
|
+
if (options.target) {
|
54
|
+
const m = await import(options.target)
|
55
|
+
console.debug('Module loaded, path:', options.target, 'module:', m)
|
56
|
+
return m
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
const configureChannel = new BroadcastChannel('configure_channel')
|
61
|
+
configureChannel.onmessage = (request: ProcessConfigRequest) => {
|
62
|
+
getConfig(request)
|
63
|
+
}
|
64
|
+
|
65
|
+
async function start(request: StartRequest, options: any): Promise<Empty> {
|
66
|
+
if (started) {
|
67
|
+
return {}
|
68
|
+
}
|
69
|
+
freezeGlobalConfig()
|
70
|
+
|
71
|
+
try {
|
72
|
+
await loader(options)
|
73
|
+
} catch (e) {
|
74
|
+
throw new ServerError(Status.INVALID_ARGUMENT, 'Failed to load processor: ' + errorString(e))
|
75
|
+
}
|
76
|
+
|
77
|
+
await PluginManager.INSTANCE.start(request)
|
78
|
+
started = true
|
79
|
+
return {}
|
80
|
+
}
|
81
|
+
|
82
|
+
export default async function ({
|
83
|
+
request,
|
84
|
+
processId,
|
85
|
+
workerPort
|
86
|
+
}: {
|
87
|
+
request: DataBinding
|
88
|
+
processId: number
|
89
|
+
workerPort?: MessagePort
|
90
|
+
}) {
|
91
|
+
const { startRequest, configRequest, options } = Piscina.workerData
|
92
|
+
if (!started) {
|
93
|
+
const logLevel = process.env['LOG_LEVEL']?.toUpperCase()
|
94
|
+
setupLogger(options['log-format'] === 'json', logLevel === 'debug' ? true : options.debug, threadId)
|
95
|
+
|
96
|
+
configureEndpoints(options)
|
97
|
+
|
98
|
+
if (startRequest) {
|
99
|
+
await start(startRequest, options)
|
100
|
+
console.debug('worker started, template instance:', startRequest.templateInstances?.length)
|
101
|
+
}
|
102
|
+
|
103
|
+
if (configRequest) {
|
104
|
+
await getConfig(configRequest)
|
105
|
+
console.debug('worker configured')
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
if (unhandled) {
|
110
|
+
const err = unhandled
|
111
|
+
unhandled = undefined
|
112
|
+
throw new RichServerError(
|
113
|
+
Status.UNAVAILABLE,
|
114
|
+
'Unhandled exception/rejection in previous request: ' + errorString(err),
|
115
|
+
[
|
116
|
+
DebugInfo.fromPartial({
|
117
|
+
detail: err.message,
|
118
|
+
stackEntries: err.stack?.split('\n')
|
119
|
+
})
|
120
|
+
]
|
121
|
+
)
|
122
|
+
}
|
123
|
+
|
124
|
+
const result = await PluginManager.INSTANCE.processBinding(
|
125
|
+
request,
|
126
|
+
undefined,
|
127
|
+
workerPort ? new WorkerStoreContext(workerPort, processId) : undefined
|
128
|
+
)
|
129
|
+
recordRuntimeInfo(result, request.handlerType)
|
130
|
+
return result
|
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
|
) {
|
@@ -474,7 +474,7 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
|
|
474
474
|
}
|
475
475
|
}
|
476
476
|
|
477
|
-
function recordRuntimeInfo(results: ProcessResult, handlerType: HandlerType) {
|
477
|
+
export function recordRuntimeInfo(results: ProcessResult, handlerType: HandlerType) {
|
478
478
|
for (const list of [results.gauges, results.counters, results.events, results.exports]) {
|
479
479
|
list.forEach((e) => {
|
480
480
|
e.runtimeInfo = {
|
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
|
+
}
|
package/src/tsup.config.ts
CHANGED
@@ -6,11 +6,12 @@ export default defineConfig({
|
|
6
6
|
js: `import { createRequire as createRequireShim } from 'module'; const require = createRequireShim(import.meta.url);`
|
7
7
|
}
|
8
8
|
},
|
9
|
-
entry: ['src/index.ts', 'src/processor-runner.ts'],
|
9
|
+
entry: ['src/index.ts', 'src/processor-runner.ts', 'src/service-worker.ts', 'src/test-processor.test.ts'],
|
10
10
|
outDir: 'lib',
|
11
11
|
minify: process.env['BRANCH'] === 'release',
|
12
12
|
sourcemap: true,
|
13
13
|
clean: true,
|
14
14
|
dts: true,
|
15
|
-
format: 'esm'
|
15
|
+
format: 'esm',
|
16
|
+
external: [/^piscina.*$/]
|
16
17
|
})
|