@sentio/runtime 2.57.11 → 2.57.12-rc.a
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-DYOBLZD3.js +80341 -0
- package/lib/{chunk-VDRKULG2.js.map → chunk-DYOBLZD3.js.map} +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +125 -1
- package/lib/index.js.map +1 -1
- package/lib/processor-runner.d.ts +33 -0
- package/lib/processor-runner.js +41147 -57
- package/lib/processor-runner.js.map +1 -1
- package/package.json +29 -4
- package/src/processor-runner.ts +12 -4
- package/src/service-manager.ts +263 -0
- package/src/service-worker.ts +116 -0
- package/src/service.ts +1 -1
- package/lib/chunk-VDRKULG2.js +0 -131
package/package.json
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
{
|
2
2
|
"name": "@sentio/runtime",
|
3
|
-
"version": "2.57.
|
3
|
+
"version": "2.57.12-rc.a",
|
4
4
|
"license": "Apache-2.0",
|
5
5
|
"type": "module",
|
6
6
|
"exports": {
|
7
|
-
".": "./lib/index.js"
|
7
|
+
".": "./lib/index.js",
|
8
|
+
"./service-worker": "./lib/service-worker.js"
|
8
9
|
},
|
9
10
|
"bin": {
|
10
11
|
"processor-runner": "./lib/processor-runner.js"
|
@@ -14,12 +15,36 @@
|
|
14
15
|
"!**/*.test.{js,ts}",
|
15
16
|
"!{lib,src}/tests"
|
16
17
|
],
|
17
|
-
"dependencies": {
|
18
|
+
"dependencies": {
|
19
|
+
"@grpc/grpc-js": "^1.9.14",
|
20
|
+
"@opentelemetry/exporter-metrics-otlp-grpc": "^0.57.0",
|
21
|
+
"@opentelemetry/exporter-prometheus": "^0.57.0",
|
22
|
+
"@opentelemetry/sdk-node": "^0.57.0",
|
23
|
+
"command-line-args": "^6.0.0",
|
24
|
+
"command-line-usage": "^7.0.1",
|
25
|
+
"ethers": "npm:@sentio/ethers@6.13.1-patch.5",
|
26
|
+
"fs-extra": "^11.2.0",
|
27
|
+
"google-protobuf": "^3.21.2",
|
28
|
+
"ix": "^7.0.0",
|
29
|
+
"long": "^5.2.3",
|
30
|
+
"nice-grpc": "^2.1.10",
|
31
|
+
"nice-grpc-client-middleware-retry": "^3.1.6",
|
32
|
+
"nice-grpc-common": "^2.0.2",
|
33
|
+
"nice-grpc-error-details": "^0.2.4",
|
34
|
+
"nice-grpc-opentelemetry": "^0.1.15",
|
35
|
+
"nice-grpc-prometheus": "^0.2.2",
|
36
|
+
"protobufjs": "^7.2.6",
|
37
|
+
"rxjs": "^7.8.1",
|
38
|
+
"utility-types": "^3.11.0",
|
39
|
+
"winston": "^3.11.0",
|
40
|
+
"@sentio/protos": "2.57.12-rc.a"
|
41
|
+
},
|
18
42
|
"devDependencies": {
|
19
43
|
"@types/command-line-args": "^5.2.3",
|
20
44
|
"@types/command-line-usage": "^5.0.4",
|
21
45
|
"@types/fs-extra": "^11.0.4",
|
22
|
-
"@types/google-protobuf": "^3.15.12"
|
46
|
+
"@types/google-protobuf": "^3.15.12",
|
47
|
+
"piscina": "5.0.0-alpha.0"
|
23
48
|
},
|
24
49
|
"engines": {
|
25
50
|
"node": ">=20"
|
package/src/processor-runner.ts
CHANGED
@@ -22,10 +22,11 @@ import { setupLogger } from './logger.js'
|
|
22
22
|
|
23
23
|
import { setupOTLP } from './otlp.js'
|
24
24
|
import { ActionServer } from './action-server.js'
|
25
|
+
import { ServiceManager } from './service-manager.js'
|
25
26
|
|
26
27
|
// const mergedRegistry = Registry.merge([globalRegistry, niceGrpcRegistry])
|
27
28
|
|
28
|
-
const optionDefinitions = [
|
29
|
+
export const optionDefinitions = [
|
29
30
|
{ name: 'target', type: String, defaultOption: true },
|
30
31
|
{ name: 'port', alias: 'p', type: String, defaultValue: '4000' },
|
31
32
|
{ name: 'concurrency', type: Number, defaultValue: 4 },
|
@@ -42,7 +43,8 @@ const optionDefinitions = [
|
|
42
43
|
{ name: 'log-format', type: String, defaultValue: 'console' },
|
43
44
|
{ name: 'debug', type: Boolean, defaultValue: false },
|
44
45
|
{ name: 'otlp-debug', type: Boolean, defaultValue: false },
|
45
|
-
{ name: 'start-action-server', type: Boolean, defaultValue: false }
|
46
|
+
{ name: 'start-action-server', type: Boolean, defaultValue: false },
|
47
|
+
{ name: 'worker', type: Number, defaultValue: 8 }
|
46
48
|
]
|
47
49
|
|
48
50
|
const options = commandLineArgs(optionDefinitions, { partial: true })
|
@@ -88,7 +90,7 @@ for (const [id, config] of Object.entries(chainsConfig)) {
|
|
88
90
|
console.debug('Starting Server', options)
|
89
91
|
|
90
92
|
let server: any
|
91
|
-
let baseService: ProcessorServiceImpl
|
93
|
+
let baseService: ProcessorServiceImpl | ServiceManager
|
92
94
|
const loader = async () => {
|
93
95
|
const m = await import(options.target)
|
94
96
|
console.debug('Module loaded', m)
|
@@ -106,7 +108,13 @@ if (options['start-action-server']) {
|
|
106
108
|
// .use(prometheusServerMiddleware())
|
107
109
|
.use(openTelemetryServerMiddleware())
|
108
110
|
.use(errorDetailsServerMiddleware)
|
109
|
-
|
111
|
+
|
112
|
+
if (options.worker > 1) {
|
113
|
+
baseService = new ServiceManager(options, loader, server.shutdown)
|
114
|
+
} else {
|
115
|
+
baseService = new ProcessorServiceImpl(loader, server.shutdown)
|
116
|
+
}
|
117
|
+
|
110
118
|
const service = new FullProcessorServiceImpl(baseService)
|
111
119
|
|
112
120
|
server.add(ProcessorDefinition, service)
|
@@ -0,0 +1,263 @@
|
|
1
|
+
import { CallContext, ServerError, Status } from 'nice-grpc'
|
2
|
+
import { DebugInfo, RichServerError } from 'nice-grpc-error-details'
|
3
|
+
import { from } from 'ix/Ix.asynciterable'
|
4
|
+
import { withAbort } from 'ix/Ix.asynciterable.operators'
|
5
|
+
import { Piscina } from 'piscina'
|
6
|
+
|
7
|
+
import {
|
8
|
+
DataBinding,
|
9
|
+
DeepPartial,
|
10
|
+
Empty,
|
11
|
+
HandlerType,
|
12
|
+
PreparedData,
|
13
|
+
PreprocessStreamRequest,
|
14
|
+
ProcessBindingResponse,
|
15
|
+
ProcessBindingsRequest,
|
16
|
+
ProcessConfigRequest,
|
17
|
+
ProcessConfigResponse,
|
18
|
+
ProcessorServiceImplementation,
|
19
|
+
ProcessResult,
|
20
|
+
ProcessStreamRequest,
|
21
|
+
ProcessStreamResponse,
|
22
|
+
StartRequest
|
23
|
+
} from '@sentio/protos'
|
24
|
+
|
25
|
+
import { PluginManager } from './plugin.js'
|
26
|
+
import { errorString, mergeProcessResults } from './utils.js'
|
27
|
+
import { GLOBAL_CONFIG } from './global-config.js'
|
28
|
+
|
29
|
+
import { StoreContext } from './db-context.js'
|
30
|
+
import { Subject } from 'rxjs'
|
31
|
+
|
32
|
+
import { processMetrics } from './metrics.js'
|
33
|
+
|
34
|
+
const { process_binding_count, process_binding_time, process_binding_error } = processMetrics
|
35
|
+
|
36
|
+
;(BigInt.prototype as any).toJSON = function () {
|
37
|
+
return this.toString()
|
38
|
+
}
|
39
|
+
|
40
|
+
export class ServiceManager implements ProcessorServiceImplementation {
|
41
|
+
private started = false
|
42
|
+
// When there is unhandled error, stop process and return unavailable error
|
43
|
+
unhandled: Error
|
44
|
+
// private processorConfig: ProcessConfigResponse
|
45
|
+
private readonly pool: Piscina<any, any>
|
46
|
+
|
47
|
+
constructor(
|
48
|
+
readonly options: any,
|
49
|
+
readonly loader: () => Promise<any>,
|
50
|
+
readonly shutdownHandler?: () => void
|
51
|
+
) {
|
52
|
+
this.pool = new Piscina({
|
53
|
+
maxThreads: options.worker,
|
54
|
+
minThreads: options.worker,
|
55
|
+
filename: new URL('./service-worker.js', import.meta.url).href
|
56
|
+
})
|
57
|
+
}
|
58
|
+
|
59
|
+
async *preprocessBindingsStream(requests: AsyncIterable<PreprocessStreamRequest>, context: CallContext) {
|
60
|
+
throw new Error('not supported')
|
61
|
+
}
|
62
|
+
|
63
|
+
async getConfig(request: ProcessConfigRequest, context: CallContext): Promise<ProcessConfigResponse> {
|
64
|
+
if (!this.started) {
|
65
|
+
throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
|
66
|
+
}
|
67
|
+
|
68
|
+
// broadcast to all worker
|
69
|
+
const bc = new BroadcastChannel('configure_channel')
|
70
|
+
bc.postMessage(request)
|
71
|
+
|
72
|
+
// also configure the main thread
|
73
|
+
const newConfig = ProcessConfigResponse.fromPartial({})
|
74
|
+
await PluginManager.INSTANCE.configure(newConfig)
|
75
|
+
return newConfig
|
76
|
+
}
|
77
|
+
|
78
|
+
//
|
79
|
+
// async configure() {
|
80
|
+
// this.processorConfig = ProcessConfigResponse.fromPartial({})
|
81
|
+
// await PluginManager.INSTANCE.configure(this.processorConfig)
|
82
|
+
// }
|
83
|
+
|
84
|
+
async start(request: StartRequest, context: CallContext): Promise<Empty> {
|
85
|
+
if (this.started) {
|
86
|
+
return {}
|
87
|
+
}
|
88
|
+
|
89
|
+
try {
|
90
|
+
await this.loader()
|
91
|
+
} catch (e) {
|
92
|
+
throw new ServerError(Status.INVALID_ARGUMENT, 'Failed to load processor: ' + errorString(e))
|
93
|
+
}
|
94
|
+
|
95
|
+
// broadcast to all worker
|
96
|
+
const bc = new BroadcastChannel('start_channel')
|
97
|
+
bc.postMessage(request)
|
98
|
+
|
99
|
+
// also start the processor in main thread
|
100
|
+
await PluginManager.INSTANCE.start(request)
|
101
|
+
this.started = true
|
102
|
+
return {}
|
103
|
+
}
|
104
|
+
|
105
|
+
async stop(request: Empty, context: CallContext): Promise<Empty> {
|
106
|
+
console.log('Server Shutting down in 5 seconds')
|
107
|
+
if (this.shutdownHandler) {
|
108
|
+
setTimeout(this.shutdownHandler, 5000)
|
109
|
+
}
|
110
|
+
return {}
|
111
|
+
}
|
112
|
+
|
113
|
+
async processBindings(request: ProcessBindingsRequest, options?: CallContext): Promise<ProcessBindingResponse> {
|
114
|
+
const promises = []
|
115
|
+
for (const binding of request.bindings) {
|
116
|
+
const promise = this.processBinding(binding, undefined)
|
117
|
+
if (GLOBAL_CONFIG.execution.sequential) {
|
118
|
+
await promise
|
119
|
+
}
|
120
|
+
promises.push(promise)
|
121
|
+
}
|
122
|
+
let promise
|
123
|
+
try {
|
124
|
+
promise = await Promise.all(promises)
|
125
|
+
processMetrics.process_binding_count.add(request.bindings.length)
|
126
|
+
} catch (e) {
|
127
|
+
processMetrics.process_binding_error.add(request.bindings.length)
|
128
|
+
throw e
|
129
|
+
}
|
130
|
+
const result = mergeProcessResults(promise)
|
131
|
+
|
132
|
+
// let updated = false
|
133
|
+
// if (PluginManager.INSTANCE.stateDiff(this.processorConfig)) {
|
134
|
+
// await this.configure()
|
135
|
+
// updated = true
|
136
|
+
// }
|
137
|
+
|
138
|
+
return {
|
139
|
+
result
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
async processBinding(
|
144
|
+
request: DataBinding,
|
145
|
+
preparedData: PreparedData | undefined,
|
146
|
+
options?: CallContext
|
147
|
+
): Promise<ProcessResult> {
|
148
|
+
if (!this.started) {
|
149
|
+
throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
|
150
|
+
}
|
151
|
+
if (this.unhandled) {
|
152
|
+
throw new RichServerError(
|
153
|
+
Status.UNAVAILABLE,
|
154
|
+
'Unhandled exception/rejection in previous request: ' + errorString(this.unhandled),
|
155
|
+
[
|
156
|
+
DebugInfo.fromPartial({
|
157
|
+
detail: this.unhandled.message,
|
158
|
+
stackEntries: this.unhandled.stack?.split('\n')
|
159
|
+
})
|
160
|
+
]
|
161
|
+
)
|
162
|
+
}
|
163
|
+
|
164
|
+
return await this.pool.run({ request, preparedData }, { name: 'processBinding' })
|
165
|
+
}
|
166
|
+
|
167
|
+
async *processBindingsStream(requests: AsyncIterable<ProcessStreamRequest>, context: CallContext) {
|
168
|
+
if (!this.started) {
|
169
|
+
throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
|
170
|
+
}
|
171
|
+
|
172
|
+
const subject = new Subject<DeepPartial<ProcessStreamResponse>>()
|
173
|
+
this.handleRequests(requests, subject)
|
174
|
+
.then(() => {
|
175
|
+
subject.complete()
|
176
|
+
})
|
177
|
+
.catch((e) => {
|
178
|
+
console.error(e)
|
179
|
+
subject.error(e)
|
180
|
+
})
|
181
|
+
yield* from(subject).pipe(withAbort(context.signal))
|
182
|
+
}
|
183
|
+
|
184
|
+
private async handleRequests(
|
185
|
+
requests: AsyncIterable<ProcessStreamRequest>,
|
186
|
+
subject: Subject<DeepPartial<ProcessStreamResponse>>
|
187
|
+
) {
|
188
|
+
const contexts = new Contexts()
|
189
|
+
for await (const request of requests) {
|
190
|
+
try {
|
191
|
+
// console.debug('received request:', request)
|
192
|
+
if (request.binding) {
|
193
|
+
process_binding_count.add(1)
|
194
|
+
|
195
|
+
// Adjust binding will make some request become invalid by setting UNKNOWN HandlerType
|
196
|
+
// for older SDK version, so we just return empty result for them here
|
197
|
+
if (request.binding.handlerType === HandlerType.UNKNOWN) {
|
198
|
+
subject.next({
|
199
|
+
processId: request.processId,
|
200
|
+
result: ProcessResult.create()
|
201
|
+
})
|
202
|
+
continue
|
203
|
+
}
|
204
|
+
|
205
|
+
const binding = request.binding
|
206
|
+
// todo support db request
|
207
|
+
// const dbContext = contexts.new(request.processId, subject)
|
208
|
+
const start = Date.now()
|
209
|
+
await this.processBinding(binding, undefined)
|
210
|
+
.then(async (result) => {
|
211
|
+
// await all pending db requests
|
212
|
+
// await dbContext.awaitPendings()
|
213
|
+
subject.next({
|
214
|
+
result,
|
215
|
+
processId: request.processId
|
216
|
+
})
|
217
|
+
})
|
218
|
+
.catch((e) => {
|
219
|
+
console.debug(e)
|
220
|
+
// dbContext.error(request.processId, e)
|
221
|
+
process_binding_error.add(1)
|
222
|
+
})
|
223
|
+
.finally(() => {
|
224
|
+
const cost = Date.now() - start
|
225
|
+
process_binding_time.add(cost)
|
226
|
+
contexts.delete(request.processId)
|
227
|
+
})
|
228
|
+
}
|
229
|
+
if (request.dbResult) {
|
230
|
+
const dbContext = contexts.get(request.processId)
|
231
|
+
try {
|
232
|
+
dbContext?.result(request.dbResult)
|
233
|
+
} catch (e) {
|
234
|
+
subject.error(new Error('db result error, process should stop'))
|
235
|
+
}
|
236
|
+
}
|
237
|
+
} catch (e) {
|
238
|
+
// should not happen
|
239
|
+
console.error('unexpect error during handle loop', e)
|
240
|
+
}
|
241
|
+
}
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
class Contexts {
|
246
|
+
private contexts: Map<number, StoreContext> = new Map()
|
247
|
+
|
248
|
+
get(processId: number) {
|
249
|
+
return this.contexts.get(processId)
|
250
|
+
}
|
251
|
+
|
252
|
+
new(processId: number, subject: Subject<DeepPartial<ProcessStreamResponse>>) {
|
253
|
+
const context = new StoreContext(subject, processId)
|
254
|
+
this.contexts.set(processId, context)
|
255
|
+
return context
|
256
|
+
}
|
257
|
+
|
258
|
+
delete(processId: number) {
|
259
|
+
const context = this.get(processId)
|
260
|
+
context?.close()
|
261
|
+
this.contexts.delete(processId)
|
262
|
+
}
|
263
|
+
}
|
@@ -0,0 +1,116 @@
|
|
1
|
+
import {
|
2
|
+
DataBinding,
|
3
|
+
Empty,
|
4
|
+
HandlerType,
|
5
|
+
PreparedData,
|
6
|
+
ProcessConfigRequest,
|
7
|
+
ProcessConfigResponse,
|
8
|
+
ProcessResult,
|
9
|
+
StartRequest
|
10
|
+
} from '@sentio/protos'
|
11
|
+
import { CallContext, ServerError, Status } from 'nice-grpc'
|
12
|
+
import { PluginManager } from './plugin.js'
|
13
|
+
import commandLineArgs from 'command-line-args'
|
14
|
+
import { optionDefinitions } from './processor-runner.js'
|
15
|
+
import { errorString } from './utils.js'
|
16
|
+
import { freezeGlobalConfig } from './global-config.js'
|
17
|
+
import { DebugInfo, RichServerError } from 'nice-grpc-error-details'
|
18
|
+
import { recordRuntimeInfo } from './service.js'
|
19
|
+
import { BroadcastChannel } from 'worker_threads'
|
20
|
+
|
21
|
+
let started = false
|
22
|
+
const options = commandLineArgs(optionDefinitions, { partial: true })
|
23
|
+
|
24
|
+
let unhandled: Error | undefined
|
25
|
+
|
26
|
+
process
|
27
|
+
.on('uncaughtException', (err) => {
|
28
|
+
console.error('Uncaught Exception, please checking if await is properly used', err)
|
29
|
+
unhandled = err
|
30
|
+
})
|
31
|
+
.on('unhandledRejection', (reason, p) => {
|
32
|
+
// @ts-ignore ignore invalid ens error
|
33
|
+
if (reason?.message.startsWith('invalid ENS name (disallowed character: "*"')) {
|
34
|
+
return
|
35
|
+
}
|
36
|
+
console.error('Unhandled Rejection, please checking if await is properly', reason)
|
37
|
+
unhandled = reason as Error
|
38
|
+
// shutdownServers(1)
|
39
|
+
})
|
40
|
+
|
41
|
+
async function getConfig(request: ProcessConfigRequest, context?: CallContext): Promise<ProcessConfigResponse> {
|
42
|
+
if (!started) {
|
43
|
+
throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
|
44
|
+
}
|
45
|
+
|
46
|
+
const newConfig = ProcessConfigResponse.fromPartial({})
|
47
|
+
await PluginManager.INSTANCE.configure(newConfig)
|
48
|
+
return newConfig
|
49
|
+
}
|
50
|
+
|
51
|
+
const loader = async () => {
|
52
|
+
const m = await import(options.target)
|
53
|
+
console.debug('Module loaded', m)
|
54
|
+
return m
|
55
|
+
}
|
56
|
+
|
57
|
+
const configureChannel = new BroadcastChannel('configure_channel')
|
58
|
+
configureChannel.onmessage = (request: ProcessConfigRequest) => {
|
59
|
+
getConfig(request)
|
60
|
+
}
|
61
|
+
|
62
|
+
async function start(request: StartRequest): Promise<Empty> {
|
63
|
+
if (started) {
|
64
|
+
return {}
|
65
|
+
}
|
66
|
+
freezeGlobalConfig()
|
67
|
+
|
68
|
+
try {
|
69
|
+
await loader()
|
70
|
+
} catch (e) {
|
71
|
+
throw new ServerError(Status.INVALID_ARGUMENT, 'Failed to load processor: ' + errorString(e))
|
72
|
+
}
|
73
|
+
|
74
|
+
await PluginManager.INSTANCE.start(request)
|
75
|
+
started = true
|
76
|
+
return {}
|
77
|
+
}
|
78
|
+
|
79
|
+
const startChannel = new BroadcastChannel('start_channel')
|
80
|
+
startChannel.onmessage = (request: StartRequest) => {
|
81
|
+
start(request)
|
82
|
+
}
|
83
|
+
|
84
|
+
async function stop(request: Empty, context: CallContext): Promise<Empty> {
|
85
|
+
return {}
|
86
|
+
}
|
87
|
+
|
88
|
+
async function processBinding(
|
89
|
+
request: DataBinding,
|
90
|
+
preparedData: PreparedData | undefined,
|
91
|
+
options?: CallContext
|
92
|
+
): Promise<ProcessResult> {
|
93
|
+
if (!started) {
|
94
|
+
throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
|
95
|
+
}
|
96
|
+
if (unhandled) {
|
97
|
+
throw new RichServerError(
|
98
|
+
Status.UNAVAILABLE,
|
99
|
+
'Unhandled exception/rejection in previous request: ' + errorString(unhandled),
|
100
|
+
[
|
101
|
+
DebugInfo.fromPartial({
|
102
|
+
detail: unhandled.message,
|
103
|
+
stackEntries: unhandled.stack?.split('\n')
|
104
|
+
})
|
105
|
+
]
|
106
|
+
)
|
107
|
+
}
|
108
|
+
|
109
|
+
const result = await PluginManager.INSTANCE.processBinding(
|
110
|
+
request,
|
111
|
+
preparedData
|
112
|
+
// PluginManager.INSTANCE.dbContextLocalStorage.getStore()
|
113
|
+
)
|
114
|
+
recordRuntimeInfo(result, request.handlerType)
|
115
|
+
return result
|
116
|
+
}
|
package/src/service.ts
CHANGED
@@ -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 = {
|