Summary
ReadOptions.runTime is documented and typed, but is unreachable whenever the SDK runs in a Lambda — the auto-derived stopTime pre-empts it. Confirmed identical in 7.1.18 and the current 7.1.21.
The code
lib/stream/leo-stream.js, fromLeo (7.1.21, lines 1355-1363):
fromLeo: (ID, queue, opts) => {
opts = Object.assign({}, opts || {});
queue = refUtil.ref(queue).queue(opts.subqueue).id;
if (!opts.stopTime && configure.registry && configure.registry.context) {
opts.stopTime = moment.now() + (configure.registry.context.getRemainingTimeInMillis() * 0.8);
}
if (!opts.stopTime && opts.runTime) { // <-- unreachable in Lambda
opts.stopTime = moment().add(opts.runTime).valueOf();
}
In a Lambda, configure.registry.context is always present, so the first branch always assigns opts.stopTime. The second branch is guarded on !opts.stopTime and therefore never executes. A bot passing runTime gets the hardcoded 0.8 × getRemainingTimeInMillis() instead, silently.
Why this is surprising
lib/lib.d.ts documents runTime as the intended knob for exactly this, and even prescribes the ratio the hardcoded value implements:
The duration of time the to read for before closing the read stream. It is common to set this to 75% to 80% of the time remaining before the lambda is shut down to give the lambda sufficient time to finish processing. Of course, different types of processing will differ.
The read stream will shutdown as soon as one of the constraints is met: runTime, loops, limit, size, stopTime.
"Of course, different types of processing will differ" is the case that cannot currently be expressed. A bot whose per-batch processing is expensive wants a ratio below 0.8 so a batch admitted just under the deadline can still finish and checkpoint. Passing runTime: '5m' to a 15-minute Lambda looks like it does that and does nothing.
There's no config for the ratio either — 0.8 is a literal with no override; runTime is the only documented way to influence it.
Repro
// in a Lambda with a 900s timeout
sdk.enrichEvents({
id: botId, inQueue: 'q-in', outQueue: 'q-out',
config: { runTime: '5m' },
transform: (payload, meta, done) => done(null, true),
});
Expected: read stream closes at ~300s.
Actual: closes at ~720s (0.8 × 900s). logger.info(opts) on line 1365 shows stopTime already set and runTime ignored.
Workaround
Pass stopTime explicitly, since it's the value the first branch guards on:
config: { stopTime: Date.now() + context.getRemainingTimeInMillis() * 0.5 }
Suggested fix
Let an explicitly-passed runTime win over the derived default, e.g.:
if (!opts.stopTime && opts.runTime) {
opts.stopTime = moment().add(opts.runTime).valueOf();
}
if (!opts.stopTime && configure.registry && configure.registry.context) {
opts.stopTime = moment.now() + (configure.registry.context.getRemainingTimeInMillis() * 0.8);
}
i.e. swap the two blocks so the caller's explicit option takes precedence and the context-derived value stays the fallback. That preserves current behavior for every bot that doesn't pass runTime.
Optionally, also expose the ratio (e.g. runTimePercent) so bots can tune the reserve without computing an absolute epoch time.
Environment
leo-sdk 7.1.18 and 7.1.21
- Node 22, AWS Lambda
Summary
ReadOptions.runTimeis documented and typed, but is unreachable whenever the SDK runs in a Lambda — the auto-derivedstopTimepre-empts it. Confirmed identical in7.1.18and the current7.1.21.The code
lib/stream/leo-stream.js,fromLeo(7.1.21, lines 1355-1363):In a Lambda,
configure.registry.contextis always present, so the first branch always assignsopts.stopTime. The second branch is guarded on!opts.stopTimeand therefore never executes. A bot passingrunTimegets the hardcoded0.8 × getRemainingTimeInMillis()instead, silently.Why this is surprising
lib/lib.d.tsdocumentsrunTimeas the intended knob for exactly this, and even prescribes the ratio the hardcoded value implements:"Of course, different types of processing will differ" is the case that cannot currently be expressed. A bot whose per-batch processing is expensive wants a ratio below 0.8 so a batch admitted just under the deadline can still finish and checkpoint. Passing
runTime: '5m'to a 15-minute Lambda looks like it does that and does nothing.There's no config for the ratio either —
0.8is a literal with no override;runTimeis the only documented way to influence it.Repro
Expected: read stream closes at ~300s.
Actual: closes at ~720s (
0.8 × 900s).logger.info(opts)on line 1365 showsstopTimealready set andrunTimeignored.Workaround
Pass
stopTimeexplicitly, since it's the value the first branch guards on:Suggested fix
Let an explicitly-passed
runTimewin over the derived default, e.g.:i.e. swap the two blocks so the caller's explicit option takes precedence and the context-derived value stays the fallback. That preserves current behavior for every bot that doesn't pass
runTime.Optionally, also expose the ratio (e.g.
runTimePercent) so bots can tune the reserve without computing an absolute epoch time.Environment
leo-sdk7.1.18 and 7.1.21