querysub 0.508.0 → 0.509.0
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/bin/function-public.js +2 -0
- package/bin/server.js +0 -2
- package/package.json +3 -4
- package/spec.txt +5 -0
- package/src/-f-node-discovery/NodeDiscovery.ts +1 -1
- package/src/-g-core-values/NodeCapabilities.ts +4 -0
- package/src/-g-core-values/scheduledShutdown.ts +48 -0
- package/src/0-path-value-core/AuthorityLookup.ts +50 -9
- package/src/0-path-value-core/PathRouter.ts +10 -31
- package/src/0-path-value-core/PathRouterRouteOverride.ts +14 -98
- package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +1 -4
- package/src/0-path-value-core/PathValueCommitter.ts +1 -6
- package/src/0-path-value-core/PathValueController.ts +5 -7
- package/src/0-path-value-core/PathWatcher.ts +8 -11
- package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +8 -22
- package/src/0-path-value-core/pathValueCore.ts +4 -5
- package/src/1-path-client/RemoteWatcher.ts +47 -34
- package/src/2-proxy/archiveMoveHarness.ts +2 -1
- package/src/3-path-functions/PathFunctionHelpers.ts +11 -10
- package/src/3-path-functions/PathFunctionRunner.ts +165 -24
- package/src/3-path-functions/functionReplay.ts +2 -0
- package/src/3-path-functions/syncSchema.ts +0 -2
- package/src/4-deploy/edgeBootstrap.ts +10 -0
- package/src/4-deploy/edgeClientWatcher.tsx +4 -6
- package/src/4-deploy/edgeNodes.ts +52 -12
- package/src/4-querysub/FunctionRunnerTracking.ts +290 -0
- package/src/4-querysub/Querysub.ts +11 -16
- package/src/4-querysub/QuerysubController.ts +13 -11
- package/src/4-querysub/permissions.ts +3 -0
- package/src/4-querysub/querysubPrediction.ts +2 -2
- package/src/config.ts +1 -3
- package/src/deployManager/components/ServiceDetailPage.tsx +127 -73
- package/src/deployManager/components/ServicesListPage.tsx +3 -3
- package/src/deployManager/machineApplyMainCode.ts +139 -153
- package/src/deployManager/machineController.ts +12 -61
- package/src/deployManager/machineSchema.ts +84 -3
- package/src/deployManager/setupMachineMain.ts +2 -1
- package/src/diagnostics/grossStats/GrossStatsController.ts +3 -2
- package/src/diagnostics/logs/errorTickets/autoFixer.ts +3 -2
- package/src/diagnostics/logs/logGitHashes.ts +1 -1
- package/src/diagnostics/managementPages.tsx +2 -0
- package/src/diagnostics/misc-pages/AuthoritySpecPage.tsx +36 -28
- package/src/diagnostics/misc-pages/FunctionCaptureAnalyzePage.tsx +1 -1
- package/src/library-components/NetworkSelector.tsx +77 -0
- package/src/misc.ts +19 -0
- package/src/user-implementation/userData.ts +1 -1
- package/src/zipThreaded.ts +3 -2
- package/bin/server-dev.js +0 -11
|
@@ -92,7 +92,8 @@ async function getGitHubApiKey(repoUrl: string, sshRemote: string, forceRefresh
|
|
|
92
92
|
await open(tokenUrl);
|
|
93
93
|
|
|
94
94
|
const rl = readline.createInterface({
|
|
95
|
-
|
|
95
|
+
// Cast, as different @types/node versions disagree on the stream types
|
|
96
|
+
input: process.stdin as unknown as NodeJS.ReadableStream,
|
|
96
97
|
output: process.stdout
|
|
97
98
|
});
|
|
98
99
|
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { getOwnNodeId, isOwnNodeId } from "../../-f-node-discovery/NodeDiscovery";
|
|
13
13
|
import { authorityLookup } from "../../0-path-value-core/AuthorityLookup";
|
|
14
14
|
import { timeoutToUndefinedSilent } from "../../errors";
|
|
15
|
+
import { spreadCallsOverTime } from "../../misc";
|
|
15
16
|
|
|
16
17
|
const POLL_INTERVAL = timeInMinute;
|
|
17
18
|
const PER_NODE_TIMEOUT = 30 * timeInSecond;
|
|
@@ -31,7 +32,7 @@ async function listClusterNodeIds(): Promise<string[]> {
|
|
|
31
32
|
async function pollOnce() {
|
|
32
33
|
let nodeIds = await listClusterNodeIds();
|
|
33
34
|
let cutoff = Date.now() - RETENTION_MS;
|
|
34
|
-
await
|
|
35
|
+
await spreadCallsOverTime(nodeIds, POLL_INTERVAL, async nodeId => {
|
|
35
36
|
if (isOwnNodeId(nodeId)) {
|
|
36
37
|
polledBuckets.set(nodeId, getGrossStatsBuckets().filter(b => b.time >= cutoff));
|
|
37
38
|
return;
|
|
@@ -46,7 +47,7 @@ async function pollOnce() {
|
|
|
46
47
|
let merged = prev ? (result ? prev.concat(result.buckets) : prev) : (result?.buckets ?? []);
|
|
47
48
|
merged = merged.filter(b => b.time >= cutoff);
|
|
48
49
|
polledBuckets.set(nodeId, merged);
|
|
49
|
-
})
|
|
50
|
+
});
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
let ensurePolling = lazy(() => {
|
|
@@ -793,8 +793,9 @@ async function runClaude(prompt: string, takeNewUserComments: () => Promise<stri
|
|
|
793
793
|
}
|
|
794
794
|
});
|
|
795
795
|
}
|
|
796
|
-
|
|
797
|
-
forwardLines(child.
|
|
796
|
+
// Casts, as different @types/node versions disagree on the stream types
|
|
797
|
+
forwardLines(child.stdout as unknown as NodeJS.ReadableStream, "[claude]", line => handleClaudeStreamLine(line));
|
|
798
|
+
forwardLines(child.stderr as unknown as NodeJS.ReadableStream, "[claude:err]");
|
|
798
799
|
|
|
799
800
|
let timedOut = false;
|
|
800
801
|
let timeout = setTimeout(() => {
|
|
@@ -299,6 +299,8 @@ export async function isManagementUser() {
|
|
|
299
299
|
ModuleId: schema.moduleId,
|
|
300
300
|
FunctionId: functionId,
|
|
301
301
|
runAtTime: getNextTime(),
|
|
302
|
+
// This call is only evaluated locally to check permissions, and is never committed, so it never reaches a FunctionRunner network
|
|
303
|
+
network: "",
|
|
302
304
|
};
|
|
303
305
|
|
|
304
306
|
let writes = await getCallWrites({
|
|
@@ -10,6 +10,8 @@ import { NodeCapabilitiesController } from "../../-g-core-values/NodeCapabilitie
|
|
|
10
10
|
import { timeoutToUndefinedSilent } from "../../errors";
|
|
11
11
|
import { sort } from "socket-function/src/misc";
|
|
12
12
|
import type { AuthoritySpec } from "../../0-path-value-core/PathRouter";
|
|
13
|
+
import { getFunctionRunnerIndex } from "../../4-querysub/FunctionRunnerTracking";
|
|
14
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
13
15
|
|
|
14
16
|
const PROBE_TIMEOUT_MS = 5000;
|
|
15
17
|
const RANGE_BAR_WIDTH_PX = 360;
|
|
@@ -79,7 +81,6 @@ class AuthorityNodeRow extends qreact.Component<{ info: NodeAuthorityInfo }> {
|
|
|
79
81
|
<AuthorityRangeBar start={spec.routeStart} end={spec.routeEnd} />
|
|
80
82
|
<span className={css.colorhsl(0, 0, 50)}>width {(spec.routeEnd - spec.routeStart).toFixed(4)}</span>
|
|
81
83
|
{spec.excludeDefault && <span className={css.colorhsl(0, 70, 35)}>(excludes default)</span>}
|
|
82
|
-
{spec.networks && spec.networks.length > 0 && <span className={css.colorhsl(210, 60, 40)}>networks: {spec.networks === "all" && "all" || Array.isArray(spec.networks) && spec.networks.join(", ") || ""}</span>}
|
|
83
84
|
<span className={css.colorhsl(0, 0, 40).ellipsis.flexFillWidth}>{info.entryPoint || "(no entry point)"}</span>
|
|
84
85
|
</div>
|
|
85
86
|
{expanded &&
|
|
@@ -95,6 +96,36 @@ class AuthorityNodeRow extends qreact.Component<{ info: NodeAuthorityInfo }> {
|
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
class FunctionRunnersSection extends qreact.Component {
|
|
100
|
+
render() {
|
|
101
|
+
let index = getFunctionRunnerIndex();
|
|
102
|
+
let nodes = index?.nodes || [];
|
|
103
|
+
return <div className={css.vbox(8).fillWidth}>
|
|
104
|
+
<h2>Function Runners ({nodes.length})</h2>
|
|
105
|
+
{nodes.length === 0 && <div className={css.colorhsl(0, 0, 50)}>(no function runners found yet)</div>}
|
|
106
|
+
{nodes.map(node =>
|
|
107
|
+
<div className={css.vbox(4).pad2(10).fillWidth.bord2(0, 0, 85).hsl(0, 0, 99)}>
|
|
108
|
+
<div className={css.hbox(10).fillWidth}>
|
|
109
|
+
<span className={css.boldStyle}>{node.nodeId}</span>
|
|
110
|
+
<span className={css.colorhsl(210, 60, 40)}>networks: {node.networks.join(", ")}</span>
|
|
111
|
+
{!node.isPublic && <span className={css.colorhsl(0, 70, 35)}>(non-public)</span>}
|
|
112
|
+
<span>latency {formatTime(node.averageLatency)}</span>
|
|
113
|
+
<span>up for {formatTime(Date.now() - node.startupTime)}</span>
|
|
114
|
+
<span className={css.colorhsl(0, 0, 40).ellipsis}>{node.entryPoint}</span>
|
|
115
|
+
</div>
|
|
116
|
+
{node.shards.map(shard =>
|
|
117
|
+
<div className={css.hbox(10).fillWidth}>
|
|
118
|
+
<span>{shard.shardRange.startFraction.toFixed(4)} - {shard.shardRange.endFraction.toFixed(4)}</span>
|
|
119
|
+
<AuthorityRangeBar start={shard.shardRange.startFraction} end={shard.shardRange.endFraction} />
|
|
120
|
+
{shard.secondaryShardRange && <span className={css.colorhsl(0, 0, 50)}>secondary {shard.secondaryShardRange.startFraction.toFixed(4)} - {shard.secondaryShardRange.endFraction.toFixed(4)}</span>}
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
</div>;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
98
129
|
export class AuthoritySpecPage extends qreact.Component {
|
|
99
130
|
render() {
|
|
100
131
|
let infos = AuthoritySpecSynced(getBrowserUrlNode()).getAllNodeAuthoritySpecs();
|
|
@@ -104,35 +135,12 @@ export class AuthoritySpecPage extends qreact.Component {
|
|
|
104
135
|
infos = infos.filter(x => x.spec && x.spec.routeStart >= 0 && x.spec.routeEnd >= 0);
|
|
105
136
|
sort(infos, x => x.spec!.routeStart);
|
|
106
137
|
|
|
107
|
-
let infosPerNetwork = new Map<string, NodeAuthorityInfo[]>();
|
|
108
|
-
for (let info of infos) {
|
|
109
|
-
let networks = info.spec!.networks;
|
|
110
|
-
if (networks === "all") {
|
|
111
|
-
networks = ["all"];
|
|
112
|
-
}
|
|
113
|
-
if (!networks || networks.length === 0) {
|
|
114
|
-
networks = ["default"];
|
|
115
|
-
}
|
|
116
|
-
for (let network of networks) {
|
|
117
|
-
let list = infosPerNetwork.get(network);
|
|
118
|
-
if (!list) {
|
|
119
|
-
list = [];
|
|
120
|
-
infosPerNetwork.set(network, list);
|
|
121
|
-
}
|
|
122
|
-
list.push(info);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
let networkEntries = Array.from(infosPerNetwork.entries());
|
|
126
|
-
sort(networkEntries, entry => entry[0] === "default" && " " || entry[0]);
|
|
127
|
-
|
|
128
138
|
return <div className={css.vbox(12).pad2(16).fillWidth}>
|
|
129
139
|
<h2>Routing Table ({infos.length})</h2>
|
|
130
|
-
{
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
</div>
|
|
135
|
-
)}
|
|
140
|
+
<div className={css.vbox(8).fillWidth}>
|
|
141
|
+
{infos.map(info => <AuthorityNodeRow key={info.nodeId} info={info} />)}
|
|
142
|
+
</div>
|
|
143
|
+
<FunctionRunnersSection />
|
|
136
144
|
</div>;
|
|
137
145
|
}
|
|
138
146
|
}
|
|
@@ -158,7 +158,7 @@ export class FunctionCaptureAnalyzePage extends qreact.Component {
|
|
|
158
158
|
return <div className={css.vbox(2).fillWidth}>
|
|
159
159
|
<div className={css.fontWeight("bold")}>{title} ({formatNumber(rows.length)})</div>
|
|
160
160
|
<div className={css.vbox(1).fillWidth.fontFamily("monospace").fontSize(12)}>
|
|
161
|
-
{shown.map(row => <div className={css.whiteSpace("pre-wrap").
|
|
161
|
+
{shown.map(row => <div className={css.whiteSpace("pre-wrap").overflowWrap("break-word")}>{row}</div>)}
|
|
162
162
|
</div>
|
|
163
163
|
{remaining > 0 &&
|
|
164
164
|
<Button onClick={() => Querysub.localCommit(() => { this.state.rowLimits[sectionId] = limit + ROW_LIMIT; })}>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { css } from "typesafecss";
|
|
2
|
+
import { qreact } from "../4-dom/qreact";
|
|
3
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
4
|
+
import { isCurrentUserSuperUser } from "../user-implementation/userData";
|
|
5
|
+
import { forcedNetworkURL, getAutoSelectedNetwork, getNetworkSelectionInfos, getSelectedNetwork } from "../4-querysub/FunctionRunnerTracking";
|
|
6
|
+
|
|
7
|
+
/** A dropdown showing all the function runner networks, which one our function calls are being put on, and allowing forcing a specific network (and unforcing it, going back to the automatic selection). */
|
|
8
|
+
export class NetworkSelector extends qreact.Component<{}> {
|
|
9
|
+
render() {
|
|
10
|
+
let infos = getNetworkSelectionInfos();
|
|
11
|
+
let forced = forcedNetworkURL.value;
|
|
12
|
+
let selected = getSelectedNetwork({ noThrow: true });
|
|
13
|
+
let auto = getAutoSelectedNetwork();
|
|
14
|
+
let isSuperUser = isCurrentUserSuperUser();
|
|
15
|
+
|
|
16
|
+
// Only networks that actually exist (have function runners) can be picked
|
|
17
|
+
let networks = infos.map(x => x.network);
|
|
18
|
+
if (forced && !networks.includes(forced)) {
|
|
19
|
+
networks.push(forced);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!selected) {
|
|
23
|
+
return <div className={css.hbox(6)}>
|
|
24
|
+
<span className={css.opacity(0.7)}>Network</span>
|
|
25
|
+
<span className={css.pad2(6, 2).bord2(0, 85, 55).colorhsl(0, 85, 55).boldStyle}>⛔ no function runner networks</span>
|
|
26
|
+
</div>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return <div className={css.hbox(6)}>
|
|
30
|
+
<span className={css.opacity(0.7)}>Network</span>
|
|
31
|
+
<select
|
|
32
|
+
value={selected}
|
|
33
|
+
className={css.pad2(4, 2).hsl(0, 0, 16).colorhsl(0, 0, 90).bord2(0, 0, 30)}
|
|
34
|
+
onChange={e => {
|
|
35
|
+
forcedNetworkURL.value = e.currentTarget.value;
|
|
36
|
+
}}
|
|
37
|
+
>
|
|
38
|
+
{networks.map(network => {
|
|
39
|
+
let info = infos.find(x => x.network === network);
|
|
40
|
+
let label = network;
|
|
41
|
+
if (info) {
|
|
42
|
+
label += ` (${info.nodeCount} runner${info.nodeCount !== 1 && "s" || ""}, ${formatTime(info.averageLatency)} latency`;
|
|
43
|
+
if (info.averageCallTime) {
|
|
44
|
+
label += `, ${formatTime(info.averageCallTime)} avg call`;
|
|
45
|
+
}
|
|
46
|
+
if (!info.isPublic) {
|
|
47
|
+
label += `, non-public`;
|
|
48
|
+
}
|
|
49
|
+
if (info.dying) {
|
|
50
|
+
label += `, shutting down`;
|
|
51
|
+
}
|
|
52
|
+
label += `)`;
|
|
53
|
+
}
|
|
54
|
+
let disabled = !!info && !info.isPublic && !isSuperUser;
|
|
55
|
+
return <option value={network} disabled={disabled}>{label}</option>;
|
|
56
|
+
})}
|
|
57
|
+
</select>
|
|
58
|
+
{forced && <span
|
|
59
|
+
className={css.button.pad2(6, 2).hsl(35, 60, 22).colorhsl(0, 0, 90).bord2(35, 60, 40)}
|
|
60
|
+
title="The network is forced. Click to reset back to the automatically picked network."
|
|
61
|
+
onMouseDown={() => forcedNetworkURL.value = ""}
|
|
62
|
+
>
|
|
63
|
+
auto: {auto}
|
|
64
|
+
</span>}
|
|
65
|
+
{(() => {
|
|
66
|
+
let selectedInfo = infos.find(x => x.network === selected);
|
|
67
|
+
if (!selectedInfo || selectedInfo.nodeCount === 0) {
|
|
68
|
+
return <span className={css.pad2(6, 2).bord2(0, 85, 55).colorhsl(0, 85, 55).boldStyle}>⛔ no function runners on this network</span>;
|
|
69
|
+
}
|
|
70
|
+
if (selectedInfo.dying) {
|
|
71
|
+
return <span className={css.colorhsl(35, 85, 55)}>⚠️ this network is shutting down</span>;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
})()}
|
|
75
|
+
</div>;
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/misc.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { canHaveChildren } from "socket-function/src/types";
|
|
2
|
+
import { delay } from "socket-function/src/batching";
|
|
2
3
|
|
|
3
4
|
// TIMING: About 20MB/s
|
|
4
5
|
export function createRandomText(count: number): string {
|
|
@@ -194,4 +195,22 @@ export function isAsyncFunction(func: unknown): boolean {
|
|
|
194
195
|
|
|
195
196
|
export function maybeUndefined<T>(value: T): T | undefined {
|
|
196
197
|
return value;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Runs `callback` for every item, but staggers the calls evenly across `totalTime` instead of
|
|
201
|
+
* firing them all at once, and awaits all of them before returning. Used inside a poll loop
|
|
202
|
+
* (with totalTime = the poll interval) so fanning out to many nodes doesn't spike — otherwise
|
|
203
|
+
* every dead node's timeout lands at the same instant each cycle. */
|
|
204
|
+
export async function spreadCallsOverTime<T>(
|
|
205
|
+
items: T[],
|
|
206
|
+
totalTime: number,
|
|
207
|
+
callback: (item: T, index: number) => Promise<void>
|
|
208
|
+
): Promise<void> {
|
|
209
|
+
await Promise.all(items.map(async (item, index) => {
|
|
210
|
+
let startOffset = index / items.length * totalTime;
|
|
211
|
+
if (startOffset > 0) {
|
|
212
|
+
await delay(startOffset);
|
|
213
|
+
}
|
|
214
|
+
await callback(item, index);
|
|
215
|
+
}));
|
|
197
216
|
}
|
|
@@ -892,7 +892,7 @@ export const LOCAL_STORAGE_USER_TYPE_KEY = "userType";
|
|
|
892
892
|
export const LOCAL_STORAGE_COMMIT_DELAY_KEY = "LOCAL_STORAGE_COMMIT_DELAY_KEY";
|
|
893
893
|
if (!isNode()) {
|
|
894
894
|
logErrors((async () => {
|
|
895
|
-
await Querysub.
|
|
895
|
+
await Querysub.startupWait();
|
|
896
896
|
let isLoggedIn = await Querysub.commitSynced(() => isCurrentUserType("user"));
|
|
897
897
|
if (isLoggedIn) {
|
|
898
898
|
Querysub.commit(() => functions.registerPageLoadTime());
|
package/src/zipThreaded.ts
CHANGED
|
@@ -64,8 +64,9 @@ async function spawnWorker() {
|
|
|
64
64
|
let { promise, onFree } = messageQueue.shift()!;
|
|
65
65
|
onFree();
|
|
66
66
|
workerObj.queueDepth = messageQueue.length;
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
let error = err as Error;
|
|
68
|
+
console.log(`Worker error: ${error.stack || error}`);
|
|
69
|
+
promise.reject(error);
|
|
69
70
|
});
|
|
70
71
|
async function unzip(buffer: Buffer): Promise<Buffer> {
|
|
71
72
|
let promise = new PromiseObj<Buffer>();
|
package/bin/server-dev.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
process.argv.push("--local");
|
|
4
|
-
// Serves the dev network AND the default network, so one server can satisfy paths with no network as well as paths on our dev network.
|
|
5
|
-
process.argv.push("--networkfile");
|
|
6
|
-
process.argv.push("~/devnetwork.txt");
|
|
7
|
-
process.argv.push("--network");
|
|
8
|
-
process.argv.push("default");
|
|
9
|
-
|
|
10
|
-
require("typenode");
|
|
11
|
-
require("../src/server.ts");
|