@sentio/runtime 4.3.5-rc.2 → 4.3.5-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/dist/index.js +1 -1
- package/dist/plugin-0w1rBRGy.d.ts.map +1 -1
- package/dist/processor-runner.js +1 -1
- package/dist/{service-v3-piYZ9lK8.js → service-v3-hRmfCtqr.js} +36 -2
- package/dist/{service-v3-piYZ9lK8.js.map → service-v3-hRmfCtqr.js.map} +1 -1
- package/package.json +1 -1
- package/src/db-context.ts +63 -8
package/package.json
CHANGED
package/src/db-context.ts
CHANGED
|
@@ -36,6 +36,19 @@ type ProcessStreamResponseV3Init = MessageInitShape<typeof ProcessStreamResponse
|
|
|
36
36
|
type Request = NonNullable<MessageInitShape<typeof DBRequestSchema>['op']>
|
|
37
37
|
type RequestType = NonNullable<Request['case']>
|
|
38
38
|
|
|
39
|
+
// Both ends of a stack carry the useful information, so a deep one is trimmed in the
|
|
40
|
+
// middle rather than at the tail: the top frames are where the late call was actually
|
|
41
|
+
// made (runtime frames are already trimmed off by captureStackTrace), and the bottom
|
|
42
|
+
// ones are the entry point that led there — which is the closest thing to naming the
|
|
43
|
+
// handler that the stack can offer, on the occasions it has not been lost.
|
|
44
|
+
const MAX_HEAD_FRAMES = 8
|
|
45
|
+
const MAX_TAIL_FRAMES = 4
|
|
46
|
+
|
|
47
|
+
// V8 keeps only the innermost `Error.stackTraceLimit` frames — 10 by default — so on a
|
|
48
|
+
// deep stack the entry point is discarded before we ever see it. Raised just for this
|
|
49
|
+
// capture, since keeping both ends is the entire point, and restored immediately after.
|
|
50
|
+
const STACK_CAPTURE_DEPTH = 50
|
|
51
|
+
|
|
39
52
|
export const timeoutError = new Error('timeout')
|
|
40
53
|
|
|
41
54
|
// Name the entity and id, not just the op kind: "get BinanceAlphaPriceEntity 56-0x…"
|
|
@@ -58,6 +71,34 @@ function describeRequest(request: Request): string {
|
|
|
58
71
|
}
|
|
59
72
|
}
|
|
60
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Keep both ends of a stack, dropping the middle when it is too long to inline.
|
|
76
|
+
*
|
|
77
|
+
* A diagnostic must not grow with the depth of what it describes, but truncating from one
|
|
78
|
+
* end throws away half of what makes it useful: the top frames are where the late call was
|
|
79
|
+
* made, the bottom ones the entry point that led there.
|
|
80
|
+
*
|
|
81
|
+
* Node's own internals are dropped first so the budget goes to frames the reader can act
|
|
82
|
+
* on — otherwise a deep call inside the test runner or the processor host fills the tail
|
|
83
|
+
* with `node:internal/...` and pushes the user's entry point into the omitted middle.
|
|
84
|
+
* V8-synthesised markers like `async Promise.all (index 3)` are not `node:` frames and are
|
|
85
|
+
* kept; they are often the single most informative line.
|
|
86
|
+
*/
|
|
87
|
+
function boundedFrames(frames: string[]): string[] {
|
|
88
|
+
const ownCode = frames.filter((frame) => !frame.includes('node:'))
|
|
89
|
+
// If everything was internal there is nothing better to show.
|
|
90
|
+
const usable = ownCode.length ? ownCode : frames
|
|
91
|
+
if (usable.length <= MAX_HEAD_FRAMES + MAX_TAIL_FRAMES) {
|
|
92
|
+
return usable
|
|
93
|
+
}
|
|
94
|
+
const omitted = usable.length - MAX_HEAD_FRAMES - MAX_TAIL_FRAMES
|
|
95
|
+
return [
|
|
96
|
+
...usable.slice(0, MAX_HEAD_FRAMES),
|
|
97
|
+
` ... ${omitted} frame(s) omitted ...`,
|
|
98
|
+
...usable.slice(-MAX_TAIL_FRAMES)
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
|
|
61
102
|
export interface IStoreContext {
|
|
62
103
|
sendRequest(request: Request, timeoutSecs?: number): Promise<DBResponse>
|
|
63
104
|
|
|
@@ -325,15 +366,29 @@ export abstract class AbstractStoreContext implements IStoreContext {
|
|
|
325
366
|
* reads is their own code rather than two frames of ours.
|
|
326
367
|
*/
|
|
327
368
|
protected reportLateMessage(what: string, boundary?: (...args: never[]) => unknown): Error {
|
|
328
|
-
const
|
|
329
|
-
`[sentio] ${what} was issued after process ${this.processId} had already finished, so it was
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
369
|
+
const summary =
|
|
370
|
+
`[sentio] ${what} was issued after process ${this.processId} had already finished, so it was ` +
|
|
371
|
+
`dropped. Something in the handler was still running after the handler returned — every store ` +
|
|
372
|
+
`write, store read and metric must be awaited before the handler completes. A common cause is ` +
|
|
373
|
+
`one Promise.all() branch rejecting while its siblings keep running: Promise.all rejects ` +
|
|
374
|
+
`immediately but does NOT cancel the others, so give each branch its own .catch() (or use ` +
|
|
375
|
+
`Promise.allSettled) to keep them inside the handler.`
|
|
376
|
+
|
|
377
|
+
const previousLimit = Error.stackTraceLimit
|
|
378
|
+
Error.stackTraceLimit = Math.max(previousLimit, STACK_CAPTURE_DEPTH)
|
|
379
|
+
const err = new Error(summary)
|
|
336
380
|
Error.captureStackTrace?.(err, boundary ?? this.reportLateMessage)
|
|
381
|
+
// Read `stack` before restoring: the frames are captured at construction, but forcing
|
|
382
|
+
// the (lazy) formatting here keeps this independent of when anyone reads it.
|
|
383
|
+
const captured = err.stack ?? ''
|
|
384
|
+
Error.stackTraceLimit = previousLimit
|
|
385
|
+
|
|
386
|
+
// Inline the frames into `message`. The datasource log view renders only the message,
|
|
387
|
+
// so a stack left in `err.stack` alone is invisible to the user who has to act on it.
|
|
388
|
+
const frames = boundedFrames(captured.split('\n').slice(1))
|
|
389
|
+
if (frames.length) {
|
|
390
|
+
err.message = `${summary}\nIssued at:\n${frames.join('\n')}`
|
|
391
|
+
}
|
|
337
392
|
console.error(err)
|
|
338
393
|
return err
|
|
339
394
|
}
|