@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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{plugin-7YBR3bUA.d.ts → plugin-0w1rBRGy.d.ts} +34 -2
- package/dist/plugin-0w1rBRGy.d.ts.map +1 -0
- package/dist/processor-runner.js +1 -1
- package/dist/{service-v3-D6oVpUbL.js → service-v3-piYZ9lK8.js} +64 -1
- package/dist/{service-v3-D6oVpUbL.js.map → service-v3-piYZ9lK8.js.map} +1 -1
- package/package.json +1 -1
- package/src/db-context.ts +84 -1
- package/src/service-v3.ts +6 -0
- package/dist/plugin-7YBR3bUA.d.ts.map +0 -1
package/package.json
CHANGED
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
|
-
|
|
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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"}
|