@sentio/runtime 2.59.0-rc.39 → 2.59.0-rc.40

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,26 +1,21 @@
1
1
  import {
2
- DataBinding,
3
- DBResponse,
4
2
  DeepPartial,
5
3
  Empty,
6
4
  ProcessConfigRequest,
7
- ProcessConfigResponse,
8
- ProcessResult,
5
+ ProcessStreamRequest,
9
6
  ProcessStreamResponse,
10
- ProcessStreamResponse_Partitions,
11
7
  StartRequest
12
8
  } from '@sentio/protos'
13
9
  import { CallContext, ServerError, Status } from 'nice-grpc'
14
- import { PluginManager } from './plugin.js'
15
10
  import { errorString } from './utils.js'
16
11
  import { freezeGlobalConfig } from './global-config.js'
17
12
  import { DebugInfo, RichServerError } from 'nice-grpc-error-details'
18
- import { recordRuntimeInfo } from './service.js'
13
+ import { ProcessorServiceImpl } from './service.js'
19
14
  import { BroadcastChannel, MessagePort, threadId } from 'worker_threads'
20
15
  import { Piscina } from 'piscina'
21
16
  import { configureEndpoints } from './endpoints.js'
22
17
  import { setupLogger } from './logger.js'
23
- import { AbstractStoreContext } from './db-context.js'
18
+ import { Subject } from 'rxjs'
24
19
 
25
20
  let started = false
26
21
 
@@ -44,15 +39,7 @@ process
44
39
  console.info('Worker thread exiting, threadId:', threadId)
45
40
  })
46
41
 
47
- async function getConfig(request: ProcessConfigRequest, context?: CallContext): Promise<ProcessConfigResponse> {
48
- if (!started) {
49
- throw new ServerError(Status.UNAVAILABLE, 'Service Not started.')
50
- }
51
-
52
- const newConfig = ProcessConfigResponse.fromPartial({})
53
- await PluginManager.INSTANCE.configure(newConfig)
54
- return newConfig
55
- }
42
+ let service: ProcessorServiceImpl | undefined
56
43
 
57
44
  const loader = async (options: any) => {
58
45
  if (options.target) {
@@ -64,9 +51,11 @@ const loader = async (options: any) => {
64
51
 
65
52
  const configureChannel = new BroadcastChannel('configure_channel')
66
53
  configureChannel.onmessage = (request: ProcessConfigRequest) => {
67
- getConfig(request)
54
+ service?.getConfig(request, emptyCallContext)
68
55
  }
69
56
 
57
+ const emptyCallContext = <CallContext>{}
58
+
70
59
  async function start(request: StartRequest, options: any): Promise<Empty> {
71
60
  if (started) {
72
61
  return {}
@@ -74,26 +63,24 @@ async function start(request: StartRequest, options: any): Promise<Empty> {
74
63
  freezeGlobalConfig()
75
64
 
76
65
  try {
77
- await loader(options)
66
+ service = new ProcessorServiceImpl(() => loader(options), options)
78
67
  } catch (e) {
79
68
  throw new ServerError(Status.INVALID_ARGUMENT, 'Failed to load processor: ' + errorString(e))
80
69
  }
81
70
 
82
- await PluginManager.INSTANCE.start(request)
71
+ await service.start(request, emptyCallContext)
83
72
  started = true
84
73
  return {}
85
74
  }
86
75
 
87
76
  export default async function ({
88
- request,
89
77
  processId,
90
- workerPort,
91
- partition
78
+ request: firstRequest,
79
+ workerPort
92
80
  }: {
93
- request: DataBinding
94
81
  processId: number
95
- workerPort?: MessagePort
96
- partition?: boolean
82
+ request: ProcessStreamRequest
83
+ workerPort: MessagePort
97
84
  }) {
98
85
  const { startRequest, configRequest, options } = Piscina.workerData
99
86
  if (!started) {
@@ -108,7 +95,7 @@ export default async function ({
108
95
  }
109
96
 
110
97
  if (configRequest) {
111
- await getConfig(configRequest)
98
+ await service?.getConfig(configRequest, emptyCallContext)
112
99
  console.debug('worker', threadId, ' configured')
113
100
  }
114
101
  }
@@ -129,80 +116,20 @@ export default async function ({
129
116
  )
130
117
  }
131
118
 
132
- console.debug('Worker', threadId, 'starting request ', processId)
133
- const now = Date.now()
134
- const timeoutPromise = new Promise((resolve, reject) => {
135
- setTimeout(
136
- () => {
137
- reject(
138
- new RichServerError(
139
- Status.DEADLINE_EXCEEDED,
140
- `Request ${processId} timed out after ${options['request-timeout']} ms`
141
- )
142
- )
143
- },
144
- (options['process-timeout'] ?? 60) * 1000
145
- )
146
- })
147
- let resultPromise: Promise<ProcessResult | ProcessStreamResponse_Partitions>
148
- if (partition) {
149
- resultPromise = PluginManager.INSTANCE.partition(request)
150
- } else {
151
- resultPromise = PluginManager.INSTANCE.processBinding(
152
- request,
153
- undefined,
154
- workerPort ? new WorkerStoreContext(workerPort, processId) : undefined
155
- ).then((result) => {
156
- recordRuntimeInfo(result, request.handlerType)
157
- return result
119
+ await new Promise<void>((resolve) => {
120
+ const subject = new Subject<DeepPartial<ProcessStreamResponse>>()
121
+ subject.subscribe((resp: ProcessStreamResponse) => {
122
+ workerPort.postMessage(resp)
123
+ // receive the response from the processor , close and resolve the promise
124
+ if (resp.result) {
125
+ resolve()
126
+ workerPort.close()
127
+ }
158
128
  })
159
- }
160
-
161
- try {
162
- const result = await Promise.race([resultPromise, timeoutPromise])
163
- console.debug(
164
- 'worker',
165
- threadId,
166
- `finished ${partition ? 'partition' : 'process'} request`,
167
- processId,
168
- 'took',
169
- Date.now() - now
170
- )
171
- return result
172
- } catch (e) {
173
- console.error('worker', threadId, `failed ${partition ? 'partition' : 'process'} request`, processId, 'error:', e)
174
- if (e instanceof RichServerError) {
175
- throw e
176
- } else {
177
- throw new RichServerError(
178
- Status.INTERNAL,
179
- `Worker ${threadId} failed to process request ${processId}: ` + errorString(e),
180
- [
181
- DebugInfo.fromPartial({
182
- detail: errorString(e),
183
- stackEntries: e.stack?.split('\n')
184
- })
185
- ]
186
- )
187
- }
188
- }
189
- }
190
-
191
- class WorkerStoreContext extends AbstractStoreContext {
192
- constructor(
193
- readonly port: MessagePort,
194
- processId: number
195
- ) {
196
- super(processId)
197
- this.port.on('message', (resp: DBResponse) => {
198
- this.result(resp)
129
+ workerPort.on('message', (msg: ProcessStreamRequest) => {
130
+ const request = msg as ProcessStreamRequest
131
+ service?.handleRequest(request, firstRequest.binding, subject)
199
132
  })
200
- this.port.on('close', () => {
201
- this.close()
202
- })
203
- }
204
-
205
- doSend(req: DeepPartial<ProcessStreamResponse>): void {
206
- this.port.postMessage(req)
207
- }
133
+ service?.handleRequest(firstRequest, firstRequest.binding, subject)
134
+ })
208
135
  }
package/src/service.ts CHANGED
@@ -58,7 +58,7 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
58
58
  private preparedData: PreparedData | undefined
59
59
  readonly enablePartition: boolean
60
60
 
61
- constructor(loader: () => Promise<any>, shutdownHandler?: () => void) {
61
+ constructor(loader: () => Promise<any>, options?: any, shutdownHandler?: () => void) {
62
62
  this.loader = loader
63
63
  this.shutdownHandler = shutdownHandler
64
64
 
@@ -66,7 +66,7 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
66
66
  ? process.env['ENABLE_PREPROCESS'].toLowerCase() == 'true'
67
67
  : false
68
68
 
69
- this.enablePartition = process.env['SENTIO_ENABLE_BINDING_DATA_PARTITION'] == 'true'
69
+ this.enablePartition = options?.['enable-partition'] == true
70
70
  }
71
71
 
72
72
  async getConfig(request: ProcessConfigRequest, context: CallContext): Promise<ProcessConfigResponse> {
@@ -416,58 +416,20 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
416
416
  yield* from(subject).pipe(withAbort(context.signal))
417
417
  }
418
418
 
419
+ private dbContexts = new Contexts()
420
+
419
421
  protected async handleRequests(
420
422
  requests: AsyncIterable<ProcessStreamRequest>,
421
423
  subject: Subject<DeepPartial<ProcessStreamResponse>>
422
424
  ) {
423
- const contexts = new Contexts()
424
425
  let lastBinding: DataBinding | undefined = undefined
425
426
  for await (const request of requests) {
426
427
  try {
427
428
  // console.log('received request:', request, 'lastBinding:', lastBinding)
428
429
  if (request.binding) {
429
- process_binding_count.add(1)
430
-
431
- // Adjust binding will make some request become invalid by setting UNKNOWN HandlerType
432
- // for older SDK version, so we just return empty result for them here
433
- if (request.binding.handlerType === HandlerType.UNKNOWN) {
434
- subject.next({
435
- processId: request.processId,
436
- result: ProcessResult.create()
437
- })
438
- continue
439
- }
440
430
  lastBinding = request.binding
441
-
442
- if (this.enablePartition) {
443
- PluginManager.INSTANCE.partition(request.binding).then((partitions) => {
444
- subject.next({
445
- processId: request.processId,
446
- partitions
447
- })
448
- })
449
- } else {
450
- this.startProcess(request.processId, request.binding, contexts, subject)
451
- }
452
- }
453
-
454
- if (request.start) {
455
- if (!lastBinding) {
456
- console.error('start request received without binding')
457
- subject.error(new Error('start request received without binding'))
458
- continue
459
- }
460
- this.startProcess(request.processId, lastBinding, contexts, subject)
461
- }
462
-
463
- if (request.dbResult) {
464
- const dbContext = contexts.get(request.processId)
465
- try {
466
- dbContext?.result(request.dbResult)
467
- } catch (e) {
468
- subject.error(new Error('db result error, process should stop'))
469
- }
470
431
  }
432
+ this.handleRequest(request, lastBinding, subject)
471
433
  } catch (e) {
472
434
  // should not happen
473
435
  console.error('unexpect error during handle loop', e)
@@ -475,13 +437,62 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
475
437
  }
476
438
  }
477
439
 
478
- private startProcess(
479
- processId: number,
480
- binding: DataBinding,
481
- contexts: Contexts,
440
+ async handleRequest(
441
+ request: ProcessStreamRequest,
442
+ lastBinding: DataBinding | undefined,
482
443
  subject: Subject<DeepPartial<ProcessStreamResponse>>
483
444
  ) {
484
- const dbContext = contexts.new(processId, subject)
445
+ if (request.binding) {
446
+ process_binding_count.add(1)
447
+
448
+ // Adjust binding will make some request become invalid by setting UNKNOWN HandlerType
449
+ // for older SDK version, so we just return empty result for them here
450
+ if (request.binding.handlerType === HandlerType.UNKNOWN) {
451
+ subject.next({
452
+ processId: request.processId,
453
+ result: ProcessResult.create()
454
+ })
455
+ return
456
+ }
457
+
458
+ if (this.enablePartition) {
459
+ try {
460
+ const partitions = await PluginManager.INSTANCE.partition(request.binding)
461
+ subject.next({
462
+ processId: request.processId,
463
+ partitions
464
+ })
465
+ } catch (e) {
466
+ console.error('Partition error:', e)
467
+ subject.error(new Error('Partition error: ' + errorString(e)))
468
+ return
469
+ }
470
+ } else {
471
+ this.startProcess(request.processId, request.binding, subject)
472
+ }
473
+ }
474
+
475
+ if (request.start) {
476
+ if (!lastBinding) {
477
+ console.error('start request received without binding')
478
+ subject.error(new Error('start request received without binding'))
479
+ return
480
+ }
481
+ this.startProcess(request.processId, lastBinding, subject)
482
+ }
483
+
484
+ if (request.dbResult) {
485
+ const dbContext = this.dbContexts.get(request.processId)
486
+ try {
487
+ dbContext?.result(request.dbResult)
488
+ } catch (e) {
489
+ subject.error(new Error('db result error, process should stop'))
490
+ }
491
+ }
492
+ }
493
+
494
+ private startProcess(processId: number, binding: DataBinding, subject: Subject<DeepPartial<ProcessStreamResponse>>) {
495
+ const dbContext = this.dbContexts.new(processId, subject)
485
496
  const start = Date.now()
486
497
  PluginManager.INSTANCE.processBinding(binding, this.preparedData, dbContext)
487
498
  .then(async (result) => {
@@ -501,7 +512,7 @@ export class ProcessorServiceImpl implements ProcessorServiceImplementation {
501
512
  .finally(() => {
502
513
  const cost = Date.now() - start
503
514
  process_binding_time.add(cost)
504
- contexts.delete(processId)
515
+ this.dbContexts.delete(processId)
505
516
  })
506
517
  }
507
518
  }