@kensio/yulin 1.21.19 → 1.21.21

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.
Files changed (19) hide show
  1. package/dist/service/lambda/function/environment/sim-lambda-execution-credentials.d.ts +25 -0
  2. package/dist/service/lambda/function/environment/sim-lambda-execution-credentials.js +45 -0
  3. package/dist/service/lambda/function/environment/sim-lambda-host-backed-variables.d.ts +5 -0
  4. package/dist/service/lambda/function/environment/sim-lambda-host-backed-variables.js +7 -1
  5. package/dist/service/lambda/function/invoke/sim-lambda-invocation-deadline.d.ts +4 -0
  6. package/dist/service/lambda/function/invoke/sim-lambda-invocation-deadline.js +21 -0
  7. package/dist/service/lambda/function/invoke/sim-lambda-stall-watch.d.ts +38 -0
  8. package/dist/service/lambda/function/invoke/sim-lambda-stall-watch.js +78 -0
  9. package/dist/service/lambda/function/invoke/sim-lambda-stalled.error.d.ts +23 -0
  10. package/dist/service/lambda/function/invoke/sim-lambda-stalled.error.js +22 -0
  11. package/dist/service/lambda/function/invoke/timer/sim-lambda-clock-timer.d.ts +4 -0
  12. package/dist/service/lambda/function/invoke/timer/sim-lambda-clock-timer.js +8 -0
  13. package/dist/service/lambda/function/invoke/timer/sim-lambda-invocation-timers.d.ts +10 -0
  14. package/dist/service/lambda/function/invoke/timer/sim-lambda-invocation-timers.js +28 -0
  15. package/dist/service/lambda/function/invoke/timer/sim-lambda-pending-timer.d.ts +9 -0
  16. package/dist/service/lambda/function/invoke/timer/sim-lambda-pending-timer.js +1 -0
  17. package/docs/services/lambda/README.md +10 -0
  18. package/docs/time/README.md +6 -0
  19. package/package.json +1 -1
@@ -16,3 +16,28 @@ export declare const simLambdaExecutionCredentials: readonly (readonly [
16
16
  string,
17
17
  string
18
18
  ])[];
19
+ /**
20
+ * The host environment variable names that point an AWS SDK at credentials
21
+ * outside the invocation.
22
+ *
23
+ * A function declaring no variables of its own runs with the host process
24
+ * environment underneath the AWS-provided ones, which is where an in-process
25
+ * handler reads its configuration from. These names are not that
26
+ * configuration. They are how a developer's shell and a CI runner select the
27
+ * AWS access they hold themselves, and real Lambda sets none of them.
28
+ *
29
+ * `AWS_PROFILE` is the one that stops an invocation outright. The Node.js
30
+ * credential provider chain skips its environment-variable provider whenever
31
+ * that name is set, reporting "AWS_PROFILE is set, skipping fromEnv
32
+ * provider.", and the placeholder credentials above go unread. Resolution
33
+ * leaves the simulation for a shared configuration file or an SSO portal, and
34
+ * the client throws while resolving, before the request it was building
35
+ * reaches a simulated service. The rest each name a credential source of their
36
+ * own and are masked for the same reason.
37
+ */
38
+ export declare const hostAwsCredentialVariableNames: ReadonlySet<string>;
39
+ /**
40
+ * The host process variables an invocation keeps, with the ones naming a
41
+ * credential source of the host's own left out.
42
+ */
43
+ export declare function withoutHostAwsCredentialVariables(variables: Record<string, string>): Record<string, string>;
@@ -17,3 +17,48 @@ export const simLambdaExecutionCredentials = [
17
17
  ["AWS_SECRET_ACCESS_KEY", "yulinSimulatedSecretAccessKeyValue000000"],
18
18
  ["AWS_SESSION_TOKEN", "yulin-simulated-session-token"],
19
19
  ];
20
+ /**
21
+ * The host environment variable names that point an AWS SDK at credentials
22
+ * outside the invocation.
23
+ *
24
+ * A function declaring no variables of its own runs with the host process
25
+ * environment underneath the AWS-provided ones, which is where an in-process
26
+ * handler reads its configuration from. These names are not that
27
+ * configuration. They are how a developer's shell and a CI runner select the
28
+ * AWS access they hold themselves, and real Lambda sets none of them.
29
+ *
30
+ * `AWS_PROFILE` is the one that stops an invocation outright. The Node.js
31
+ * credential provider chain skips its environment-variable provider whenever
32
+ * that name is set, reporting "AWS_PROFILE is set, skipping fromEnv
33
+ * provider.", and the placeholder credentials above go unread. Resolution
34
+ * leaves the simulation for a shared configuration file or an SSO portal, and
35
+ * the client throws while resolving, before the request it was building
36
+ * reaches a simulated service. The rest each name a credential source of their
37
+ * own and are masked for the same reason.
38
+ */
39
+ export const hostAwsCredentialVariableNames = new Set([
40
+ "AWS_CONTAINER_AUTHORIZATION_TOKEN",
41
+ "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE",
42
+ "AWS_CONTAINER_CREDENTIALS_FULL_URI",
43
+ "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
44
+ "AWS_CONFIG_FILE",
45
+ "AWS_DEFAULT_PROFILE",
46
+ "AWS_PROFILE",
47
+ "AWS_ROLE_ARN",
48
+ "AWS_ROLE_SESSION_NAME",
49
+ "AWS_SHARED_CREDENTIALS_FILE",
50
+ "AWS_WEB_IDENTITY_TOKEN_FILE",
51
+ ]);
52
+ /**
53
+ * The host process variables an invocation keeps, with the ones naming a
54
+ * credential source of the host's own left out.
55
+ */
56
+ export function withoutHostAwsCredentialVariables(variables) {
57
+ const kept = [];
58
+ for (const [name, value] of Object.entries(variables)) {
59
+ if (!hostAwsCredentialVariableNames.has(name)) {
60
+ kept.push([name, value]);
61
+ }
62
+ }
63
+ return Object.fromEntries(kept);
64
+ }
@@ -6,6 +6,11 @@
6
6
  * merged once, so a variable the test process sets between two invocations
7
7
  * reaches the second of them.
8
8
  *
9
+ * The host's own AWS credential variables are left out. An invocation is
10
+ * attributed to the function's execution Role, and a name pointing an SDK at
11
+ * the credentials of whoever started the test process would make the
12
+ * invocation depend on them. See `hostAwsCredentialVariableNames`.
13
+ *
9
14
  * What the handler itself wrote is kept and laid over both, which is the warm
10
15
  * execution environment semantics a function declaring variables gets from
11
16
  * reusing one object. A name the handler wrote and the host also sets is the
@@ -1,4 +1,5 @@
1
1
  import { simProcessEnvironment } from "../../../../util/process/sim-process-environment.js";
2
+ import { withoutHostAwsCredentialVariables } from "./sim-lambda-execution-credentials.js";
2
3
  /**
3
4
  * The environment of a function that declares no variables of its own.
4
5
  *
@@ -7,6 +8,11 @@ import { simProcessEnvironment } from "../../../../util/process/sim-process-envi
7
8
  * merged once, so a variable the test process sets between two invocations
8
9
  * reaches the second of them.
9
10
  *
11
+ * The host's own AWS credential variables are left out. An invocation is
12
+ * attributed to the function's execution Role, and a name pointing an SDK at
13
+ * the credentials of whoever started the test process would make the
14
+ * invocation depend on them. See `hostAwsCredentialVariableNames`.
15
+ *
10
16
  * What the handler itself wrote is kept and laid over both, which is the warm
11
17
  * execution environment semantics a function declaring variables gets from
12
18
  * reusing one object. A name the handler wrote and the host also sets is the
@@ -19,7 +25,7 @@ export class SimLambdaHostBackedVariables {
19
25
  */
20
26
  async runWith(functionVariables, run) {
21
27
  const merged = {
22
- ...simProcessEnvironment.definedHostVariables(),
28
+ ...withoutHostAwsCredentialVariables(simProcessEnvironment.definedHostVariables()),
23
29
  ...functionVariables,
24
30
  ...Object.fromEntries(this.written),
25
31
  };
@@ -14,6 +14,10 @@ interface SimLambdaInvocationDeadlineProperties {
14
14
  * waits for a handler that has been timed out, and what it goes on to do
15
15
  * reaches nobody, which is the closest an in-process simulation gets to an
16
16
  * execution environment being taken away.
17
+ *
18
+ * Simulated time standing still holds the handler's timers and the deadline
19
+ * alike. A stall watch on host time ends an invocation neither of them can
20
+ * reach, with an error saying why.
17
21
  */
18
22
  export declare class SimLambdaInvocationDeadline {
19
23
  #private;
@@ -1,6 +1,8 @@
1
1
  import { SimLambdaClockTimer } from "./timer/sim-lambda-clock-timer.js";
2
2
  import { SimLambdaInvocationTimers } from "./timer/sim-lambda-invocation-timers.js";
3
3
  import { simLambdaProcessTimers } from "./timer/sim-lambda-process-timers.js";
4
+ import { SimLambdaStallWatch } from "./sim-lambda-stall-watch.js";
5
+ import { simLambdaStalledError } from "./sim-lambda-stalled.error.js";
4
6
  import { simLambdaTimedOutError } from "./sim-lambda-timed-out.error.js";
5
7
  const millisecondsPerSecond = 1000;
6
8
  /**
@@ -13,6 +15,10 @@ const millisecondsPerSecond = 1000;
13
15
  * waits for a handler that has been timed out, and what it goes on to do
14
16
  * reaches nobody, which is the closest an in-process simulation gets to an
15
17
  * execution environment being taken away.
18
+ *
19
+ * Simulated time standing still holds the handler's timers and the deadline
20
+ * alike. A stall watch on host time ends an invocation neither of them can
21
+ * reach, with an error saying why.
16
22
  */
17
23
  export class SimLambdaInvocationDeadline {
18
24
  #properties;
@@ -40,6 +46,7 @@ export class SimLambdaInvocationDeadline {
40
46
  const ending = () => {
41
47
  timers.cancelAll();
42
48
  deadline.cancel();
49
+ stall.cancel();
43
50
  };
44
51
  const failing = (error) => {
45
52
  ending();
@@ -59,7 +66,21 @@ export class SimLambdaInvocationDeadline {
59
66
  }));
60
67
  },
61
68
  });
69
+ const stall = new SimLambdaStallWatch({
70
+ background,
71
+ timeoutSeconds,
72
+ waitingOn: () => timers.pending(),
73
+ stalled: (waitingOn, waitedMilliseconds) => {
74
+ failing(simLambdaStalledError({
75
+ awsRequestId,
76
+ at: background.now(),
77
+ waitingOn,
78
+ waitedMilliseconds,
79
+ }));
80
+ },
81
+ });
62
82
  deadline.startAt(new Date(background.now().getTime() + timeoutSeconds * millisecondsPerSecond));
83
+ stall.start();
63
84
  try {
64
85
  return await background.outstanding(async () => await racing(timers, stopped, handler, ending));
65
86
  }
@@ -0,0 +1,38 @@
1
+ import type { BackgroundScheduler } from "../../../../util/background/background.js";
2
+ import type { SimLambdaPendingTimer } from "./timer/sim-lambda-pending-timer.js";
3
+ interface SimLambdaStallWatchProperties {
4
+ readonly background: BackgroundScheduler;
5
+ /** The seconds the invocation has to answer in, on the simulated clock. */
6
+ readonly timeoutSeconds: number;
7
+ /** The timer the invocation is waiting on, where it is waiting on one. */
8
+ readonly waitingOn: () => SimLambdaPendingTimer | undefined;
9
+ /** What happens once the invocation turns out to have no way to finish. */
10
+ readonly stalled: (waitingOn: SimLambdaPendingTimer, waitedMilliseconds: number) => void;
11
+ }
12
+ /**
13
+ * A watch on host time for an invocation the simulated clock has left behind.
14
+ *
15
+ * A handler's timers wait on the simulation's clock, and so does the
16
+ * invocation's deadline. Simulated time standing still holds both, and the
17
+ * invocation runs on until the test framework gives up on the test around it.
18
+ * Elapsed host time is the only thing separating that from a test meaning to
19
+ * start an invocation, advance the clock and then wait for it, which is why
20
+ * host time is what this watches.
21
+ *
22
+ * A turn that finds simulated time has moved starts the wait again, so a test
23
+ * advancing the clock in steps is left alone. The host timer is unreferenced,
24
+ * and an invocation nobody is driving never holds the process open.
25
+ */
26
+ export declare class SimLambdaStallWatch {
27
+ #private;
28
+ constructor(properties: SimLambdaStallWatchProperties);
29
+ /**
30
+ * Start watching, from the instant the clock reads now.
31
+ */
32
+ start(): void;
33
+ /**
34
+ * Give up watching, as an invocation that has ended does.
35
+ */
36
+ cancel(): void;
37
+ }
38
+ export {};
@@ -0,0 +1,78 @@
1
+ import { clearTimeout, setTimeout } from "node:timers";
2
+ const millisecondsPerSecond = 1000;
3
+ /** The least host time a stopped clock gets before an invocation is stalled. */
4
+ const shortestWatchMilliseconds = 1000;
5
+ /** The most host time a stopped clock gets, whatever the function's timeout. */
6
+ const longestWatchMilliseconds = 2000;
7
+ /**
8
+ * A watch on host time for an invocation the simulated clock has left behind.
9
+ *
10
+ * A handler's timers wait on the simulation's clock, and so does the
11
+ * invocation's deadline. Simulated time standing still holds both, and the
12
+ * invocation runs on until the test framework gives up on the test around it.
13
+ * Elapsed host time is the only thing separating that from a test meaning to
14
+ * start an invocation, advance the clock and then wait for it, which is why
15
+ * host time is what this watches.
16
+ *
17
+ * A turn that finds simulated time has moved starts the wait again, so a test
18
+ * advancing the clock in steps is left alone. The host timer is unreferenced,
19
+ * and an invocation nobody is driving never holds the process open.
20
+ */
21
+ export class SimLambdaStallWatch {
22
+ #properties;
23
+ #delay;
24
+ #seenAt = 0;
25
+ #hostTimer;
26
+ constructor(properties) {
27
+ this.#properties = properties;
28
+ this.#delay = simLambdaStallWatchDelay(properties.timeoutSeconds);
29
+ }
30
+ /**
31
+ * Start watching, from the instant the clock reads now.
32
+ */
33
+ start() {
34
+ this.#seenAt = this.#properties.background.now().getTime();
35
+ this.#arm();
36
+ }
37
+ /**
38
+ * Give up watching, as an invocation that has ended does.
39
+ */
40
+ cancel() {
41
+ clearTimeout(this.#hostTimer);
42
+ this.#hostTimer = undefined;
43
+ }
44
+ #arm() {
45
+ this.#hostTimer = setTimeout(() => {
46
+ this.#hostTimer = undefined;
47
+ this.#check();
48
+ }, this.#delay);
49
+ this.#hostTimer.unref();
50
+ }
51
+ /**
52
+ * See whether the invocation still has a way to finish, and watch on where
53
+ * it has: either simulated time has moved since the last turn, or the
54
+ * handler is busy with something other than a timer.
55
+ */
56
+ #check() {
57
+ const now = this.#properties.background.now().getTime();
58
+ const waitingOn = this.#properties.waitingOn();
59
+ if (waitingOn === undefined || now !== this.#seenAt) {
60
+ this.#seenAt = now;
61
+ this.#arm();
62
+ return;
63
+ }
64
+ this.#properties.stalled(waitingOn, this.#delay);
65
+ }
66
+ }
67
+ /**
68
+ * How long the host waits before it calls an invocation stalled.
69
+ *
70
+ * The function's own timeout is the bound that fits, since an invocation the
71
+ * deadline would have ended already has nothing left to wait for. It is held
72
+ * between one and two seconds. Below that a loaded host could fail a test that
73
+ * was about to advance the clock, and above it a test framework's own timeout
74
+ * arrives first and takes the diagnostic with it.
75
+ */
76
+ function simLambdaStallWatchDelay(timeoutSeconds) {
77
+ return Math.min(Math.max(timeoutSeconds * millisecondsPerSecond, shortestWatchMilliseconds), longestWatchMilliseconds);
78
+ }
@@ -0,0 +1,23 @@
1
+ import { SimLambdaRuntimeError } from "../../error/sim-lambda-runtime.error.js";
2
+ import type { SimLambdaPendingTimer } from "./timer/sim-lambda-pending-timer.js";
3
+ interface SimLambdaStalledProperties {
4
+ readonly awsRequestId: string;
5
+ /** The simulated instant the clock has stopped at. */
6
+ readonly at: Date;
7
+ /** The timer the invocation is waiting on. */
8
+ readonly waitingOn: SimLambdaPendingTimer;
9
+ /** How long the host has watched simulated time stand still. */
10
+ readonly waitedMilliseconds: number;
11
+ }
12
+ /**
13
+ * The error an invocation gets when nothing is left that could end it.
14
+ *
15
+ * A handler's timers wait on the simulation's clock, and so does the
16
+ * invocation's own deadline. Simulated time standing still holds both, and the
17
+ * invocation would otherwise run until the test framework gave up on the test
18
+ * around it, reporting the test rather than the timer. This names the timer
19
+ * the handler is waiting on, the instant it is due at, and what to do about
20
+ * it.
21
+ */
22
+ export declare function simLambdaStalledError(properties: SimLambdaStalledProperties): SimLambdaRuntimeError;
23
+ export {};
@@ -0,0 +1,22 @@
1
+ import { SimLambdaRuntimeError } from "../../error/sim-lambda-runtime.error.js";
2
+ /** What Yulin calls an invocation the clock is never going to release. */
3
+ const stalledErrorType = "Yulin.StalledInvocation";
4
+ /**
5
+ * The error an invocation gets when nothing is left that could end it.
6
+ *
7
+ * A handler's timers wait on the simulation's clock, and so does the
8
+ * invocation's own deadline. Simulated time standing still holds both, and the
9
+ * invocation would otherwise run until the test framework gave up on the test
10
+ * around it, reporting the test rather than the timer. This names the timer
11
+ * the handler is waiting on, the instant it is due at, and what to do about
12
+ * it.
13
+ */
14
+ export function simLambdaStalledError(properties) {
15
+ const { awsRequestId, at, waitingOn, waitedMilliseconds } = properties;
16
+ const error = new SimLambdaRuntimeError(stalledErrorType, `${at.toISOString()} ${awsRequestId} Task is waiting on a ${waitingOn.delay} ms timer due at ${waitingOn.dueTime.toISOString()}. Simulated time has stood still for ${waitedMilliseconds} ms of host time, leaving both that timer and the invocation deadline out of reach. Advance the simulation's clock past the timer delay to release it.`);
17
+ // Nothing in the handler threw, so its frames say nothing about why the
18
+ // invocation is stuck, and the simulator's own frames say less. The timeout
19
+ // this stands in for is reported without a stack too.
20
+ error.stack = `${error.name}: ${error.message}`;
21
+ return error;
22
+ }
@@ -21,6 +21,10 @@ interface SimLambdaClockTimerProperties {
21
21
  export declare class SimLambdaClockTimer {
22
22
  #private;
23
23
  constructor(properties: SimLambdaClockTimerProperties);
24
+ /** The delay this timer was last started with, in milliseconds. */
25
+ get delay(): number;
26
+ /** The simulated instant this timer's work is due to run at. */
27
+ get dueTime(): Date;
24
28
  /**
25
29
  * Wait a delay of simulated time, and answer with how long that turned out
26
30
  * to be. A delay of nothing leaves the timer due where the clock already
@@ -31,6 +31,14 @@ export class SimLambdaClockTimer {
31
31
  this.#run = properties.run;
32
32
  this.#dueTime = properties.background.now();
33
33
  }
34
+ /** The delay this timer was last started with, in milliseconds. */
35
+ get delay() {
36
+ return this.#delay;
37
+ }
38
+ /** The simulated instant this timer's work is due to run at. */
39
+ get dueTime() {
40
+ return new Date(this.#dueTime);
41
+ }
34
42
  /**
35
43
  * Wait a delay of simulated time, and answer with how long that turned out
36
44
  * to be. A delay of nothing leaves the timer due where the clock already
@@ -1,4 +1,5 @@
1
1
  import type { BackgroundScheduler } from "../../../../../util/background/background.js";
2
+ import type { SimLambdaPendingTimer } from "./sim-lambda-pending-timer.js";
2
3
  import { type SimLambdaTimerCallback, SimLambdaTimerHandle, type SimLambdaTimerOwner, type SimLambdaTimers } from "./sim-lambda-timer-handle.js";
3
4
  interface SimLambdaInvocationTimersProperties {
4
5
  readonly background: BackgroundScheduler;
@@ -31,6 +32,15 @@ export declare class SimLambdaInvocationTimers implements SimLambdaTimers, SimLa
31
32
  * Run a callback every time simulated time moves on by a delay.
32
33
  */
33
34
  setInterval(callback: SimLambdaTimerCallback, delay?: number, ...arguments_: unknown[]): SimLambdaTimerHandle;
35
+ /**
36
+ * The timer this invocation is waiting on, the earliest due one where it
37
+ * has several.
38
+ *
39
+ * A timer due at the instant the clock already reads is waiting for nothing:
40
+ * the next turn of the host event loop runs it. Only a timer the clock has
41
+ * yet to reach counts.
42
+ */
43
+ pending(): SimLambdaPendingTimer | undefined;
34
44
  /**
35
45
  * Give up one timer, as `clearTimeout` and `clearInterval` do.
36
46
  */
@@ -42,6 +42,27 @@ export class SimLambdaInvocationTimers {
42
42
  this.#call(callback, arguments_);
43
43
  });
44
44
  }
45
+ /**
46
+ * The timer this invocation is waiting on, the earliest due one where it
47
+ * has several.
48
+ *
49
+ * A timer due at the instant the clock already reads is waiting for nothing:
50
+ * the next turn of the host event loop runs it. Only a timer the clock has
51
+ * yet to reach counts.
52
+ */
53
+ pending() {
54
+ const now = this.#background.now().getTime();
55
+ let earliest;
56
+ for (const timer of this.#running.values()) {
57
+ if (timer.dueTime.getTime() > now && isEarlier(timer, earliest)) {
58
+ earliest = timer;
59
+ }
60
+ }
61
+ if (earliest === undefined) {
62
+ return undefined;
63
+ }
64
+ return { delay: earliest.delay, dueTime: earliest.dueTime };
65
+ }
45
66
  /**
46
67
  * Give up one timer, as `clearTimeout` and `clearInterval` do.
47
68
  */
@@ -110,3 +131,10 @@ export class SimLambdaInvocationTimers {
110
131
  this.#released = undefined;
111
132
  }
112
133
  }
134
+ /**
135
+ * Whether a timer comes due before the earliest one found so far. The first
136
+ * timer looked at has nothing to beat.
137
+ */
138
+ function isEarlier(timer, earliest) {
139
+ return earliest === undefined || timer.dueTime < earliest.dueTime;
140
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * A timer one invocation is waiting on, and the instant it comes due.
3
+ */
4
+ export interface SimLambdaPendingTimer {
5
+ /** The delay the code under test asked for, in milliseconds. */
6
+ readonly delay: number;
7
+ /** The simulated instant the timer's work runs at. */
8
+ readonly dueTime: Date;
9
+ }
@@ -3158,6 +3158,16 @@ that is where such a handler's configuration comes from when nothing declares it
3158
3158
  variables on the function keeps the test explicit about where they came from. Zip-packaged code in
3159
3159
  the vm runtime gets the function's own variables either way, and never the test process's.
3160
3160
 
3161
+ One group of host variables is masked whatever the function declares. `AWS_PROFILE`,
3162
+ `AWS_DEFAULT_PROFILE`, `AWS_CONFIG_FILE`, `AWS_SHARED_CREDENTIALS_FILE`,
3163
+ `AWS_WEB_IDENTITY_TOKEN_FILE`, `AWS_ROLE_ARN`, `AWS_ROLE_SESSION_NAME` and the
3164
+ `AWS_CONTAINER_CREDENTIALS_*` and `AWS_CONTAINER_AUTHORIZATION_*` names go unset for the length of
3165
+ an invocation. Each of them points an AWS SDK at credentials held by whoever started the test
3166
+ process, and a client built in a handler follows one straight out of the simulation. `AWS_PROFILE`
3167
+ does that even with the placeholder credentials above in place, because the Node.js credential
3168
+ provider chain skips its environment-variable provider whenever a profile is named. Masking them
3169
+ keeps a test independent of the machine running it, and real Lambda sets none of them anyway.
3170
+
3161
3171
  This is also how a function reaches something outside the simulation, such as a Redis or a
3162
3172
  Postgres. See [non-AWS dependencies](https://yulinsim.dev/non-aws-dependencies/).
3163
3173
 
@@ -197,6 +197,12 @@ simulation's clock during an invocation. Start an invocation without awaiting it
197
197
  clock past the timer delay, then await the invocation. Lambda's configured timeout and
198
198
  `context.getRemainingTimeInMillis()` use the same clock.
199
199
 
200
+ An invocation left waiting on one of those timers while simulated time stands still has nothing to
201
+ end it, because the function's own timeout waits on the same clock. Yulin fails it after a second or
202
+ two of real time with a `Yulin.StalledInvocation` error naming the timer and its delay. Advancing
203
+ the clock resets that wait, in one step or several, so the pattern above keeps working. A handler
204
+ busy with something other than a timer is left alone for as long as it takes.
205
+
200
206
  ### Where real AWS gets the time
201
207
 
202
208
  Real Lambda has no current-time API. A production handler gets the current time from the machine
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kensio/yulin",
3
- "version": "1.21.19",
3
+ "version": "1.21.21",
4
4
  "description": "AWS system behaviour simulation for isolated unit testing",
5
5
  "repository": "https://github.com/KensioSoftware/yulin",
6
6
  "homepage": "https://yulinsim.dev/",