@mark1russell7/service 0.5.14 → 0.5.16
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/README.md +5 -0
- package/build/index.d.ts +146 -6
- package/build/index.js +281 -25
- package/build/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,9 +15,14 @@ This release has the declaration, its export, the law, the health runtime, the l
|
|
|
15
15
|
- **Keyed operations.** A keyed operation has the window that the server keeps its key for. `mayReissue` answers from the declaration of an operation. An operation without a declaration does not go again.
|
|
16
16
|
- **The law.** `evaluate` and `verdictOf` decide one rule of the timer-ordering law on one binding. A rule is data: linear inequalities over `symbol@hop`, with a margin. A verdict is `holds`, `violated`, `unbounded` (it holds only because a timer is absent) or `depends` (nothing is bound).
|
|
17
17
|
- **The linter.** `lint(description, scenario, rules)` applies the service rules (`SERVICE_RULES`) to a declaration. The declaration binds the symbols of the service: the polls of its checks, the windows of its policies, its drain. The scenario binds the symbols of the platform and of each dependency. A `depends` finding names the symbols that have no bound.
|
|
18
|
+
- **The claims of a scenario.** A default is true only for a version: the keep-alive of Node is 5 s, but 65 s on main. Thus a value of the scenario can be a claim that names its epoch: `{ epoch: "node@22", value }`. Each finding then gives the epoch of each value that its rule reads (`epochs`).
|
|
19
|
+
- **A measured value.** A claim can give a distribution by its statistics: `{ epoch, distribution: { p50, p99 } }`. The law binds the p99. A distribution without a p99 binds nothing, thus each rule that reads it depends.
|
|
20
|
+
- **The four ways that a pod stops.** `STOPS` gives the steps of each way. A deletion and a liveness restart have the preStop hook and the drain. A crash and an OOM stop have neither. `lintStop(description, scenario, rules, way)` applies only the rules of the drain that guard that way: O04 for a deletion, O09 for a liveness restart. For a crash or an OOM stop, a finding of `STOP_RULE` depends on the time until the kubelet finds the exit.
|
|
18
21
|
- **The scopes of a rule.** A rule about a client applies one time for each dependency. A rule about a consumer, for example O20, applies one time for each consumer and each worker.
|
|
19
22
|
- **The loops of a progress check.** A check of the step `progress` can name the loops that mark it, with its stall time: `marks: { loops, stall }`. Each loop is a check or a worker of the service.
|
|
20
23
|
- **The stall time.** `lint()` compares the stall time with the longest gap between two marks of the slowest loop (`PROGRESS_RULE`). A loop marks at the end of each turn, also a turn that fails. The gap of a check is `interval × (1 + jitter) + timeout`. The gap of a worker is its sleep or its longest backoff, the longer one, plus `unit_processing_time`. At 300 s ± 20 % and a timeout of 10 s, a poll gives 370 s.
|
|
24
|
+
- **The marks of the timers.** A harness that runs a service k times faster scales its timers. Each timer has a mark: `scalable`, `fixed`, `stepped` (only multiples of a step) or `floored` (a peer sets a floor).
|
|
25
|
+
- **The compression of a service.** `compressionOf(description, scenario, rules, k, marks)` gives each rule and scope whose verdict the compression changes. It also names the symbols that have no mark. `PLATFORM_MARKS` marks the timers of Kubernetes. For example, the propagation of the endpoints is `fixed`, thus a compression of the hold of O04 can break O04.
|
|
21
26
|
- **A name that is not a loop.** A name that is not a check or a worker gets a `depends` finding. Without `marks`, a check gets no finding of this rule.
|
|
22
27
|
- **Keyed limiters.** `svc.limiter(name, spec)` declares a rate limit for each key, as data. `keyedLimiter` makes its runtime on a store ([The keyed limiter](#the-keyed-limiter)).
|
|
23
28
|
- **The hold.** The cap of the `fail readiness` phase binds `readiness_hold@process`, and a service without that phase holds for zero. The hold keeps the listener open after the signal, as a preStop sleep does. Thus O04 can compare the sum of the two with the propagation of the endpoints.
|
package/build/index.d.ts
CHANGED
|
@@ -84,6 +84,55 @@ export declare const evaluate: (rule: Rule, bind: Binding) => Evaluated[];
|
|
|
84
84
|
* applies, the arithmetic cannot decide, and the verdict is "depends".
|
|
85
85
|
*/
|
|
86
86
|
export declare const verdictOf: (rule: Rule, bind: Binding, evaluated: readonly Evaluated[]) => Verdict;
|
|
87
|
+
/**
|
|
88
|
+
* A mark tells how a timer moves when a compression scales time by k (E10). A "scalable" timer
|
|
89
|
+
* becomes k times its value. A "fixed" timer keeps its value. A "stepped" timer takes only
|
|
90
|
+
* multiples of its step, thus it rounds up. A "floored" timer has a floor that a peer sets, for
|
|
91
|
+
* example the minimum retransmission timeout of the kernel. The step and the floor are in seconds.
|
|
92
|
+
*/
|
|
93
|
+
type TimerMark = {
|
|
94
|
+
readonly kind: "scalable";
|
|
95
|
+
} | {
|
|
96
|
+
readonly kind: "fixed";
|
|
97
|
+
} | {
|
|
98
|
+
readonly kind: "stepped";
|
|
99
|
+
readonly step: number;
|
|
100
|
+
} | {
|
|
101
|
+
readonly kind: "floored";
|
|
102
|
+
readonly floor: number;
|
|
103
|
+
readonly by: string;
|
|
104
|
+
};
|
|
105
|
+
/** The marks map `symbol@hop` to a mark. A bare symbol marks the symbol at each hop. */
|
|
106
|
+
type TimerMarks = Readonly<Record<string, TimerMark | undefined>>;
|
|
107
|
+
/** A compressed binding, and the symbols that have no mark. */
|
|
108
|
+
interface Compressed {
|
|
109
|
+
readonly bind: Binding;
|
|
110
|
+
/** These symbols have no mark. They keep their values, thus the compression proves nothing about them. */
|
|
111
|
+
readonly unmarked: readonly string[];
|
|
112
|
+
}
|
|
113
|
+
/** A change is a rule whose verdict a compression changes. */
|
|
114
|
+
interface Change {
|
|
115
|
+
readonly rule: string;
|
|
116
|
+
readonly before: Verdict;
|
|
117
|
+
readonly after: Verdict;
|
|
118
|
+
}
|
|
119
|
+
/** The result of a compression: the rules whose verdict it changes, and the symbols of the rules that have no mark. */
|
|
120
|
+
interface Compression {
|
|
121
|
+
readonly changed: readonly Change[];
|
|
122
|
+
readonly unmarked: readonly string[];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* This function scales each timer of a binding by k, by its mark (E10). An absent timer ("inf" or
|
|
126
|
+
* "off") stays absent. A chain scales each value. The margins of the binding and the constants of
|
|
127
|
+
* the rules do not scale: they are tolerances, not timers. A symbol with no mark keeps its value.
|
|
128
|
+
*/
|
|
129
|
+
export declare const compress: (bind: Binding, k: number, marks: TimerMarks) => Compressed;
|
|
130
|
+
/**
|
|
131
|
+
* This function tells which rules a compression by k changes. A compression keeps each ordering
|
|
132
|
+
* when no verdict changes. A change in either direction counts: a violation that the compression
|
|
133
|
+
* hides is as wrong as one that it makes. The unmarked symbols are the symbols that the rules read.
|
|
134
|
+
*/
|
|
135
|
+
export declare const compression: (rules: readonly Rule[], bind: Binding, k: number, marks: TimerMarks) => Compression;
|
|
87
136
|
//#endregion
|
|
88
137
|
//#region src/lint.d.ts
|
|
89
138
|
/**
|
|
@@ -96,10 +145,31 @@ export declare const verdictOf: (rule: Rule, bind: Binding, evaluated: readonly
|
|
|
96
145
|
* A rule about a client applies one time for each dependency. A rule about a consumer applies one
|
|
97
146
|
* time for each consumer and each worker. A rule about a limiter applies one time for each limiter.
|
|
98
147
|
*/
|
|
148
|
+
/** A statistic of a measured distribution. The law binds p99 (`LAW_STATISTIC`). */
|
|
149
|
+
type Statistic = "p50" | "p90" | "p99" | "p999" | "max";
|
|
150
|
+
/** The law binds this statistic of a distribution. */
|
|
151
|
+
export declare const LAW_STATISTIC: Statistic;
|
|
152
|
+
/**
|
|
153
|
+
* A claim is a value of the scenario that names its epoch: the component and the version that give
|
|
154
|
+
* it, for example "node@22" (E2). A default is true only for a version: the keep-alive of Node is
|
|
155
|
+
* 5 s, but 65 s on main. A claim gives a set value, or a measured distribution by its statistics.
|
|
156
|
+
*/
|
|
157
|
+
type Claim = {
|
|
158
|
+
readonly epoch: string;
|
|
159
|
+
readonly value: Extent;
|
|
160
|
+
} | {
|
|
161
|
+
readonly epoch: string;
|
|
162
|
+
readonly distribution: Readonly<Partial<Record<Statistic, Quantity>>>;
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* A value of the scenario is an amount, or a claim. An amount names no epoch, thus the finding of
|
|
166
|
+
* each rule that reads it gives null for its epoch.
|
|
167
|
+
*/
|
|
168
|
+
type ScenarioValue = Extent | Claim;
|
|
99
169
|
/** A scenario gives the values of the platform and of each dependency, by the `symbol@hop` of the law. */
|
|
100
170
|
interface Scenario {
|
|
101
171
|
/** These are the values of the platform: prestop_sleep@pod, termination_grace@pod, probe_period@pod, propagation_bound@cluster. */
|
|
102
|
-
readonly platform: Readonly<Record<string,
|
|
172
|
+
readonly platform: Readonly<Record<string, ScenarioValue>>;
|
|
103
173
|
/**
|
|
104
174
|
* These are the probe windows of the kubelet, period × failure threshold. They add to the
|
|
105
175
|
* windows of the declaration. A probe reads the cached verdict, thus detection is the latch,
|
|
@@ -110,16 +180,18 @@ interface Scenario {
|
|
|
110
180
|
readonly liveness: Quantity;
|
|
111
181
|
};
|
|
112
182
|
/** These are the values of each dependency, by its name: failover_time@dependency, server_idle_close@server. */
|
|
113
|
-
readonly dependencies?: Readonly<Record<string, Readonly<Record<string,
|
|
183
|
+
readonly dependencies?: Readonly<Record<string, Readonly<Record<string, ScenarioValue>>>> | undefined;
|
|
114
184
|
/** These are the values of the source of each consumer and worker, by its name and by the `symbol@hop` of the law: lease_duration@broker. */
|
|
115
|
-
readonly transports?: Readonly<Record<string, Readonly<Record<string,
|
|
185
|
+
readonly transports?: Readonly<Record<string, Readonly<Record<string, ScenarioValue>>>> | undefined;
|
|
116
186
|
/**
|
|
117
187
|
* These are the values of the callers of each limiter, by its name and by the `symbol@hop` of the
|
|
118
188
|
* law: client_retry_window@caller, retry_delay@caller. A symbol without a hop binds at `caller`.
|
|
119
189
|
*/
|
|
120
|
-
readonly limiters?: Readonly<Record<string, Readonly<Record<string,
|
|
190
|
+
readonly limiters?: Readonly<Record<string, Readonly<Record<string, ScenarioValue>>>> | undefined;
|
|
121
191
|
}
|
|
122
|
-
/**
|
|
192
|
+
/** The epochs of a scope map each symbol that the scenario binds to the epoch of its value, or null when the value names none. */
|
|
193
|
+
type Epochs = Readonly<Record<string, string | null>>;
|
|
194
|
+
/** A finding is the verdict of one rule for one scope: the service, or the client of a dependency. When a value of the scenario names an epoch, the finding gives the epoch of each value that the rule reads. */
|
|
123
195
|
interface Finding {
|
|
124
196
|
readonly rule: string;
|
|
125
197
|
readonly scope: string;
|
|
@@ -129,6 +201,8 @@ interface Finding {
|
|
|
129
201
|
readonly evaluated: readonly Evaluated[];
|
|
130
202
|
/** These are the symbols that have no bound. A "depends" verdict waits for them. */
|
|
131
203
|
readonly unbound: readonly string[];
|
|
204
|
+
/** These are the epochs of the symbols of the rule that the scenario binds (E2). Thus a finding is true for these versions. */
|
|
205
|
+
readonly epochs?: Epochs | undefined;
|
|
132
206
|
}
|
|
133
207
|
/** These are the identifiers of the rules that each declaration gets. */
|
|
134
208
|
export declare const SERVICE_RULES: readonly string[];
|
|
@@ -150,11 +224,31 @@ export declare const LIMITER_RULES: readonly Rule[];
|
|
|
150
224
|
export declare const serviceBindings: (svc: ServiceDescription, scenario: Scenario) => {
|
|
151
225
|
readonly bind: Record<string, Bound>;
|
|
152
226
|
readonly from: Record<string, string>;
|
|
227
|
+
readonly epochs: Epochs;
|
|
153
228
|
};
|
|
154
229
|
/** This identifier is the rule of the findings about a field that the declaration does not give. */
|
|
155
230
|
export declare const DECLARATION_RULE = "declaration";
|
|
156
231
|
/** This identifier is the rule of the findings about the stall time of a progress check (`Check.marks`). */
|
|
157
232
|
export declare const PROGRESS_RULE = "progress";
|
|
233
|
+
/**
|
|
234
|
+
* These are the marks of the timers of the platform (E10). Kubernetes takes the probe period, the
|
|
235
|
+
* preStop sleep and the termination grace in whole seconds, thus each is stepped by 1 s. The
|
|
236
|
+
* cluster sets the propagation of the endpoints, thus a compression of the service cannot scale it.
|
|
237
|
+
*/
|
|
238
|
+
export declare const PLATFORM_MARKS: TimerMarks;
|
|
239
|
+
/** A change of a compression of a service: a rule and a scope whose verdict the compression changes. */
|
|
240
|
+
interface ScopedChange extends Change {
|
|
241
|
+
readonly scope: string;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* This function tells which findings of `lint` a compression by k changes (E10). It scales the
|
|
245
|
+
* bindings of each scope by the marks. Thus a harness that runs the service k times faster learns
|
|
246
|
+
* the orderings that its run breaks. The unmarked symbols are the symbols of the rules with no mark.
|
|
247
|
+
*/
|
|
248
|
+
export declare const compressionOf: (svc: ServiceDescription, scenario: Scenario, rules: readonly Rule[], k: number, marks: TimerMarks) => {
|
|
249
|
+
readonly changed: readonly ScopedChange[];
|
|
250
|
+
readonly unmarked: readonly string[];
|
|
251
|
+
};
|
|
158
252
|
/**
|
|
159
253
|
* This function applies each service rule to a declaration and a scenario. A rule applies one time
|
|
160
254
|
* for the service, for each client, for each consumer and worker, or for each limiter. The function
|
|
@@ -163,6 +257,52 @@ export declare const PROGRESS_RULE = "progress";
|
|
|
163
257
|
*/
|
|
164
258
|
export declare const lint: (svc: ServiceDescription, scenario: Scenario, rules: readonly Rule[]) => Finding[];
|
|
165
259
|
//#endregion
|
|
260
|
+
//#region src/stops.d.ts
|
|
261
|
+
/**
|
|
262
|
+
* This module keeps apart the four ways that a pod stops (B11). Each way runs a different part of
|
|
263
|
+
* the shutdown, thus a different set of the rules of the drain guards it.
|
|
264
|
+
*/
|
|
265
|
+
/** A way that a pod stops: a deletion, a liveness restart, a crash, or a stop for lack of memory (OOM). */
|
|
266
|
+
type StopWay = "deletion" | "liveness-restart" | "crash" | "oom";
|
|
267
|
+
/** What a way of stopping runs, and the rules of the drain that guard it. */
|
|
268
|
+
interface Stop {
|
|
269
|
+
readonly way: StopWay;
|
|
270
|
+
/** This is the signal that the process gets first, or null when the process exits by itself. */
|
|
271
|
+
readonly signal: "SIGTERM" | "SIGKILL" | null;
|
|
272
|
+
/** This flag tells if the kubelet runs the preStop hook. */
|
|
273
|
+
readonly preStop: boolean;
|
|
274
|
+
/** This flag tells if the process runs its shutdown phases. */
|
|
275
|
+
readonly drain: boolean;
|
|
276
|
+
/**
|
|
277
|
+
* This is how the pod leaves its endpoints: by its deletion, when its readiness fails, or when
|
|
278
|
+
* the kubelet finds that the container exited.
|
|
279
|
+
*/
|
|
280
|
+
readonly leaves: "deletion" | "readiness" | "exit";
|
|
281
|
+
/** These are the identifiers of the rules of the drain that guard this way. */
|
|
282
|
+
readonly rules: readonly string[];
|
|
283
|
+
}
|
|
284
|
+
/** These are the rules of the drain. Each way of stopping applies only its own rules of this set. */
|
|
285
|
+
export declare const DRAIN_RULE_IDS: readonly string[];
|
|
286
|
+
/**
|
|
287
|
+
* These are the four ways that a pod stops.
|
|
288
|
+
* - A deletion runs the preStop hook, then SIGTERM. The endpoints drop the pod at its deletion,
|
|
289
|
+
* thus the preStop sleep and the hold must cover the propagation (O04).
|
|
290
|
+
* - A liveness restart also runs the preStop hook and SIGTERM, with the grace. The pod stays in its
|
|
291
|
+
* endpoints until its readiness fails, thus readiness must fail first (O09).
|
|
292
|
+
* - A crash and an OOM stop run no hook and no drain. The kubelet finds the exit at its relist, and
|
|
293
|
+
* no rule bounds that time. A client that can reissue a request is the only guard.
|
|
294
|
+
*/
|
|
295
|
+
export declare const STOPS: Readonly<Record<StopWay, Stop>>;
|
|
296
|
+
/** The identifier of the finding of a way of stopping that runs no drain. */
|
|
297
|
+
export declare const STOP_RULE = "stop";
|
|
298
|
+
/**
|
|
299
|
+
* This function applies the rules to a declaration for one way of stopping. A rule of the drain
|
|
300
|
+
* applies only when it guards that way. The other rules apply as `lint()` applies them. A way with
|
|
301
|
+
* no drain also gets a finding of `STOP_RULE` that depends. No rule bounds the time until the
|
|
302
|
+
* kubelet finds the exit, and the work in flight is lost.
|
|
303
|
+
*/
|
|
304
|
+
export declare const lintStop: (svc: ServiceDescription, scenario: Scenario, rules: readonly Rule[], way: StopWay) => Finding[];
|
|
305
|
+
//#endregion
|
|
166
306
|
//#region src/worker.d.ts
|
|
167
307
|
/**
|
|
168
308
|
* These are the knobs of a worker loop, by the names of the ordering law where the law has them.
|
|
@@ -292,5 +432,5 @@ export declare const drainMachine: (description: ServiceDescription) => Machine;
|
|
|
292
432
|
/** These are the static machines of the runtime, by the name that their events carry. */
|
|
293
433
|
export declare const MACHINES: Readonly<Record<Exclude<MachineName, "drain" | "client">, Machine>>;
|
|
294
434
|
//#endregion
|
|
295
|
-
export { type Allowed, type Backoff, type Binding, type Bound, type Cancel, type Check, type CheckFn, type CheckMetrics, type CheckVerdict, type Clock, type Controller, type ControllerBuilder, type CooperativeFloor, type Coupling, DURATION_FRACTIONS, type Decision, type DelayBand, type Dependency, type DependencyHandle, type DurationHistogram, EPISODES_KEPT, EXIT_SIGNALS, type EmbeddedProcess, type Episode, type Evaluated, type ExitCodes, type ExitSignal, type ExitTrigger, FORMAT, type FailsBy, type Finding, type Gate, type Health, type HealthMetrics, type HealthServer, type HealthVerdict, type Heartbeat, type HeartbeatOptions, type HostStream, type Idempotency, type Inequality, type IterationContext, type Jitter, type JobAction, type JobContext, type JobFailure, type JobSettle, type JobSource, type KeyState, type KeyedLimiter, type Lifecycle, type LifecycleEvent, type LifecycleState, type LimitPolicy, type LimitRule, type Limiter, type LimiterEviction, type LimiterSpec, type LimiterState, type LimiterStore, type LimiterStoreSpec, type MachineName, type MemoryStore, type MessageShape, type MessageTrigger, type Operation, type OperationOf, type Outcome, PROCESS_TRIGGERS, type Phase, type PhaseContext, type PhaseWork, type PlatformClockOptions, type PodTiming, type Poll, type ProbeAnswer, type ProbeKind, type ProcessHost, type ProcessPort, type ProcessTrigger, type Profile, type ProgressMarks, type QueueDrain, type QueueDrainOptions, type QueueKnobs, type Recorder, type RedeliveryPolicy, type RefusalCause, type Refused, type RepeatSignal, type RequestEnd, type RequestEvent, type RequestFn, type RequestOptions, type RequestStream, type RequestWork, type RequeueCause, type Result, type RoutePattern, type RouteRequest, type Rule, type RunOptions, SLIDING_WINDOW_MACHINE, STAGES, type Scenario, type ServiceBuilder, type ServiceDescription, type ShutdownOptions, type Side, type SlidingWindow, type Span, type Stage, type StartOptions, type Step, type StoreFailure, TOKEN_BUCKET_MACHINE, type TakeRequest, type TakeStep, type Term, type Timing, type TokenBucket, type Transport, type TransportImpl, type UnitRequest, type Verdict, type Watchdog, type WatchdogSpec, type WorkerKnobs, type WorkerLoop, type WorkerLoopOptions, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, cooperativeAbortAt, daemon, decide, detectionTime, embeddedProcess, ephemeral, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterMachine, limiterProblems, limiterStateOf, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redact, redeliverOnce, replay, retryAfterSeconds, service, stageRank, watchdog };
|
|
435
|
+
export { type Allowed, type Backoff, type Binding, type Bound, type Cancel, type Change, type Check, type CheckFn, type CheckMetrics, type CheckVerdict, type Claim, type Clock, type Compressed, type Compression, type Controller, type ControllerBuilder, type CooperativeFloor, type Coupling, DURATION_FRACTIONS, type Decision, type DelayBand, type Dependency, type DependencyHandle, type DurationHistogram, EPISODES_KEPT, EXIT_SIGNALS, type EmbeddedProcess, type Episode, type Epochs, type Evaluated, type ExitCodes, type ExitSignal, type ExitTrigger, FORMAT, type FailsBy, type Finding, type Gate, type Health, type HealthMetrics, type HealthServer, type HealthVerdict, type Heartbeat, type HeartbeatOptions, type HostStream, type Idempotency, type Inequality, type IterationContext, type Jitter, type JobAction, type JobContext, type JobFailure, type JobSettle, type JobSource, type KeyState, type KeyedLimiter, type Lifecycle, type LifecycleEvent, type LifecycleState, type LimitPolicy, type LimitRule, type Limiter, type LimiterEviction, type LimiterSpec, type LimiterState, type LimiterStore, type LimiterStoreSpec, type MachineName, type MemoryStore, type MessageShape, type MessageTrigger, type Operation, type OperationOf, type Outcome, PROCESS_TRIGGERS, type Phase, type PhaseContext, type PhaseWork, type PlatformClockOptions, type PodTiming, type Poll, type ProbeAnswer, type ProbeKind, type ProcessHost, type ProcessPort, type ProcessTrigger, type Profile, type ProgressMarks, type QueueDrain, type QueueDrainOptions, type QueueKnobs, type Recorder, type RedeliveryPolicy, type RefusalCause, type Refused, type RepeatSignal, type RequestEnd, type RequestEvent, type RequestFn, type RequestOptions, type RequestStream, type RequestWork, type RequeueCause, type Result, type RoutePattern, type RouteRequest, type Rule, type RunOptions, SLIDING_WINDOW_MACHINE, STAGES, type Scenario, type ScenarioValue, type ScopedChange, type ServiceBuilder, type ServiceDescription, type ShutdownOptions, type Side, type SlidingWindow, type Span, type Stage, type StartOptions, type Statistic, type Step, type Stop, type StopWay, type StoreFailure, TOKEN_BUCKET_MACHINE, type TakeRequest, type TakeStep, type Term, type TimerMark, type TimerMarks, type Timing, type TokenBucket, type Transport, type TransportImpl, type UnitRequest, type Verdict, type Watchdog, type WatchdogSpec, type WorkerKnobs, type WorkerLoop, type WorkerLoopOptions, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, cooperativeAbortAt, daemon, decide, detectionTime, embeddedProcess, ephemeral, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterMachine, limiterProblems, limiterStateOf, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redact, redeliverOnce, replay, retryAfterSeconds, service, stageRank, watchdog };
|
|
296
436
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.js
CHANGED
|
@@ -2318,8 +2318,74 @@ const verdictOf = (rule, bind, evaluated) => {
|
|
|
2318
2318
|
const all = inequalities(rule);
|
|
2319
2319
|
return applicable.some((index) => readsAbsent(all[index], bind)) ? "unbounded" : "holds";
|
|
2320
2320
|
};
|
|
2321
|
+
/** This function gives the mark of a symbol: its mark at its hop, else the mark of the bare symbol. */
|
|
2322
|
+
const markOf = (marks, key) => marks[key] ?? marks[key.split("@")[0] ?? key];
|
|
2323
|
+
const STEP_EPSILON = 1e-9;
|
|
2324
|
+
const scaled = (seconds, k, mark) => {
|
|
2325
|
+
switch (mark.kind) {
|
|
2326
|
+
case "scalable": return seconds * k;
|
|
2327
|
+
case "fixed": return seconds;
|
|
2328
|
+
case "stepped": return Math.max(Math.ceil(seconds * k / mark.step - STEP_EPSILON), 0) * mark.step;
|
|
2329
|
+
case "floored": return Math.max(seconds * k, mark.floor);
|
|
2330
|
+
}
|
|
2331
|
+
};
|
|
2332
|
+
/**
|
|
2333
|
+
* This function scales each timer of a binding by k, by its mark (E10). An absent timer ("inf" or
|
|
2334
|
+
* "off") stays absent. A chain scales each value. The margins of the binding and the constants of
|
|
2335
|
+
* the rules do not scale: they are tolerances, not timers. A symbol with no mark keeps its value.
|
|
2336
|
+
*/
|
|
2337
|
+
const compress = (bind, k, marks) => {
|
|
2338
|
+
if (!Number.isFinite(k) || k <= 0) throw new RangeError(`a compression scales time by a finite factor above 0, not ${k}`);
|
|
2339
|
+
const out = {};
|
|
2340
|
+
const unmarked = [];
|
|
2341
|
+
for (const [key, bound] of Object.entries(bind)) {
|
|
2342
|
+
if (key === "margin" || key === "margins") continue;
|
|
2343
|
+
const mark = markOf(marks, key);
|
|
2344
|
+
if (mark === void 0) {
|
|
2345
|
+
if (bound !== void 0) unmarked.push(key);
|
|
2346
|
+
out[key] = bound;
|
|
2347
|
+
} else if (typeof bound === "number") out[key] = scaled(bound, k, mark);
|
|
2348
|
+
else if (Array.isArray(bound)) out[key] = bound.map((seconds) => scaled(seconds, k, mark));
|
|
2349
|
+
else out[key] = bound;
|
|
2350
|
+
}
|
|
2351
|
+
const margins = {
|
|
2352
|
+
...bind.margin !== void 0 ? { margin: bind.margin } : {},
|
|
2353
|
+
...bind.margins !== void 0 ? { margins: bind.margins } : {}
|
|
2354
|
+
};
|
|
2355
|
+
return {
|
|
2356
|
+
bind: {
|
|
2357
|
+
...out,
|
|
2358
|
+
...margins
|
|
2359
|
+
},
|
|
2360
|
+
unmarked
|
|
2361
|
+
};
|
|
2362
|
+
};
|
|
2363
|
+
/**
|
|
2364
|
+
* This function tells which rules a compression by k changes. A compression keeps each ordering
|
|
2365
|
+
* when no verdict changes. A change in either direction counts: a violation that the compression
|
|
2366
|
+
* hides is as wrong as one that it makes. The unmarked symbols are the symbols that the rules read.
|
|
2367
|
+
*/
|
|
2368
|
+
const compression = (rules, bind, k, marks) => {
|
|
2369
|
+
const compressed = compress(bind, k, marks);
|
|
2370
|
+
const changed = rules.flatMap((rule) => {
|
|
2371
|
+
const before = verdictOf(rule, bind, evaluate(rule, bind));
|
|
2372
|
+
const after = verdictOf(rule, compressed.bind, evaluate(rule, compressed.bind));
|
|
2373
|
+
return before === after ? [] : [{
|
|
2374
|
+
rule: rule.id,
|
|
2375
|
+
before,
|
|
2376
|
+
after
|
|
2377
|
+
}];
|
|
2378
|
+
});
|
|
2379
|
+
const read = new Set(rules.flatMap(symbolsOf));
|
|
2380
|
+
return {
|
|
2381
|
+
changed,
|
|
2382
|
+
unmarked: compressed.unmarked.filter((symbol) => read.has(symbol))
|
|
2383
|
+
};
|
|
2384
|
+
};
|
|
2321
2385
|
//#endregion
|
|
2322
2386
|
//#region src/lint.ts
|
|
2387
|
+
/** The law binds this statistic of a distribution. */
|
|
2388
|
+
const LAW_STATISTIC = "p99";
|
|
2323
2389
|
/** These are the identifiers of the rules that each declaration gets. */
|
|
2324
2390
|
const SERVICE_RULES = [
|
|
2325
2391
|
"O04",
|
|
@@ -2410,6 +2476,35 @@ const LIMITER_RULES = [{
|
|
|
2410
2476
|
const secondsOf = (amount) => rationals.toNumber(convertExact(rationals.fromNumber(amount.value), amount.unit, "s"));
|
|
2411
2477
|
/** This function gives an amount, or no bound, as the law binds it. */
|
|
2412
2478
|
const boundOf = (amount) => amount.value === "infinite" ? "inf" : secondsOf(amount);
|
|
2479
|
+
/**
|
|
2480
|
+
* This function gives a value of the scenario as the law binds it, with its epoch. A distribution
|
|
2481
|
+
* binds its p99. A distribution without a p99 binds nothing, thus each rule that reads it depends.
|
|
2482
|
+
*/
|
|
2483
|
+
const claimOf = (value) => {
|
|
2484
|
+
if (!("epoch" in value)) return {
|
|
2485
|
+
bound: boundOf(value),
|
|
2486
|
+
epoch: null
|
|
2487
|
+
};
|
|
2488
|
+
if ("value" in value) return {
|
|
2489
|
+
bound: boundOf(value.value),
|
|
2490
|
+
epoch: value.epoch
|
|
2491
|
+
};
|
|
2492
|
+
const statistic = value.distribution["p99"];
|
|
2493
|
+
return {
|
|
2494
|
+
bound: statistic === void 0 ? void 0 : secondsOf(statistic),
|
|
2495
|
+
epoch: value.epoch
|
|
2496
|
+
};
|
|
2497
|
+
};
|
|
2498
|
+
/** This function binds each value of a part of the scenario, at the symbol that `at` gives, and records its epoch. */
|
|
2499
|
+
const bindScenario = (values, at, bind, epochs) => {
|
|
2500
|
+
for (const [symbol, value] of Object.entries(values)) {
|
|
2501
|
+
const claim = claimOf(value);
|
|
2502
|
+
const key = at(symbol);
|
|
2503
|
+
epochs[key] = claim.epoch;
|
|
2504
|
+
if (claim.bound !== void 0) bind[key] = claim.bound;
|
|
2505
|
+
else delete bind[key];
|
|
2506
|
+
}
|
|
2507
|
+
};
|
|
2413
2508
|
/** This function tells if a unit is a time. A unit that the package does not know is not a time. */
|
|
2414
2509
|
const isTime = (unit) => {
|
|
2415
2510
|
try {
|
|
@@ -2424,6 +2519,7 @@ const most = (values) => values.length === 0 ? void 0 : Math.max(...values);
|
|
|
2424
2519
|
const serviceBindings = (svc, scenario) => {
|
|
2425
2520
|
const bind = {};
|
|
2426
2521
|
const from = {};
|
|
2522
|
+
const epochs = {};
|
|
2427
2523
|
const set = (symbol, bound, said) => {
|
|
2428
2524
|
if (bound === void 0) return;
|
|
2429
2525
|
bind[symbol] = bound;
|
|
@@ -2446,18 +2542,25 @@ const serviceBindings = (svc, scenario) => {
|
|
|
2446
2542
|
set("cooperative_window@process", boundOf(svc.shutdown.cooperativeWindow), "the declared cooperative window");
|
|
2447
2543
|
const hold = svc.shutdown.phases.find((phase) => phase.name === "fail readiness");
|
|
2448
2544
|
set("readiness_hold@process", hold === void 0 ? 0 : secondsOf(hold.cap), hold === void 0 ? "no fail readiness phase: the intake stops at once after the signal" : "the cap of the fail readiness phase, which holds while the endpoints update");
|
|
2449
|
-
|
|
2545
|
+
bindScenario(scenario.platform, (symbol) => symbol, bind, epochs);
|
|
2546
|
+
for (const [symbol, epoch] of Object.entries(epochs)) if (bind[symbol] === void 0) delete from[symbol];
|
|
2547
|
+
else from[symbol] = epoch === null ? "the scenario" : `the scenario, for ${epoch}`;
|
|
2450
2548
|
return {
|
|
2451
2549
|
bind,
|
|
2452
|
-
from
|
|
2550
|
+
from,
|
|
2551
|
+
epochs
|
|
2453
2552
|
};
|
|
2454
2553
|
};
|
|
2455
2554
|
/** This function gives the client knobs of a dependency at @client, and the values of the scenario at its own hops. */
|
|
2456
2555
|
const dependencyBindings = (dependency, scenario) => {
|
|
2457
2556
|
const bind = {};
|
|
2557
|
+
const epochs = {};
|
|
2458
2558
|
for (const [knob, amount] of Object.entries(dependency.knobs)) bind[`${knob}@client`] = boundOf(amount);
|
|
2459
|
-
|
|
2460
|
-
return
|
|
2559
|
+
bindScenario(scenario.dependencies?.[dependency.name] ?? {}, (symbol) => symbol.includes("@") ? symbol : `${symbol}@dependency`, bind, epochs);
|
|
2560
|
+
return {
|
|
2561
|
+
bind,
|
|
2562
|
+
epochs
|
|
2563
|
+
};
|
|
2461
2564
|
};
|
|
2462
2565
|
/**
|
|
2463
2566
|
* These are the hops of the law at which the knobs of a transport bind, by its kind. A worker does
|
|
@@ -2474,12 +2577,16 @@ const TRANSPORT_HOPS = {
|
|
|
2474
2577
|
*/
|
|
2475
2578
|
const transportBindings = (transport, hop, scenario) => {
|
|
2476
2579
|
const bind = {};
|
|
2580
|
+
const epochs = {};
|
|
2477
2581
|
for (const [knob, amount] of Object.entries(transport.knobs)) {
|
|
2478
2582
|
if (!isTime(amount.unit)) continue;
|
|
2479
2583
|
bind[knob.includes("@") ? knob : `${knob}@${hop}`] = boundOf(amount);
|
|
2480
2584
|
}
|
|
2481
|
-
|
|
2482
|
-
return
|
|
2585
|
+
bindScenario(scenario.transports?.[transport.name] ?? {}, (symbol) => symbol, bind, epochs);
|
|
2586
|
+
return {
|
|
2587
|
+
bind,
|
|
2588
|
+
epochs
|
|
2589
|
+
};
|
|
2483
2590
|
};
|
|
2484
2591
|
/**
|
|
2485
2592
|
* This function gives the symbols of a limiter, and the values of the scenario for its callers.
|
|
@@ -2493,8 +2600,12 @@ const limiterBindings = (limiter, scenario) => {
|
|
|
2493
2600
|
"retry_after@limiter": retryAfterSeconds(longestRetryAfter(limiter.policy)),
|
|
2494
2601
|
"retry_after_at_cap@limiter": retryAfterSeconds(idleSpan(limiter.policy))
|
|
2495
2602
|
};
|
|
2496
|
-
|
|
2497
|
-
|
|
2603
|
+
const epochs = {};
|
|
2604
|
+
bindScenario(scenario.limiters?.[limiter.name] ?? {}, (symbol) => symbol.includes("@") ? symbol : `${symbol}@caller`, bind, epochs);
|
|
2605
|
+
return {
|
|
2606
|
+
bind,
|
|
2607
|
+
epochs
|
|
2608
|
+
};
|
|
2498
2609
|
};
|
|
2499
2610
|
const SCOPED = /@(client|dependency|server)$/;
|
|
2500
2611
|
const TRANSPORT_SCOPED = /@consumer$/;
|
|
@@ -2644,6 +2755,79 @@ const progressFindings = (svc) => {
|
|
|
2644
2755
|
}
|
|
2645
2756
|
return findings;
|
|
2646
2757
|
};
|
|
2758
|
+
/** This function gives the scopes of a rule that reads these symbols: the service, each client, each consumer and worker, or each limiter. */
|
|
2759
|
+
const scopesOf = (svc, scenario, service, symbols) => {
|
|
2760
|
+
const within = (name, scoped) => {
|
|
2761
|
+
const bind = {
|
|
2762
|
+
...service.bind,
|
|
2763
|
+
...scoped.bind
|
|
2764
|
+
};
|
|
2765
|
+
for (const symbol of Object.keys(scoped.epochs)) if (!(symbol in scoped.bind)) delete bind[symbol];
|
|
2766
|
+
return [
|
|
2767
|
+
name,
|
|
2768
|
+
bind,
|
|
2769
|
+
{
|
|
2770
|
+
...service.epochs,
|
|
2771
|
+
...scoped.epochs
|
|
2772
|
+
}
|
|
2773
|
+
];
|
|
2774
|
+
};
|
|
2775
|
+
return symbols.some((symbol) => LIMITER_SCOPED.test(symbol)) ? (svc.limiters ?? []).map((limiter) => within(limiter.name, limiterBindings(limiter, scenario))) : symbols.some((symbol) => TRANSPORT_SCOPED.test(symbol)) ? svc.transports.flatMap((transport) => {
|
|
2776
|
+
const hop = TRANSPORT_HOPS[transport.kind];
|
|
2777
|
+
return hop === null ? [] : [within(transport.name, transportBindings(transport, hop, scenario))];
|
|
2778
|
+
}) : symbols.some((symbol) => SCOPED.test(symbol)) ? svc.dependencies.map((dependency) => within(dependency.name, dependencyBindings(dependency, scenario))) : [[
|
|
2779
|
+
svc.name,
|
|
2780
|
+
service.bind,
|
|
2781
|
+
service.epochs
|
|
2782
|
+
]];
|
|
2783
|
+
};
|
|
2784
|
+
/** This function gives the epochs of the symbols of a rule that the scenario binds. */
|
|
2785
|
+
const epochsOf = (symbols, epochs) => Object.fromEntries(symbols.flatMap((symbol) => symbol in epochs ? [[symbol, epochs[symbol] ?? null]] : []));
|
|
2786
|
+
/**
|
|
2787
|
+
* These are the marks of the timers of the platform (E10). Kubernetes takes the probe period, the
|
|
2788
|
+
* preStop sleep and the termination grace in whole seconds, thus each is stepped by 1 s. The
|
|
2789
|
+
* cluster sets the propagation of the endpoints, thus a compression of the service cannot scale it.
|
|
2790
|
+
*/
|
|
2791
|
+
const PLATFORM_MARKS = {
|
|
2792
|
+
"probe_period@pod": {
|
|
2793
|
+
kind: "stepped",
|
|
2794
|
+
step: 1
|
|
2795
|
+
},
|
|
2796
|
+
"prestop_sleep@pod": {
|
|
2797
|
+
kind: "stepped",
|
|
2798
|
+
step: 1
|
|
2799
|
+
},
|
|
2800
|
+
"termination_grace@pod": {
|
|
2801
|
+
kind: "stepped",
|
|
2802
|
+
step: 1
|
|
2803
|
+
},
|
|
2804
|
+
"propagation_bound@cluster": { kind: "fixed" }
|
|
2805
|
+
};
|
|
2806
|
+
/**
|
|
2807
|
+
* This function tells which findings of `lint` a compression by k changes (E10). It scales the
|
|
2808
|
+
* bindings of each scope by the marks. Thus a harness that runs the service k times faster learns
|
|
2809
|
+
* the orderings that its run breaks. The unmarked symbols are the symbols of the rules with no mark.
|
|
2810
|
+
*/
|
|
2811
|
+
const compressionOf = (svc, scenario, rules, k, marks) => {
|
|
2812
|
+
const service = serviceBindings(svc, scenario);
|
|
2813
|
+
const changed = [];
|
|
2814
|
+
const unmarked = /* @__PURE__ */ new Set();
|
|
2815
|
+
for (const rule of rules) {
|
|
2816
|
+
if (!SERVICE_RULES.includes(rule.id)) continue;
|
|
2817
|
+
for (const [scope, bind] of scopesOf(svc, scenario, service, symbolsOf(rule))) {
|
|
2818
|
+
const result = compression([rule], bind, k, marks);
|
|
2819
|
+
for (const change of result.changed) changed.push({
|
|
2820
|
+
...change,
|
|
2821
|
+
scope
|
|
2822
|
+
});
|
|
2823
|
+
for (const symbol of result.unmarked) unmarked.add(symbol);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
return {
|
|
2827
|
+
changed,
|
|
2828
|
+
unmarked: [...unmarked]
|
|
2829
|
+
};
|
|
2830
|
+
};
|
|
2647
2831
|
/**
|
|
2648
2832
|
* This function applies each service rule to a declaration and a scenario. A rule applies one time
|
|
2649
2833
|
* for the service, for each client, for each consumer and worker, or for each limiter. The function
|
|
@@ -2651,26 +2835,14 @@ const progressFindings = (svc) => {
|
|
|
2651
2835
|
* Each progress check that names its loops also gets its findings (`PROGRESS_RULE`).
|
|
2652
2836
|
*/
|
|
2653
2837
|
const lint = (svc, scenario, rules) => {
|
|
2654
|
-
const service = serviceBindings(svc, scenario)
|
|
2838
|
+
const service = serviceBindings(svc, scenario);
|
|
2655
2839
|
const findings = [...declarationFindings(svc), ...progressFindings(svc)];
|
|
2656
2840
|
for (const rule of rules) {
|
|
2657
2841
|
if (!SERVICE_RULES.includes(rule.id)) continue;
|
|
2658
2842
|
const symbols = symbolsOf(rule);
|
|
2659
|
-
const
|
|
2660
|
-
...service,
|
|
2661
|
-
...limiterBindings(limiter, scenario)
|
|
2662
|
-
}]) : symbols.some((symbol) => TRANSPORT_SCOPED.test(symbol)) ? svc.transports.flatMap((transport) => {
|
|
2663
|
-
const hop = TRANSPORT_HOPS[transport.kind];
|
|
2664
|
-
return hop === null ? [] : [[transport.name, {
|
|
2665
|
-
...service,
|
|
2666
|
-
...transportBindings(transport, hop, scenario)
|
|
2667
|
-
}]];
|
|
2668
|
-
}) : symbols.some((symbol) => SCOPED.test(symbol)) ? svc.dependencies.map((dependency) => [dependency.name, {
|
|
2669
|
-
...service,
|
|
2670
|
-
...dependencyBindings(dependency, scenario)
|
|
2671
|
-
}]) : [[svc.name, service]];
|
|
2672
|
-
for (const [scope, bind] of scopes) {
|
|
2843
|
+
for (const [scope, bind, all] of scopesOf(svc, scenario, service, symbols)) {
|
|
2673
2844
|
const evaluated = evaluate(rule, bind);
|
|
2845
|
+
const epochs = epochsOf(symbols, all);
|
|
2674
2846
|
findings.push({
|
|
2675
2847
|
rule: rule.id,
|
|
2676
2848
|
scope,
|
|
@@ -2678,13 +2850,97 @@ const lint = (svc, scenario, rules) => {
|
|
|
2678
2850
|
statement: rule.rule,
|
|
2679
2851
|
...rule.window !== void 0 ? { window: rule.window } : {},
|
|
2680
2852
|
evaluated,
|
|
2681
|
-
unbound: symbols.filter((symbol) => bind[symbol] === void 0)
|
|
2853
|
+
unbound: symbols.filter((symbol) => bind[symbol] === void 0),
|
|
2854
|
+
...Object.values(epochs).some((epoch) => epoch !== null) ? { epochs } : {}
|
|
2682
2855
|
});
|
|
2683
2856
|
}
|
|
2684
2857
|
}
|
|
2685
2858
|
return findings;
|
|
2686
2859
|
};
|
|
2687
2860
|
//#endregion
|
|
2861
|
+
//#region src/stops.ts
|
|
2862
|
+
/** These are the rules of the drain. Each way of stopping applies only its own rules of this set. */
|
|
2863
|
+
const DRAIN_RULE_IDS = [
|
|
2864
|
+
"O04",
|
|
2865
|
+
"O09",
|
|
2866
|
+
"O10",
|
|
2867
|
+
"O16"
|
|
2868
|
+
];
|
|
2869
|
+
/**
|
|
2870
|
+
* These are the four ways that a pod stops.
|
|
2871
|
+
* - A deletion runs the preStop hook, then SIGTERM. The endpoints drop the pod at its deletion,
|
|
2872
|
+
* thus the preStop sleep and the hold must cover the propagation (O04).
|
|
2873
|
+
* - A liveness restart also runs the preStop hook and SIGTERM, with the grace. The pod stays in its
|
|
2874
|
+
* endpoints until its readiness fails, thus readiness must fail first (O09).
|
|
2875
|
+
* - A crash and an OOM stop run no hook and no drain. The kubelet finds the exit at its relist, and
|
|
2876
|
+
* no rule bounds that time. A client that can reissue a request is the only guard.
|
|
2877
|
+
*/
|
|
2878
|
+
const STOPS = {
|
|
2879
|
+
deletion: {
|
|
2880
|
+
way: "deletion",
|
|
2881
|
+
signal: "SIGTERM",
|
|
2882
|
+
preStop: true,
|
|
2883
|
+
drain: true,
|
|
2884
|
+
leaves: "deletion",
|
|
2885
|
+
rules: [
|
|
2886
|
+
"O04",
|
|
2887
|
+
"O10",
|
|
2888
|
+
"O16"
|
|
2889
|
+
]
|
|
2890
|
+
},
|
|
2891
|
+
"liveness-restart": {
|
|
2892
|
+
way: "liveness-restart",
|
|
2893
|
+
signal: "SIGTERM",
|
|
2894
|
+
preStop: true,
|
|
2895
|
+
drain: true,
|
|
2896
|
+
leaves: "readiness",
|
|
2897
|
+
rules: [
|
|
2898
|
+
"O09",
|
|
2899
|
+
"O10",
|
|
2900
|
+
"O16"
|
|
2901
|
+
]
|
|
2902
|
+
},
|
|
2903
|
+
crash: {
|
|
2904
|
+
way: "crash",
|
|
2905
|
+
signal: null,
|
|
2906
|
+
preStop: false,
|
|
2907
|
+
drain: false,
|
|
2908
|
+
leaves: "exit",
|
|
2909
|
+
rules: []
|
|
2910
|
+
},
|
|
2911
|
+
oom: {
|
|
2912
|
+
way: "oom",
|
|
2913
|
+
signal: "SIGKILL",
|
|
2914
|
+
preStop: false,
|
|
2915
|
+
drain: false,
|
|
2916
|
+
leaves: "exit",
|
|
2917
|
+
rules: []
|
|
2918
|
+
}
|
|
2919
|
+
};
|
|
2920
|
+
/** The identifier of the finding of a way of stopping that runs no drain. */
|
|
2921
|
+
const STOP_RULE = "stop";
|
|
2922
|
+
/**
|
|
2923
|
+
* This function applies the rules to a declaration for one way of stopping. A rule of the drain
|
|
2924
|
+
* applies only when it guards that way. The other rules apply as `lint()` applies them. A way with
|
|
2925
|
+
* no drain also gets a finding of `STOP_RULE` that depends. No rule bounds the time until the
|
|
2926
|
+
* kubelet finds the exit, and the work in flight is lost.
|
|
2927
|
+
*/
|
|
2928
|
+
const lintStop = (svc, scenario, rules, way) => {
|
|
2929
|
+
const stop = STOPS[way];
|
|
2930
|
+
const applies = rules.filter((rule) => !DRAIN_RULE_IDS.includes(rule.id) || stop.rules.includes(rule.id));
|
|
2931
|
+
const findings = lint(svc, scenario, applies);
|
|
2932
|
+
if (stop.drain) return findings;
|
|
2933
|
+
return [...findings, {
|
|
2934
|
+
rule: STOP_RULE,
|
|
2935
|
+
scope: svc.name,
|
|
2936
|
+
verdict: "depends",
|
|
2937
|
+
statement: `a ${way} runs no preStop hook and no drain, and no rule bounds the time until the kubelet finds the exit`,
|
|
2938
|
+
window: "the work in flight is lost, and the endpoints keep the pod until the kubelet finds the exit",
|
|
2939
|
+
evaluated: [],
|
|
2940
|
+
unbound: ["exit_detection@pod"]
|
|
2941
|
+
}];
|
|
2942
|
+
};
|
|
2943
|
+
//#endregion
|
|
2688
2944
|
//#region src/profiles.ts
|
|
2689
2945
|
const s = (value) => ({
|
|
2690
2946
|
value,
|
|
@@ -3671,6 +3927,6 @@ const MACHINES = {
|
|
|
3671
3927
|
watchdog: WATCHDOG_MACHINE
|
|
3672
3928
|
};
|
|
3673
3929
|
//#endregion
|
|
3674
|
-
export { DECLARATION_RULE, DURATION_FRACTIONS, EPISODES_KEPT, EXIT_MACHINE, EXIT_SIGNALS, FORMAT, GATE_MACHINE, LATCH_MACHINE, LIMITER_RULES, MACHINES, POLLER_MACHINE, PROCESS_TRIGGERS, PROGRESS_RULE, SERVICE_RULES, SLIDING_WINDOW_MACHINE, STAGES, TOKEN_BUCKET_MACHINE, WATCHDOG_MACHINE, WINDOW_MACHINE, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, capKnob, cooperativeAbortAt, daemon, decide, detectionTime, drainMachine, embeddedProcess, ephemeral, evaluate, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, inequalities, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterMachine, limiterProblems, limiterStateOf, lint, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redact, redeliverOnce, replay, retryAfterSeconds, service, serviceBindings, stageRank, symbolsOf, verdictOf, watchdog, workerLoop };
|
|
3930
|
+
export { DECLARATION_RULE, DRAIN_RULE_IDS, DURATION_FRACTIONS, EPISODES_KEPT, EXIT_MACHINE, EXIT_SIGNALS, FORMAT, GATE_MACHINE, LATCH_MACHINE, LAW_STATISTIC, LIMITER_RULES, MACHINES, PLATFORM_MARKS, POLLER_MACHINE, PROCESS_TRIGGERS, PROGRESS_RULE, SERVICE_RULES, SLIDING_WINDOW_MACHINE, STAGES, STOPS, STOP_RULE, TOKEN_BUCKET_MACHINE, WATCHDOG_MACHINE, WINDOW_MACHINE, asDebounce, asWindow, backoffBand, backoffDelay, backoffProblems, capKnob, compress, compression, compressionOf, cooperativeAbortAt, daemon, decide, detectionTime, drainMachine, embeddedProcess, ephemeral, evaluate, failSpan, freeAt, heartbeat, heartbeatDetection, idleSpan, inequalities, isExitCode, isExitSignal, isMessageShape, keyedLimiter, kubernetes, limitRule, limiterMachine, limiterProblems, limiterStateOf, lint, lintStop, localDev, longestRetryAfter, matchesMessage, mayReissue, memoryStore, nodeProcess, platformClock, processPortOf, queueDrain, realClock, recoverSpan, redact, redeliverOnce, replay, retryAfterSeconds, service, serviceBindings, stageRank, symbolsOf, verdictOf, watchdog, workerLoop };
|
|
3675
3931
|
|
|
3676
3932
|
//# sourceMappingURL=index.js.map
|