@sentio/runtime 4.0.0-rc.2 → 4.0.0-rc.3
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-OLNJTVP4.js +15167 -0
- package/lib/chunk-OLNJTVP4.js.map +1 -0
- package/lib/{chunk-MSVN53DI.js → chunk-VVWSRCNO.js} +1 -5
- package/lib/{chunk-MSVN53DI.js.map → chunk-VVWSRCNO.js.map} +1 -1
- package/lib/index.d.ts +62 -408
- package/lib/index.js +21290 -40
- package/lib/index.js.map +1 -1
- package/lib/processor-runner.js +25 -235
- package/lib/processor-runner.js.map +1 -1
- package/package.json +1 -1
- package/src/full-service.ts +0 -35
- package/src/index.ts +1 -1
- package/src/processor-runner.ts +2 -16
- package/src/service-v3.ts +3 -3
- package/src/utils.ts +21 -1
- package/lib/chunk-63SN5KU2.js +0 -38205
- package/lib/chunk-63SN5KU2.js.map +0 -1
- package/src/service.ts +0 -556
package/src/service.ts
DELETED
|
@@ -1,556 +0,0 @@
|
|
|
1
|
-
import { ConnectError, Code, type HandlerContext, type ServiceImpl } from '@connectrpc/connect'
|
|
2
|
-
import { from } from 'ix/Ix.asynciterable'
|
|
3
|
-
import { withAbort } from 'ix/Ix.asynciterable.operators'
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
type DataBinding,
|
|
7
|
-
type Empty,
|
|
8
|
-
EmptySchema,
|
|
9
|
-
type EthCallParam,
|
|
10
|
-
HandlerType,
|
|
11
|
-
type PreparedData,
|
|
12
|
-
PreparedDataSchema,
|
|
13
|
-
type PreprocessResult,
|
|
14
|
-
type PreprocessStreamRequest,
|
|
15
|
-
ProcessBindingResponseSchema,
|
|
16
|
-
type ProcessBindingsRequest,
|
|
17
|
-
type ProcessConfigRequest,
|
|
18
|
-
type ProcessConfigResponse,
|
|
19
|
-
ProcessConfigResponseSchema,
|
|
20
|
-
Processor,
|
|
21
|
-
type ProcessResult,
|
|
22
|
-
ProcessResultSchema,
|
|
23
|
-
RuntimeInfoSchema,
|
|
24
|
-
type ProcessStreamRequest,
|
|
25
|
-
ProcessStreamResponseSchema,
|
|
26
|
-
PreprocessStreamResponseSchema,
|
|
27
|
-
type StartRequest
|
|
28
|
-
} from '@sentio/protos'
|
|
29
|
-
import { create, type MessageInitShape } from '@bufbuild/protobuf'
|
|
30
|
-
|
|
31
|
-
import { PluginManager } from './plugin.js'
|
|
32
|
-
import { errorString, makeEthCallKey, mergeProcessResults } from './utils.js'
|
|
33
|
-
import { freezeGlobalConfig, GLOBAL_CONFIG } from './global-config.js'
|
|
34
|
-
|
|
35
|
-
import { StoreContext } from './db-context.js'
|
|
36
|
-
import { Subject } from 'rxjs'
|
|
37
|
-
import { getProvider } from './provider.js'
|
|
38
|
-
import { EthChainId } from '@sentio/chain'
|
|
39
|
-
import { Provider } from 'ethers'
|
|
40
|
-
import { decodeMulticallResult, encodeMulticallData, getMulticallAddress, Multicall3Call } from './multicall.js'
|
|
41
|
-
|
|
42
|
-
import { processMetrics } from './metrics.js'
|
|
43
|
-
import { ProcessorRuntimeOptions } from './processor-runner-program.js'
|
|
44
|
-
|
|
45
|
-
const { process_binding_count, process_binding_time, process_binding_error } = processMetrics
|
|
46
|
-
|
|
47
|
-
;(BigInt.prototype as any).toJSON = function () {
|
|
48
|
-
return this.toString()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Init-shapes carried over the rxjs Subject before being yielded by connect.
|
|
52
|
-
// connect accepts MessageInitShape for streaming outputs, so the oneof discriminated
|
|
53
|
-
// union must be filled in (e.g. { value: { case: 'result', value: ... } }).
|
|
54
|
-
export type ProcessStreamResponseInit = MessageInitShape<typeof ProcessStreamResponseSchema>
|
|
55
|
-
export type PreprocessStreamResponseInit = MessageInitShape<typeof PreprocessStreamResponseSchema>
|
|
56
|
-
|
|
57
|
-
export class ProcessorServiceImpl implements ServiceImpl<typeof Processor> {
|
|
58
|
-
private started = false
|
|
59
|
-
// When there is unhandled error, stop process and return unavailable error
|
|
60
|
-
unhandled: Error
|
|
61
|
-
// private processorConfig: ProcessConfigResponse
|
|
62
|
-
|
|
63
|
-
private readonly loader: () => Promise<any>
|
|
64
|
-
|
|
65
|
-
private readonly shutdownHandler?: () => void
|
|
66
|
-
|
|
67
|
-
private readonly enablePreprocess: boolean
|
|
68
|
-
|
|
69
|
-
private preparedData: PreparedData | undefined
|
|
70
|
-
readonly enablePartition: boolean
|
|
71
|
-
|
|
72
|
-
constructor(loader: () => Promise<any>, options?: ProcessorRuntimeOptions, shutdownHandler?: () => void) {
|
|
73
|
-
this.loader = loader
|
|
74
|
-
this.shutdownHandler = shutdownHandler
|
|
75
|
-
|
|
76
|
-
this.enablePreprocess = process.env['ENABLE_PREPROCESS']
|
|
77
|
-
? process.env['ENABLE_PREPROCESS'].toLowerCase() == 'true'
|
|
78
|
-
: false
|
|
79
|
-
|
|
80
|
-
this.enablePartition = options?.enablePartition == true
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async getConfig(request: ProcessConfigRequest, context: HandlerContext): Promise<ProcessConfigResponse> {
|
|
84
|
-
if (!this.started) {
|
|
85
|
-
throw new ConnectError('Service Not started.', Code.Unavailable)
|
|
86
|
-
}
|
|
87
|
-
// if (!this.processorConfig) {
|
|
88
|
-
// throw new ConnectError('Process config empty.', Code.Internal)
|
|
89
|
-
// }
|
|
90
|
-
|
|
91
|
-
// Don't use .create to keep compatiblity
|
|
92
|
-
const newConfig = create(ProcessConfigResponseSchema, {})
|
|
93
|
-
await PluginManager.INSTANCE.configure(newConfig)
|
|
94
|
-
return newConfig
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
// async configure() {
|
|
99
|
-
// this.processorConfig = ProcessConfigResponse.fromPartial({})
|
|
100
|
-
// await PluginManager.INSTANCE.configure(this.processorConfig)
|
|
101
|
-
// }
|
|
102
|
-
|
|
103
|
-
async start(request: StartRequest, context: HandlerContext): Promise<MessageInitShape<typeof EmptySchema>> {
|
|
104
|
-
if (this.started) {
|
|
105
|
-
return {}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
freezeGlobalConfig()
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
// for (const plugin of ['@sentio/sdk', '@sentio/sdk/eth']) {
|
|
112
|
-
// try {
|
|
113
|
-
// await import(plugin)
|
|
114
|
-
// } catch (e) {
|
|
115
|
-
// console.error('Failed to load plugin: ', plugin)
|
|
116
|
-
// }
|
|
117
|
-
// }
|
|
118
|
-
//
|
|
119
|
-
// for (const plugin of ['@sentio/sdk/aptos', '@sentio/sdk/solana']) {
|
|
120
|
-
// try {
|
|
121
|
-
// await import(plugin)
|
|
122
|
-
// } catch (e) {}
|
|
123
|
-
// }
|
|
124
|
-
|
|
125
|
-
await this.loader()
|
|
126
|
-
} catch (e) {
|
|
127
|
-
throw new ConnectError('Failed to load processor: ' + errorString(e), Code.InvalidArgument)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
await PluginManager.INSTANCE.start(request)
|
|
131
|
-
|
|
132
|
-
// try {
|
|
133
|
-
// await this.configure()
|
|
134
|
-
// } catch (e) {
|
|
135
|
-
// throw new ConnectError('Failed to start processor : ' + errorString(e), Code.Internal)
|
|
136
|
-
// }
|
|
137
|
-
this.started = true
|
|
138
|
-
return {}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async stop(request: Empty, context: HandlerContext): Promise<MessageInitShape<typeof EmptySchema>> {
|
|
142
|
-
console.log('Server Shutting down in 5 seconds')
|
|
143
|
-
if (this.shutdownHandler) {
|
|
144
|
-
setTimeout(this.shutdownHandler, 5000)
|
|
145
|
-
}
|
|
146
|
-
return {}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async processBindings(
|
|
150
|
-
request: ProcessBindingsRequest,
|
|
151
|
-
context?: HandlerContext
|
|
152
|
-
): Promise<MessageInitShape<typeof ProcessBindingResponseSchema>> {
|
|
153
|
-
const preparedData = this.enablePreprocess
|
|
154
|
-
? await this.preprocessBindings(request.bindings, {}, undefined, context)
|
|
155
|
-
: create(PreparedDataSchema, { ethCallResults: {} })
|
|
156
|
-
|
|
157
|
-
const promises = []
|
|
158
|
-
for (const binding of request.bindings) {
|
|
159
|
-
const promise = this.processBinding(binding, preparedData)
|
|
160
|
-
if (GLOBAL_CONFIG.execution.sequential) {
|
|
161
|
-
await promise
|
|
162
|
-
}
|
|
163
|
-
promises.push(promise)
|
|
164
|
-
}
|
|
165
|
-
let promise
|
|
166
|
-
try {
|
|
167
|
-
promise = await Promise.all(promises)
|
|
168
|
-
processMetrics.process_binding_count.add(request.bindings.length)
|
|
169
|
-
} catch (e) {
|
|
170
|
-
processMetrics.process_binding_error.add(request.bindings.length)
|
|
171
|
-
throw e
|
|
172
|
-
}
|
|
173
|
-
const result = mergeProcessResults(promise)
|
|
174
|
-
|
|
175
|
-
// let updated = false
|
|
176
|
-
// if (PluginManager.INSTANCE.stateDiff(this.processorConfig)) {
|
|
177
|
-
// await this.configure()
|
|
178
|
-
// updated = true
|
|
179
|
-
// }
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
result
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
async preprocessBindings(
|
|
187
|
-
bindings: DataBinding[],
|
|
188
|
-
preprocessStore: { [k: string]: any },
|
|
189
|
-
dbContext?: StoreContext,
|
|
190
|
-
options?: HandlerContext
|
|
191
|
-
): Promise<PreparedData> {
|
|
192
|
-
// console.debug(`preprocessBindings start, bindings: ${bindings.length}`)
|
|
193
|
-
const promises = []
|
|
194
|
-
for (const binding of bindings) {
|
|
195
|
-
promises.push(this.preprocessBinding(binding, preprocessStore, dbContext, options))
|
|
196
|
-
}
|
|
197
|
-
let preprocessResults: PreprocessResult[]
|
|
198
|
-
try {
|
|
199
|
-
preprocessResults = await Promise.all(promises)
|
|
200
|
-
} catch (e) {
|
|
201
|
-
throw e
|
|
202
|
-
}
|
|
203
|
-
const groupedRequests = new Map<string, EthCallParam[]>()
|
|
204
|
-
const providers = new Map<string, Provider>()
|
|
205
|
-
for (const result of preprocessResults) {
|
|
206
|
-
for (const param of result.ethCallParams) {
|
|
207
|
-
const { chainId, blockTag } = param.context!
|
|
208
|
-
if (!providers.has(chainId)) {
|
|
209
|
-
providers.set(chainId, getProvider(chainId as EthChainId))
|
|
210
|
-
}
|
|
211
|
-
const key = [chainId, blockTag].join('|')
|
|
212
|
-
if (!groupedRequests.has(key)) {
|
|
213
|
-
groupedRequests.set(key, [])
|
|
214
|
-
}
|
|
215
|
-
groupedRequests.get(key)!.push(param)
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const start = Date.now()
|
|
220
|
-
const MULTICALL_THRESHOLD = 1
|
|
221
|
-
const callPromises: Promise<[string, string]>[] = []
|
|
222
|
-
const multicallPromises: Promise<[string, string][]>[] = []
|
|
223
|
-
|
|
224
|
-
for (const params of groupedRequests.values()) {
|
|
225
|
-
const { chainId, blockTag } = params[0].context!
|
|
226
|
-
const multicallAddress = getMulticallAddress(chainId as EthChainId)
|
|
227
|
-
if (params.length <= MULTICALL_THRESHOLD || !multicallAddress) {
|
|
228
|
-
for (const param of params) {
|
|
229
|
-
callPromises.push(
|
|
230
|
-
providers
|
|
231
|
-
.get(chainId)!
|
|
232
|
-
.call({
|
|
233
|
-
to: param.context!.address,
|
|
234
|
-
data: param.calldata,
|
|
235
|
-
blockTag
|
|
236
|
-
})
|
|
237
|
-
.then((result) => [makeEthCallKey(param), result])
|
|
238
|
-
)
|
|
239
|
-
}
|
|
240
|
-
continue
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// construct multicalls
|
|
244
|
-
const CHUNK_SIZE = 128
|
|
245
|
-
for (let i = 0; i < params.length; i += CHUNK_SIZE) {
|
|
246
|
-
const chunk = params.slice(i, i + CHUNK_SIZE)
|
|
247
|
-
const calls: Multicall3Call[] = chunk.map((param) => ({
|
|
248
|
-
target: param.context!.address,
|
|
249
|
-
callData: param.calldata
|
|
250
|
-
}))
|
|
251
|
-
const data = encodeMulticallData(calls)
|
|
252
|
-
multicallPromises.push(
|
|
253
|
-
providers
|
|
254
|
-
.get(chainId)!
|
|
255
|
-
.call({
|
|
256
|
-
to: multicallAddress,
|
|
257
|
-
data: data,
|
|
258
|
-
blockTag
|
|
259
|
-
})
|
|
260
|
-
.then((raw) => {
|
|
261
|
-
const result = decodeMulticallResult(raw).returnData
|
|
262
|
-
if (result.length != chunk.length) {
|
|
263
|
-
throw new Error(`multicall result length mismatch, params: ${chunk.length}, result: ${result.length}`)
|
|
264
|
-
}
|
|
265
|
-
const ret: [string, string][] = []
|
|
266
|
-
for (let i = 0; i < chunk.length; i++) {
|
|
267
|
-
ret.push([makeEthCallKey(chunk[i]), result[i]])
|
|
268
|
-
}
|
|
269
|
-
return ret
|
|
270
|
-
})
|
|
271
|
-
)
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
let results: { [p: string]: string } = {}
|
|
276
|
-
try {
|
|
277
|
-
results = Object.fromEntries(await Promise.all(callPromises))
|
|
278
|
-
for (const multicallResult of await Promise.all(multicallPromises)) {
|
|
279
|
-
results = {
|
|
280
|
-
...results,
|
|
281
|
-
...Object.fromEntries(multicallResult)
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
} catch (e) {
|
|
285
|
-
console.error(`eth call error: ${e}`)
|
|
286
|
-
}
|
|
287
|
-
// console.debug(
|
|
288
|
-
// `${Object.keys(results).length} calls finished, actual calls: ${callPromises.length + multicallPromises.length}, elapsed: ${Date.now() - start}ms`
|
|
289
|
-
// )
|
|
290
|
-
return create(PreparedDataSchema, {
|
|
291
|
-
ethCallResults: results
|
|
292
|
-
})
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
async preprocessBinding(
|
|
296
|
-
request: DataBinding,
|
|
297
|
-
preprocessStore: { [k: string]: any },
|
|
298
|
-
dbContext?: StoreContext,
|
|
299
|
-
options?: HandlerContext
|
|
300
|
-
): Promise<PreprocessResult> {
|
|
301
|
-
if (!this.started) {
|
|
302
|
-
throw new ConnectError('Service Not started.', Code.Unavailable)
|
|
303
|
-
}
|
|
304
|
-
if (this.unhandled) {
|
|
305
|
-
throw new ConnectError(
|
|
306
|
-
'Unhandled exception/rejection in previous request: ' + errorString(this.unhandled),
|
|
307
|
-
Code.Unavailable
|
|
308
|
-
)
|
|
309
|
-
}
|
|
310
|
-
return await PluginManager.INSTANCE.preprocessBinding(request, preprocessStore, dbContext)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
async processBinding(
|
|
314
|
-
request: DataBinding,
|
|
315
|
-
preparedData: PreparedData | undefined,
|
|
316
|
-
options?: HandlerContext
|
|
317
|
-
): Promise<ProcessResult> {
|
|
318
|
-
if (!this.started) {
|
|
319
|
-
throw new ConnectError('Service Not started.', Code.Unavailable)
|
|
320
|
-
}
|
|
321
|
-
if (this.unhandled) {
|
|
322
|
-
throw new ConnectError(
|
|
323
|
-
'Unhandled exception/rejection in previous request: ' + errorString(this.unhandled),
|
|
324
|
-
Code.Unavailable
|
|
325
|
-
)
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const result = await PluginManager.INSTANCE.processBinding(
|
|
329
|
-
request,
|
|
330
|
-
preparedData,
|
|
331
|
-
PluginManager.INSTANCE.dbContextLocalStorage.getStore()
|
|
332
|
-
)
|
|
333
|
-
recordRuntimeInfo(result, request.handlerType)
|
|
334
|
-
return result
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
async *processBindingsStream(requests: AsyncIterable<ProcessStreamRequest>, context: HandlerContext) {
|
|
338
|
-
if (!this.started) {
|
|
339
|
-
throw new ConnectError('Service Not started.', Code.Unavailable)
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const subject = new Subject<ProcessStreamResponseInit>()
|
|
343
|
-
this.handleRequests(requests, subject)
|
|
344
|
-
.then(() => {
|
|
345
|
-
if (this.preparedData) {
|
|
346
|
-
this.preparedData = create(PreparedDataSchema, { ethCallResults: {} })
|
|
347
|
-
}
|
|
348
|
-
subject.complete()
|
|
349
|
-
})
|
|
350
|
-
.catch((e) => {
|
|
351
|
-
console.error(e)
|
|
352
|
-
subject.error(e)
|
|
353
|
-
})
|
|
354
|
-
yield* from(subject).pipe(withAbort(context.signal))
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
async handlePreprocessRequests(
|
|
358
|
-
requests: AsyncIterable<PreprocessStreamRequest>,
|
|
359
|
-
subject: Subject<PreprocessStreamResponseInit>
|
|
360
|
-
) {
|
|
361
|
-
const contexts = new Contexts()
|
|
362
|
-
const preprocessStore: { [k: string]: any } = {}
|
|
363
|
-
|
|
364
|
-
for await (const request of requests) {
|
|
365
|
-
try {
|
|
366
|
-
if (request.value.case === 'bindings') {
|
|
367
|
-
const bindings = request.value.value.bindings
|
|
368
|
-
// NOTE: StoreContext/Contexts are typed for the V2 ProcessStreamResponse stream, but the
|
|
369
|
-
// preprocess flow reuses them only to drive DB request/response plumbing. The preprocess
|
|
370
|
-
// stream message (flat `dbRequest`) differs from the V2 oneof shape, so we hand the
|
|
371
|
-
// preprocess subject in via a cast. db-context.ts owns the actual emit shape; integrator
|
|
372
|
-
// should confirm StoreContext.doSend stays compatible with both stream message types.
|
|
373
|
-
const dbContext = contexts.new(request.processId, subject as unknown as Subject<ProcessStreamResponseInit>)
|
|
374
|
-
const start = Date.now()
|
|
375
|
-
this.preprocessBindings(bindings, preprocessStore, dbContext, undefined)
|
|
376
|
-
.then((preparedData) => {
|
|
377
|
-
// TODO maybe not proper to pass data in this way
|
|
378
|
-
this.preparedData = create(PreparedDataSchema, {
|
|
379
|
-
ethCallResults: {
|
|
380
|
-
...this.preparedData?.ethCallResults,
|
|
381
|
-
...preparedData.ethCallResults
|
|
382
|
-
}
|
|
383
|
-
})
|
|
384
|
-
subject.next({
|
|
385
|
-
processId: request.processId
|
|
386
|
-
})
|
|
387
|
-
})
|
|
388
|
-
.catch((e) => {
|
|
389
|
-
console.debug(e)
|
|
390
|
-
dbContext.error(request.processId, e)
|
|
391
|
-
})
|
|
392
|
-
.finally(() => {
|
|
393
|
-
const cost = Date.now() - start
|
|
394
|
-
console.debug('preprocessBinding', request.processId, ' took', cost, 'ms')
|
|
395
|
-
contexts.delete(request.processId)
|
|
396
|
-
})
|
|
397
|
-
}
|
|
398
|
-
if (request.value.case === 'dbResult') {
|
|
399
|
-
const dbContext = contexts.get(request.processId)
|
|
400
|
-
dbContext?.result(request.value.value)
|
|
401
|
-
}
|
|
402
|
-
} catch (e) {
|
|
403
|
-
// should not happen
|
|
404
|
-
console.error('unexpect error during handle loop', e)
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
async *preprocessBindingsStream(requests: AsyncIterable<PreprocessStreamRequest>, context: HandlerContext) {
|
|
410
|
-
if (!this.started) {
|
|
411
|
-
throw new ConnectError('Service Not started.', Code.Unavailable)
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
const subject = new Subject<PreprocessStreamResponseInit>()
|
|
415
|
-
this.handlePreprocessRequests(requests, subject)
|
|
416
|
-
.then(() => {
|
|
417
|
-
subject.complete()
|
|
418
|
-
})
|
|
419
|
-
.catch((e) => {
|
|
420
|
-
console.error(e)
|
|
421
|
-
subject.error(e)
|
|
422
|
-
})
|
|
423
|
-
yield* from(subject).pipe(withAbort(context.signal))
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
private dbContexts = new Contexts()
|
|
427
|
-
|
|
428
|
-
protected async handleRequests(
|
|
429
|
-
requests: AsyncIterable<ProcessStreamRequest>,
|
|
430
|
-
subject: Subject<ProcessStreamResponseInit>
|
|
431
|
-
) {
|
|
432
|
-
let lastBinding: DataBinding | undefined = undefined
|
|
433
|
-
for await (const request of requests) {
|
|
434
|
-
try {
|
|
435
|
-
// console.log('received request:', request, 'lastBinding:', lastBinding)
|
|
436
|
-
if (request.value.case === 'binding') {
|
|
437
|
-
lastBinding = request.value.value
|
|
438
|
-
}
|
|
439
|
-
this.handleRequest(request, lastBinding, subject)
|
|
440
|
-
} catch (e) {
|
|
441
|
-
// should not happen
|
|
442
|
-
console.error('unexpect error during handle loop', e)
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
async handleRequest(
|
|
448
|
-
request: ProcessStreamRequest,
|
|
449
|
-
lastBinding: DataBinding | undefined,
|
|
450
|
-
subject: Subject<ProcessStreamResponseInit>
|
|
451
|
-
) {
|
|
452
|
-
if (request.value.case === 'binding') {
|
|
453
|
-
const binding = request.value.value
|
|
454
|
-
process_binding_count.add(1)
|
|
455
|
-
|
|
456
|
-
// Adjust binding will make some request become invalid by setting UNKNOWN HandlerType
|
|
457
|
-
// for older SDK version, so we just return empty result for them here
|
|
458
|
-
if (binding.handlerType === HandlerType.UNKNOWN) {
|
|
459
|
-
subject.next({
|
|
460
|
-
processId: request.processId,
|
|
461
|
-
value: { case: 'result', value: create(ProcessResultSchema) }
|
|
462
|
-
})
|
|
463
|
-
return
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
if (this.enablePartition) {
|
|
467
|
-
try {
|
|
468
|
-
const partitions = await PluginManager.INSTANCE.partition(binding)
|
|
469
|
-
subject.next({
|
|
470
|
-
processId: request.processId,
|
|
471
|
-
value: { case: 'partitions', value: partitions }
|
|
472
|
-
})
|
|
473
|
-
} catch (e) {
|
|
474
|
-
console.error('Partition error:', e)
|
|
475
|
-
subject.error(new Error('Partition error: ' + errorString(e)))
|
|
476
|
-
return
|
|
477
|
-
}
|
|
478
|
-
} else {
|
|
479
|
-
this.startProcess(request.processId, binding, subject)
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (request.value.case === 'start') {
|
|
484
|
-
if (!lastBinding) {
|
|
485
|
-
console.error('start request received without binding')
|
|
486
|
-
subject.error(new Error('start request received without binding'))
|
|
487
|
-
return
|
|
488
|
-
}
|
|
489
|
-
this.startProcess(request.processId, lastBinding, subject)
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
if (request.value.case === 'dbResult') {
|
|
493
|
-
const dbContext = this.dbContexts.get(request.processId)
|
|
494
|
-
try {
|
|
495
|
-
dbContext?.result(request.value.value)
|
|
496
|
-
} catch (e) {
|
|
497
|
-
subject.error(new Error('db result error, process should stop'))
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
private startProcess(processId: number, binding: DataBinding, subject: Subject<ProcessStreamResponseInit>) {
|
|
503
|
-
const dbContext = this.dbContexts.new(processId, subject)
|
|
504
|
-
const start = Date.now()
|
|
505
|
-
PluginManager.INSTANCE.processBinding(binding, this.preparedData, dbContext)
|
|
506
|
-
.then(async (result) => {
|
|
507
|
-
// await all pending db requests
|
|
508
|
-
await dbContext.awaitPendings()
|
|
509
|
-
subject.next({
|
|
510
|
-
value: { case: 'result', value: result },
|
|
511
|
-
processId: processId
|
|
512
|
-
})
|
|
513
|
-
recordRuntimeInfo(result, binding.handlerType)
|
|
514
|
-
})
|
|
515
|
-
.catch((e) => {
|
|
516
|
-
console.error(e, e.stack)
|
|
517
|
-
dbContext.error(processId, e)
|
|
518
|
-
process_binding_error.add(1)
|
|
519
|
-
})
|
|
520
|
-
.finally(() => {
|
|
521
|
-
const cost = Date.now() - start
|
|
522
|
-
process_binding_time.add(cost)
|
|
523
|
-
this.dbContexts.delete(processId)
|
|
524
|
-
})
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
export function recordRuntimeInfo(results: ProcessResult, handlerType: HandlerType) {
|
|
529
|
-
for (const list of [results.gauges, results.counters, results.events, results.exports]) {
|
|
530
|
-
list.forEach((e) => {
|
|
531
|
-
e.runtimeInfo = create(RuntimeInfoSchema, {
|
|
532
|
-
from: handlerType
|
|
533
|
-
})
|
|
534
|
-
})
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
class Contexts {
|
|
539
|
-
private contexts: Map<number, StoreContext> = new Map()
|
|
540
|
-
|
|
541
|
-
get(processId: number) {
|
|
542
|
-
return this.contexts.get(processId)
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
new(processId: number, subject: Subject<ProcessStreamResponseInit>) {
|
|
546
|
-
const context = new StoreContext(subject, processId)
|
|
547
|
-
this.contexts.set(processId, context)
|
|
548
|
-
return context
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
delete(processId: number) {
|
|
552
|
-
const context = this.get(processId)
|
|
553
|
-
context?.close()
|
|
554
|
-
this.contexts.delete(processId)
|
|
555
|
-
}
|
|
556
|
-
}
|