@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.
- package/dist/service/lambda/function/environment/sim-lambda-execution-credentials.d.ts +25 -0
- package/dist/service/lambda/function/environment/sim-lambda-execution-credentials.js +45 -0
- package/dist/service/lambda/function/environment/sim-lambda-host-backed-variables.d.ts +5 -0
- package/dist/service/lambda/function/environment/sim-lambda-host-backed-variables.js +7 -1
- package/dist/service/lambda/function/invoke/sim-lambda-invocation-deadline.d.ts +4 -0
- package/dist/service/lambda/function/invoke/sim-lambda-invocation-deadline.js +21 -0
- package/dist/service/lambda/function/invoke/sim-lambda-stall-watch.d.ts +38 -0
- package/dist/service/lambda/function/invoke/sim-lambda-stall-watch.js +78 -0
- package/dist/service/lambda/function/invoke/sim-lambda-stalled.error.d.ts +23 -0
- package/dist/service/lambda/function/invoke/sim-lambda-stalled.error.js +22 -0
- package/dist/service/lambda/function/invoke/timer/sim-lambda-clock-timer.d.ts +4 -0
- package/dist/service/lambda/function/invoke/timer/sim-lambda-clock-timer.js +8 -0
- package/dist/service/lambda/function/invoke/timer/sim-lambda-invocation-timers.d.ts +10 -0
- package/dist/service/lambda/function/invoke/timer/sim-lambda-invocation-timers.js +28 -0
- package/dist/service/lambda/function/invoke/timer/sim-lambda-pending-timer.d.ts +9 -0
- package/dist/service/lambda/function/invoke/timer/sim-lambda-pending-timer.js +1 -0
- package/docs/services/lambda/README.md +10 -0
- package/docs/time/README.md +6 -0
- 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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
|
package/docs/time/README.md
CHANGED
|
@@ -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