@celilo/console-server 0.6.0 → 0.6.1
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/spa/index.html
CHANGED
|
@@ -4,7 +4,15 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>celilo</title>
|
|
7
|
-
|
|
7
|
+
<!-- Inline so there is no file to ship and no second request. The glyph is
|
|
8
|
+
drawn by the VIEWER's emoji font, so it will not look identical across
|
|
9
|
+
platforms, and a system without the woman-technologist ZWJ sequence
|
|
10
|
+
falls back to two glyphs side by side rather than one. -->
|
|
11
|
+
<link
|
|
12
|
+
rel="icon"
|
|
13
|
+
href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>👩💻</text></svg>"
|
|
14
|
+
/>
|
|
15
|
+
<script type="module" crossorigin src="/assets/index-bR5M5myY.js"></script>
|
|
8
16
|
<link rel="stylesheet" crossorigin href="/assets/index-eLF_dgP-.css">
|
|
9
17
|
</head>
|
|
10
18
|
<body>
|
package/src/upstream.ts
CHANGED
|
@@ -66,8 +66,15 @@ export interface UpstreamOptions {
|
|
|
66
66
|
const EXIT_DENIED = 126;
|
|
67
67
|
|
|
68
68
|
interface CacheEntry {
|
|
69
|
+
/** When the value SETTLED. Meaningless while `pending`. */
|
|
69
70
|
at: number;
|
|
70
|
-
|
|
71
|
+
/**
|
|
72
|
+
* The promise, not the resolved value — so concurrent callers for the same
|
|
73
|
+
* argv join one request instead of each starting their own.
|
|
74
|
+
*/
|
|
75
|
+
value: Promise<unknown>;
|
|
76
|
+
/** In flight. A pending entry is joined regardless of the TTL. */
|
|
77
|
+
pending: boolean;
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
export class Upstream {
|
|
@@ -90,10 +97,41 @@ export class Upstream {
|
|
|
90
97
|
async read<T>(argv: readonly string[]): Promise<T> {
|
|
91
98
|
const key = argv.join(' ');
|
|
92
99
|
const hit = this.cache.get(key);
|
|
93
|
-
|
|
100
|
+
// A PENDING entry is joined whatever the TTL says. This cache used to store
|
|
101
|
+
// the resolved value and `await` before setting it, so every caller that
|
|
102
|
+
// arrived while a read was in flight missed and started its own. The console
|
|
103
|
+
// polls every 5s and mounts several panels at once, and each panel opens its
|
|
104
|
+
// OWN ssh connection because ControlMaster is deliberately off (celilo#921),
|
|
105
|
+
// so identical argv went to celilo three or four times per cycle. Measured
|
|
106
|
+
// on the live console 2026-09-27: 28 `console:status` ops against 18
|
|
107
|
+
// `alerts:list` + 10 `backup:list`, with panels at 12-14s against a 15s
|
|
108
|
+
// client timeout.
|
|
109
|
+
//
|
|
110
|
+
// Joining only on a fresh TTL would not fix it: a read takes ~4s here and
|
|
111
|
+
// the TTL is 2s, so the entry expires WHILE IN FLIGHT and the next caller
|
|
112
|
+
// duplicates it anyway. With reads slower than the TTL that is the common
|
|
113
|
+
// case, not an edge one — hence `pending`.
|
|
114
|
+
if (hit && (hit.pending || this.now() - hit.at < this.ttl)) return hit.value as Promise<T>;
|
|
115
|
+
|
|
116
|
+
const value = this.runJson<T>(argv);
|
|
117
|
+
const entry: CacheEntry = { at: this.now(), value, pending: true };
|
|
118
|
+
this.cache.set(key, entry);
|
|
119
|
+
|
|
120
|
+
value.then(
|
|
121
|
+
() => {
|
|
122
|
+
// The TTL runs from when the value SETTLED, not when the request
|
|
123
|
+
// started; otherwise a read slower than the TTL is stale on arrival.
|
|
124
|
+
entry.at = this.now();
|
|
125
|
+
entry.pending = false;
|
|
126
|
+
},
|
|
127
|
+
() => {
|
|
128
|
+
// A failed read must not be served for the rest of the window, and must
|
|
129
|
+
// not leave a permanently-pending entry that every later caller joins.
|
|
130
|
+
// Retry it; do not remember it.
|
|
131
|
+
if (this.cache.get(key) === entry) this.cache.delete(key);
|
|
132
|
+
},
|
|
133
|
+
);
|
|
94
134
|
|
|
95
|
-
const value = await this.runJson<T>(argv);
|
|
96
|
-
this.cache.set(key, { at: this.now(), value });
|
|
97
135
|
return value;
|
|
98
136
|
}
|
|
99
137
|
|