@sentio/runtime 4.3.5-rc.1 → 4.3.5-rc.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentio/runtime",
3
- "version": "4.3.5-rc.1",
3
+ "version": "4.3.5-rc.2",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
package/src/db-context.ts CHANGED
@@ -38,6 +38,26 @@ type RequestType = NonNullable<Request['case']>
38
38
 
39
39
  export const timeoutError = new Error('timeout')
40
40
 
41
+ // Name the entity and id, not just the op kind: "get BinanceAlphaPriceEntity 56-0x…"
42
+ // usually points straight at the call site, while a bare "get" rarely does.
43
+ function describeRequest(request: Request): string {
44
+ switch (request.case) {
45
+ case 'get':
46
+ return `get ${request.value.entity} ${request.value.id}`
47
+ case 'list':
48
+ return `list ${request.value.entity}`
49
+ case 'upsert':
50
+ case 'update':
51
+ case 'delete': {
52
+ const entities = [...new Set(request.value.entity ?? [])].join(',')
53
+ return `${request.case} ${entities} (${request.value.id?.length ?? 0} row(s))`
54
+ }
55
+ default:
56
+ // The oneof can be unset, in which case `case` is undefined.
57
+ return request.case ?? 'op'
58
+ }
59
+ }
60
+
41
61
  export interface IStoreContext {
42
62
  sendRequest(request: Request, timeoutSecs?: number): Promise<DBResponse>
43
63
 
@@ -64,7 +84,7 @@ export abstract class AbstractStoreContext implements IStoreContext {
64
84
  // Set once the process has finished and the context was closed. Guards
65
85
  // against a lingering batch timer emitting after the final result (which
66
86
  // would both lose the write and desync the reused processor stream).
67
- private closed = false
87
+ protected closed = false
68
88
 
69
89
  constructor(readonly processId: number) {}
70
90
 
@@ -78,6 +98,10 @@ export abstract class AbstractStoreContext implements IStoreContext {
78
98
  abstract doSend(resp: ProcessStreamResponseInit | ProcessStreamResponseV3Init): void
79
99
 
80
100
  sendRequest(request: Request, timeoutSecs?: number): Promise<DBResponse> {
101
+ if (this.closed) {
102
+ return Promise.reject(this.reportLateMessage(`store ${describeRequest(request)}`, this.sendRequest))
103
+ }
104
+
81
105
  if (STORE_BATCH_IDLE > 0 && STORE_BATCH_SIZE > 1 && request.case === 'upsert') {
82
106
  // batch upsert if possible
83
107
  return this.sendUpsertInBatch(request.value as DBRequest_DBUpsert)
@@ -169,6 +193,21 @@ export abstract class AbstractStoreContext implements IStoreContext {
169
193
  this.doSend({ value: { case: 'result', value: errorResult }, processId })
170
194
  }
171
195
 
196
+ /**
197
+ * Mark the process finished. Must be called synchronously *immediately before*
198
+ * the final result is emitted — not from a later `.finally()`.
199
+ *
200
+ * Once the result is on the stream the driver may hand that stream to another
201
+ * process, so nothing may follow it. The gap between emitting the result and
202
+ * close() running is several microtasks wide, which is more than enough for a
203
+ * detached continuation to wake up and slip a request through: without this the
204
+ * observed outbound order is ["result", "dbRequest"], exactly what the guards
205
+ * exist to prevent. close() still does the teardown afterwards.
206
+ */
207
+ finish() {
208
+ this.closed = true
209
+ }
210
+
172
211
  close() {
173
212
  this.closed = true
174
213
  // Drop any un-flushed batch and cancel its timer so it can never emit
@@ -266,6 +305,39 @@ export abstract class AbstractStoreContext implements IStoreContext {
266
305
  }
267
306
  }
268
307
 
308
+ /**
309
+ * Report a message the process tried to emit after it had already finished, and
310
+ * return the Error describing it.
311
+ *
312
+ * Such a message cannot be delivered: the final result was already sent, and the
313
+ * stream it would go out on has been handed back to the pool and may now belong to
314
+ * another process — writing to it makes the driver fail *that* process with ERR200
315
+ * "unexpected ProcessID". Dropping it is the only safe option.
316
+ *
317
+ * This always logs rather than relying solely on the rejection, because the late
318
+ * call is nearly always on a path that already swallows errors — the classic case
319
+ * being one `Promise.all` branch rejecting while its siblings keep running — so the
320
+ * rejection alone would be invisible. The Error is built here, at the call site, so
321
+ * its stack points into the handler code that failed to await.
322
+ *
323
+ * `boundary` is the runtime entry point the caller came through; its frame and
324
+ * everything below it is trimmed from the stack so the very first line the user
325
+ * reads is their own code rather than two frames of ours.
326
+ */
327
+ protected reportLateMessage(what: string, boundary?: (...args: never[]) => unknown): Error {
328
+ const err = new Error(
329
+ `[sentio] ${what} was issued after process ${this.processId} had already finished, so it was dropped. ` +
330
+ `Something in the handler was still running after the handler returned — every store write, ` +
331
+ `store read and metric must be awaited before the handler completes. A common cause is one ` +
332
+ `Promise.all() branch rejecting while its siblings keep running: Promise.all rejects immediately ` +
333
+ `but does NOT cancel the others, so give each branch its own .catch() (or use Promise.allSettled) ` +
334
+ `to keep them inside the handler. The stack below points at the call that arrived too late.`
335
+ )
336
+ Error.captureStackTrace?.(err, boundary ?? this.reportLateMessage)
337
+ console.error(err)
338
+ return err
339
+ }
340
+
269
341
  async awaitPendings() {
270
342
  // Flush any buffered upsert batch and wait for its ack before returning.
271
343
  // Callers use this to guarantee every write has been sent AND applied by
@@ -308,6 +380,13 @@ export class DataBindingContext extends AbstractStoreContext implements IDataBin
308
380
  }
309
381
 
310
382
  sendTemplateRequest(templates: Array<TemplateInstance>, unbind: boolean) {
383
+ if (this.closed) {
384
+ this.reportLateMessage(
385
+ `${unbind ? 'unbind' : 'bind'} of ${templates.length} template instance(s)`,
386
+ this.sendTemplateRequest
387
+ )
388
+ return
389
+ }
311
390
  this.subject.next({
312
391
  processId: this.processId,
313
392
  value: {
@@ -320,6 +399,10 @@ export class DataBindingContext extends AbstractStoreContext implements IDataBin
320
399
  })
321
400
  }
322
401
  sendTimeseriesRequest(timeseries: Array<TimeseriesResult>) {
402
+ if (this.closed) {
403
+ this.reportLateMessage(`${timeseries.length} timeseries record(s)`, this.sendTimeseriesRequest)
404
+ return
405
+ }
323
406
  this.subject.next({
324
407
  processId: this.processId,
325
408
  value: {
package/src/service-v3.ts CHANGED
@@ -181,6 +181,11 @@ export class ProcessorServiceImplV3 implements ServiceImpl<typeof ProcessorV3> {
181
181
  const otherResults = clone(ProcessResultSchema, result)
182
182
  otherResults.timeseriesResult = []
183
183
 
184
+ // Close the context to further messages in the same synchronous step that
185
+ // emits the result: after this point the stream may be recycled to another
186
+ // process, and waiting for the .finally() below would leave a microtask
187
+ // window in which a detached continuation could still append to it.
188
+ context.finish()
184
189
  subject.next({
185
190
  processId,
186
191
  value: {
@@ -191,6 +196,7 @@ export class ProcessorServiceImplV3 implements ServiceImpl<typeof ProcessorV3> {
191
196
  })
192
197
  .catch((e) => {
193
198
  console.error(e, e.stack)
199
+ context.finish() // same reasoning as the success path — error() emits a result too
194
200
  context.error(processId, e)
195
201
  process_binding_error.add(1)
196
202
  })
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin-7YBR3bUA.d.ts","names":[],"sources":["../src/db-context.ts","../src/plugin.ts"],"mappings":"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"}