querysub 0.2.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/.dependency-cruiser.js +304 -0
- package/.eslintrc.js +51 -0
- package/.github/copilot-instructions.md +1 -0
- package/.vscode/settings.json +25 -0
- package/bin/deploy.js +4 -0
- package/bin/function.js +4 -0
- package/bin/server.js +4 -0
- package/costsBenefits.txt +112 -0
- package/deploy.ts +3 -0
- package/inject.ts +1 -0
- package/package.json +60 -0
- package/prompts.txt +54 -0
- package/spec.txt +820 -0
- package/src/-a-archives/archiveCache.ts +913 -0
- package/src/-a-archives/archives.ts +148 -0
- package/src/-a-archives/archivesBackBlaze.ts +792 -0
- package/src/-a-archives/archivesDisk.ts +418 -0
- package/src/-a-archives/copyLocalToBackblaze.ts +24 -0
- package/src/-a-auth/certs.ts +517 -0
- package/src/-a-auth/der.ts +122 -0
- package/src/-a-auth/ed25519.ts +1015 -0
- package/src/-a-auth/node-forge-ed25519.d.ts +17 -0
- package/src/-b-authorities/dnsAuthority.ts +203 -0
- package/src/-b-authorities/emailAuthority.ts +57 -0
- package/src/-c-identity/IdentityController.ts +200 -0
- package/src/-d-trust/NetworkTrust2.ts +150 -0
- package/src/-e-certs/EdgeCertController.ts +288 -0
- package/src/-e-certs/certAuthority.ts +192 -0
- package/src/-f-node-discovery/NodeDiscovery.ts +543 -0
- package/src/-g-core-values/NodeCapabilities.ts +134 -0
- package/src/-g-core-values/oneTimeForward.ts +91 -0
- package/src/-h-path-value-serialize/PathValueSerializer.ts +769 -0
- package/src/-h-path-value-serialize/stringSerializer.ts +176 -0
- package/src/0-path-value-core/LoggingClient.tsx +24 -0
- package/src/0-path-value-core/NodePathAuthorities.ts +978 -0
- package/src/0-path-value-core/PathController.ts +1 -0
- package/src/0-path-value-core/PathValueCommitter.ts +565 -0
- package/src/0-path-value-core/PathValueController.ts +231 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +154 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +820 -0
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +180 -0
- package/src/0-path-value-core/debugLogs.ts +90 -0
- package/src/0-path-value-core/pathValueArchives.ts +483 -0
- package/src/0-path-value-core/pathValueCore.ts +2217 -0
- package/src/1-path-client/RemoteWatcher.ts +558 -0
- package/src/1-path-client/pathValueClientWatcher.ts +702 -0
- package/src/2-proxy/PathValueProxyWatcher.ts +1857 -0
- package/src/2-proxy/archiveMoveHarness.ts +376 -0
- package/src/2-proxy/garbageCollection.ts +753 -0
- package/src/2-proxy/pathDatabaseProxyBase.ts +37 -0
- package/src/2-proxy/pathValueProxy.ts +139 -0
- package/src/2-proxy/schema2.ts +518 -0
- package/src/3-path-functions/PathFunctionHelpers.ts +129 -0
- package/src/3-path-functions/PathFunctionRunner.ts +619 -0
- package/src/3-path-functions/PathFunctionRunnerMain.ts +67 -0
- package/src/3-path-functions/deployBlock.ts +10 -0
- package/src/3-path-functions/deployCheck.ts +7 -0
- package/src/3-path-functions/deployMain.ts +160 -0
- package/src/3-path-functions/pathFunctionLoader.ts +282 -0
- package/src/3-path-functions/syncSchema.ts +475 -0
- package/src/3-path-functions/tests/functionsTest.ts +135 -0
- package/src/3-path-functions/tests/rejectTest.ts +77 -0
- package/src/4-dom/css.tsx +29 -0
- package/src/4-dom/cssTypes.d.ts +212 -0
- package/src/4-dom/qreact.tsx +2322 -0
- package/src/4-dom/qreactTest.tsx +417 -0
- package/src/4-querysub/Querysub.ts +877 -0
- package/src/4-querysub/QuerysubController.ts +620 -0
- package/src/4-querysub/copyEvent.ts +0 -0
- package/src/4-querysub/permissions.ts +289 -0
- package/src/4-querysub/permissionsShared.ts +1 -0
- package/src/4-querysub/querysubPrediction.ts +525 -0
- package/src/5-diagnostics/FullscreenModal.tsx +67 -0
- package/src/5-diagnostics/GenericFormat.tsx +165 -0
- package/src/5-diagnostics/Modal.tsx +79 -0
- package/src/5-diagnostics/Table.tsx +183 -0
- package/src/5-diagnostics/TimeGrouper.tsx +114 -0
- package/src/5-diagnostics/diskValueAudit.ts +216 -0
- package/src/5-diagnostics/memoryValueAudit.ts +442 -0
- package/src/5-diagnostics/nodeMetadata.ts +135 -0
- package/src/5-diagnostics/qreactDebug.tsx +309 -0
- package/src/5-diagnostics/shared.ts +26 -0
- package/src/5-diagnostics/synchronousLagTracking.ts +47 -0
- package/src/TestController.ts +35 -0
- package/src/allowclient.flag +0 -0
- package/src/bits.ts +86 -0
- package/src/buffers.ts +69 -0
- package/src/config.ts +53 -0
- package/src/config2.ts +48 -0
- package/src/diagnostics/ActionsHistory.ts +56 -0
- package/src/diagnostics/NodeViewer.tsx +503 -0
- package/src/diagnostics/SizeLimiter.ts +62 -0
- package/src/diagnostics/TimeDebug.tsx +18 -0
- package/src/diagnostics/benchmark.ts +139 -0
- package/src/diagnostics/errorLogs/ErrorLogController.ts +515 -0
- package/src/diagnostics/errorLogs/ErrorLogCore.ts +274 -0
- package/src/diagnostics/errorLogs/LogClassifiers.tsx +302 -0
- package/src/diagnostics/errorLogs/LogFilterUI.tsx +84 -0
- package/src/diagnostics/errorLogs/LogNotify.tsx +101 -0
- package/src/diagnostics/errorLogs/LogTimeSelector.tsx +724 -0
- package/src/diagnostics/errorLogs/LogViewer.tsx +757 -0
- package/src/diagnostics/errorLogs/hookErrors.ts +60 -0
- package/src/diagnostics/errorLogs/logFiltering.tsx +149 -0
- package/src/diagnostics/heapTag.ts +13 -0
- package/src/diagnostics/listenOnDebugger.ts +77 -0
- package/src/diagnostics/logs/DiskLoggerPage.tsx +572 -0
- package/src/diagnostics/logs/ObjectDisplay.tsx +165 -0
- package/src/diagnostics/logs/ansiFormat.ts +108 -0
- package/src/diagnostics/logs/diskLogGlobalContext.ts +38 -0
- package/src/diagnostics/logs/diskLogger.ts +305 -0
- package/src/diagnostics/logs/diskShimConsoleLogs.ts +32 -0
- package/src/diagnostics/logs/injectFileLocationToConsole.ts +50 -0
- package/src/diagnostics/logs/logGitHashes.ts +30 -0
- package/src/diagnostics/managementPages.tsx +289 -0
- package/src/diagnostics/periodic.ts +89 -0
- package/src/diagnostics/runSaturationTest.ts +416 -0
- package/src/diagnostics/satSchema.ts +64 -0
- package/src/diagnostics/trackResources.ts +82 -0
- package/src/diagnostics/watchdog.ts +55 -0
- package/src/errors.ts +132 -0
- package/src/forceProduction.ts +3 -0
- package/src/fs.ts +72 -0
- package/src/heapDumps.ts +666 -0
- package/src/https.ts +2 -0
- package/src/inject.ts +1 -0
- package/src/library-components/ATag.tsx +84 -0
- package/src/library-components/Button.tsx +344 -0
- package/src/library-components/ButtonSelector.tsx +64 -0
- package/src/library-components/DropdownCustom.tsx +151 -0
- package/src/library-components/DropdownSelector.tsx +32 -0
- package/src/library-components/Input.tsx +334 -0
- package/src/library-components/InputLabel.tsx +198 -0
- package/src/library-components/InputPicker.tsx +125 -0
- package/src/library-components/LazyComponent.tsx +62 -0
- package/src/library-components/MeasureHeightCSS.tsx +48 -0
- package/src/library-components/MeasuredDiv.tsx +47 -0
- package/src/library-components/ShowMore.tsx +51 -0
- package/src/library-components/SyncedController.ts +171 -0
- package/src/library-components/TimeRangeSelector.tsx +407 -0
- package/src/library-components/URLParam.ts +263 -0
- package/src/library-components/colors.tsx +14 -0
- package/src/library-components/drag.ts +114 -0
- package/src/library-components/icons.tsx +692 -0
- package/src/library-components/niceStringify.ts +50 -0
- package/src/library-components/renderToString.ts +52 -0
- package/src/misc/PromiseRace.ts +101 -0
- package/src/misc/color.ts +30 -0
- package/src/misc/getParentProcessId.cs +53 -0
- package/src/misc/getParentProcessId.ts +53 -0
- package/src/misc/hash.ts +83 -0
- package/src/misc/ipPong.js +13 -0
- package/src/misc/networking.ts +2 -0
- package/src/misc/random.ts +45 -0
- package/src/misc.ts +19 -0
- package/src/noserverhotreload.flag +0 -0
- package/src/path.ts +226 -0
- package/src/persistentLocalStore.ts +37 -0
- package/src/promise.ts +15 -0
- package/src/server.ts +73 -0
- package/src/src.d.ts +1 -0
- package/src/test/heapProcess.ts +36 -0
- package/src/test/mongoSatTest.tsx +55 -0
- package/src/test/satTest.ts +193 -0
- package/src/test/test.tsx +552 -0
- package/src/zip.ts +92 -0
- package/src/zipThreaded.ts +106 -0
- package/src/zipThreadedWorker.js +19 -0
- package/tsconfig.json +27 -0
- package/yarnSpec.txt +56 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
import { delay, runInfinitePoll } from "socket-function/src/batching";
|
|
2
|
+
import { cache } from "socket-function/src/caching";
|
|
3
|
+
import { magenta, red, yellow } from "socket-function/src/formatting/logColors";
|
|
4
|
+
import { sha256HashBuffer, timeInHour } from "socket-function/src/misc";
|
|
5
|
+
import { measureFnc } from "socket-function/src/profiling/measure";
|
|
6
|
+
import { getLowUint32, getShortNumber } from "../bits";
|
|
7
|
+
import { registerDynamicResource } from "../diagnostics/trackResources";
|
|
8
|
+
import { logErrors } from "../errors";
|
|
9
|
+
import { getPathDepth, getPathFromStr, getPathIndex, trimPathStrToDepth } from "../path";
|
|
10
|
+
import { rawSchema } from "../2-proxy/pathDatabaseProxyBase";
|
|
11
|
+
import { ClientWatcher } from "../1-path-client/pathValueClientWatcher";
|
|
12
|
+
import { getProxyPath } from "../2-proxy/pathValueProxy";
|
|
13
|
+
import { atomic, atomicObjectRead, atomicObjectWrite, doAtomicWrites, doProxyOptions, isSynced, proxyWatcher } from "../2-proxy/PathValueProxyWatcher";
|
|
14
|
+
import { __getRoutingHash, authorityStorage, compareTime, debugTime, epochTime, getNextTime, isCoreQuiet, MAX_ACCEPTED_CHANGE_AGE, MAX_CHANGE_AGE, PathValue, Time } from "../0-path-value-core/pathValueCore";
|
|
15
|
+
import { getModuleFromSpec, watchModuleHotreloads } from "./pathFunctionLoader";
|
|
16
|
+
import debugbreak from "debugbreak";
|
|
17
|
+
import { Querysub } from "../4-querysub/Querysub";
|
|
18
|
+
import { parseArgs } from "./PathFunctionHelpers";
|
|
19
|
+
import { PERMISSIONS_FUNCTION_ID, getExportPath } from "./syncSchema";
|
|
20
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
21
|
+
import { set_debug_getFunctionRunnerShards } from "../-g-core-values/NodeCapabilities";
|
|
22
|
+
|
|
23
|
+
export const functionSchema = rawSchema<{
|
|
24
|
+
[domainName: string]: {
|
|
25
|
+
PathFunctionRunner: {
|
|
26
|
+
[ModuleId: string]: {
|
|
27
|
+
Data: unknown;
|
|
28
|
+
Sources: { [FunctionId: string]: FunctionSpec | undefined; };
|
|
29
|
+
Calls: { [CallId: string]: CallSpec | undefined; };
|
|
30
|
+
Results: { [CallId: string]: FunctionResult | undefined; };
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
}>();
|
|
35
|
+
|
|
36
|
+
export const DEPTH_TO_DATA = 4;
|
|
37
|
+
export const DOMAIN_INDEX = 0;
|
|
38
|
+
export const MODULE_INDEX = 2;
|
|
39
|
+
|
|
40
|
+
export function getSchemaPartsFromPath(path: string): { domainName: string; moduleId: string } | undefined {
|
|
41
|
+
let parts = getPathFromStr(path);
|
|
42
|
+
let domainName = parts[0];
|
|
43
|
+
let moduleId = parts[2];
|
|
44
|
+
if (!domainName || !moduleId) return undefined;
|
|
45
|
+
return { domainName, moduleId };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type FunctionSpec = {
|
|
49
|
+
DomainName: string;
|
|
50
|
+
ModuleId: string;
|
|
51
|
+
FunctionId: string;
|
|
52
|
+
exportPathStr: string;
|
|
53
|
+
FilePath: string;
|
|
54
|
+
gitURL: string;
|
|
55
|
+
gitRef: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export interface CallSpec {
|
|
59
|
+
DomainName: string;
|
|
60
|
+
ModuleId: string;
|
|
61
|
+
CallId: string;
|
|
62
|
+
FunctionId: string;
|
|
63
|
+
// NOTE: This is no longer JSON. Use parseArgs to decode it
|
|
64
|
+
argsEncoded: string;
|
|
65
|
+
callerMachineId: string;
|
|
66
|
+
callerIP: string;
|
|
67
|
+
runAtTime: Time;
|
|
68
|
+
}
|
|
69
|
+
export function debugCallSpec(spec: CallSpec): string {
|
|
70
|
+
return `${spec.DomainName}/${spec.ModuleId}/${spec.FunctionId}`;
|
|
71
|
+
}
|
|
72
|
+
export type FunctionResult = ({
|
|
73
|
+
|
|
74
|
+
} | {
|
|
75
|
+
error: string;
|
|
76
|
+
}) & {
|
|
77
|
+
timeTaken: number;
|
|
78
|
+
evalTime: number;
|
|
79
|
+
runCount: number;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let currentCallSpec: CallSpec | undefined;
|
|
83
|
+
export function getCurrentCall() {
|
|
84
|
+
if (!currentCallSpec) {
|
|
85
|
+
debugbreak(2);
|
|
86
|
+
debugger;
|
|
87
|
+
throw new Error("Not presently in a call, and so cannot get call");
|
|
88
|
+
}
|
|
89
|
+
return currentCallSpec;
|
|
90
|
+
}
|
|
91
|
+
export function getCurrentCallAllowUndefined() {
|
|
92
|
+
return currentCallSpec;
|
|
93
|
+
}
|
|
94
|
+
export function overrideCurrentCall<T>(call: CallSpec, code: () => T) {
|
|
95
|
+
let prev = currentCallSpec;
|
|
96
|
+
currentCallSpec = call;
|
|
97
|
+
try {
|
|
98
|
+
return code();
|
|
99
|
+
} finally {
|
|
100
|
+
currentCallSpec = prev;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getDebugName(call: CallSpec, functionSpec: FunctionSpec, colored = false) {
|
|
105
|
+
let pathName = getPathFromStr(functionSpec.exportPathStr).slice(1).join(".");
|
|
106
|
+
let mainPart = `${call.DomainName}:${functionSpec.FilePath}|${pathName}|${debugTime(call.runAtTime)}`;
|
|
107
|
+
if (colored) {
|
|
108
|
+
return `${magenta(mainPart)}`;
|
|
109
|
+
} else {
|
|
110
|
+
return `${mainPart}`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
type PermissionsCheckType = {
|
|
115
|
+
new(callerMachineId: { callerMachineId: string; callerIP: string; }): {
|
|
116
|
+
checkPermissions(path: string): { permissionsPath: string; allowed: boolean; };
|
|
117
|
+
};
|
|
118
|
+
skipPermissionsChecks: <T>(code: () => T) => T;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export type DebugFunctionShardInfo = {
|
|
122
|
+
domainName: string;
|
|
123
|
+
shardRange: { startFraction: number, endFraction: number };
|
|
124
|
+
secondaryShardRange?: { startFraction: number, endFraction: number };
|
|
125
|
+
};
|
|
126
|
+
let debugFunctionRunnerShards: DebugFunctionShardInfo[] = [];
|
|
127
|
+
export function debug_getFunctionRunnerShards() {
|
|
128
|
+
return debugFunctionRunnerShards;
|
|
129
|
+
}
|
|
130
|
+
setImmediate(() => {
|
|
131
|
+
set_debug_getFunctionRunnerShards(debug_getFunctionRunnerShards);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
export class PathFunctionRunner {
|
|
135
|
+
public static RUN_START_COUNT = 0;
|
|
136
|
+
public static RUN_FINISH_COUNT = 0;
|
|
137
|
+
public static DEBUG_CALLS = false;
|
|
138
|
+
public static DEBUG_CALL_TRIGGERS = false;
|
|
139
|
+
public static DEBUG_WATCHES_THRESHOLD = 5;
|
|
140
|
+
public static MAX_WATCH_LOOPS = 1000;
|
|
141
|
+
|
|
142
|
+
constructor(private config: {
|
|
143
|
+
domainName: string;
|
|
144
|
+
shardRange: { startFraction: number, endFraction: number };
|
|
145
|
+
secondaryShardRange?: { startFraction: number, endFraction: number };
|
|
146
|
+
PermissionsChecker: PermissionsCheckType | undefined;
|
|
147
|
+
}) {
|
|
148
|
+
debugFunctionRunnerShards.push({
|
|
149
|
+
domainName: config.domainName,
|
|
150
|
+
shardRange: config.shardRange,
|
|
151
|
+
secondaryShardRange: config.secondaryShardRange,
|
|
152
|
+
});
|
|
153
|
+
logErrors(this.startWatching());
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Calls runCall on any calls that match our domain + shard range
|
|
157
|
+
private async startWatching() {
|
|
158
|
+
let { shardRange, secondaryShardRange } = this.config;
|
|
159
|
+
|
|
160
|
+
// We will use PathValueProxyWatcher to watch the paths
|
|
161
|
+
// Maintain only one outstanding runCall per callId
|
|
162
|
+
// - Once it finishes, we can trigger another call, if we notice the result is not set.
|
|
163
|
+
// - AS we should get write prediction for the result, we can rerun our proxy check
|
|
164
|
+
// loop immediately.
|
|
165
|
+
|
|
166
|
+
let fullFraction = shardRange;
|
|
167
|
+
if (secondaryShardRange) {
|
|
168
|
+
fullFraction = {
|
|
169
|
+
startFraction: Math.min(shardRange.startFraction, secondaryShardRange.startFraction),
|
|
170
|
+
endFraction: Math.max(shardRange.endFraction, secondaryShardRange.endFraction),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let config = this.config;
|
|
175
|
+
|
|
176
|
+
let self = this;
|
|
177
|
+
|
|
178
|
+
let outstandingCalls = 0;
|
|
179
|
+
|
|
180
|
+
let watchModuleCalls = cache((moduleId: string) => {
|
|
181
|
+
// Object.keys(functionSchema()[domainName].PathFunctionRunner[moduleId].Calls);
|
|
182
|
+
// Object.keys(functionSchema()[domainName].PathFunctionRunner[moduleId].Results);
|
|
183
|
+
Querysub.keys(functionSchema()[domainName].PathFunctionRunner[moduleId].Calls, fullFraction);
|
|
184
|
+
Querysub.keys(functionSchema()[domainName].PathFunctionRunner[moduleId].Results, fullFraction);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
let callsToCall = new Map<string, { call: CallSpec; functionSpec: FunctionSpec }>();
|
|
188
|
+
let runningCalls = new Set<string>();
|
|
189
|
+
|
|
190
|
+
let callIdsToUnwatch = new Set<{ filePath: string; callId: string; }>();
|
|
191
|
+
|
|
192
|
+
// Rarely we might need to queue a function multiple times, when we are late to receive rejections.
|
|
193
|
+
// HOWEVER, after enough times, we should stop, as we will probably just infinitely queue it.
|
|
194
|
+
const MAX_QUEUE_COUNT = 25;
|
|
195
|
+
let queueLimitCounts = new Map<string, number>();
|
|
196
|
+
// Clear every hour, so we don't leave
|
|
197
|
+
runInfinitePoll(timeInHour, () => queueLimitCounts.clear());
|
|
198
|
+
|
|
199
|
+
const domainName = config.domainName;
|
|
200
|
+
const watcher = proxyWatcher.createWatcher({
|
|
201
|
+
trackTriggers: true,
|
|
202
|
+
skipPermissionsCheck: true,
|
|
203
|
+
// Maybe allow running with unsynced reads? This might be good, because we don't need all reads
|
|
204
|
+
// to detect calls. BUT, it might also be bad, because it might cause us to rerun in a cascading
|
|
205
|
+
// fashion? (As in, be really slow).
|
|
206
|
+
// - Shouldn't be needed, because writes don't take that long
|
|
207
|
+
// - Could fix bugs where we never get a read, and therefore all function calls fails to run?
|
|
208
|
+
// allowUnsyncedReads: true
|
|
209
|
+
watchFunction: function findFunctionsToCall() {
|
|
210
|
+
// TODO: Maybe abstract the "preserve watchers" stuff,
|
|
211
|
+
// so that it is automatically done at the start of our trigger.
|
|
212
|
+
const watcher = proxyWatcher.getTriggeredWatcher();
|
|
213
|
+
|
|
214
|
+
// Preserve previous watches explicitly (so we don't have to use the proxy, which would be slower)
|
|
215
|
+
{
|
|
216
|
+
for (let path of watcher.lastUnsyncedAccesses) {
|
|
217
|
+
if (!authorityStorage.isSynced(path)) {
|
|
218
|
+
watcher.pendingUnsyncedAccesses.add(path);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
for (let path of watcher.lastUnsyncedParentAccesses) {
|
|
222
|
+
if (!authorityStorage.isParentSynced(path)) {
|
|
223
|
+
watcher.pendingUnsyncedParentAccesses.add(path);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// NOTE: If we don't clone, then pathValueClientWatcher cannot detect changes, and we
|
|
227
|
+
// end up staying watched on everything!
|
|
228
|
+
watcher.pendingWatches.paths = new Set(watcher.lastWatches.paths);
|
|
229
|
+
watcher.pendingWatches.parentPaths = new Set(watcher.lastWatches.parentPaths);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
let data = () => functionSchema()[domainName].PathFunctionRunner;
|
|
235
|
+
|
|
236
|
+
for (let { filePath, callId } of callIdsToUnwatch) {
|
|
237
|
+
watcher.pendingWatches.paths.delete(getProxyPath(() => data()[filePath].Calls[callId]));
|
|
238
|
+
watcher.pendingWatches.paths.delete(getProxyPath(() => data()[filePath].Results[callId]));
|
|
239
|
+
}
|
|
240
|
+
callIdsToUnwatch.clear();
|
|
241
|
+
for (let [filePath, moduleData] of Object.entries(data())) {
|
|
242
|
+
watchModuleCalls(filePath);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let triggerPaths = new Set<string>();
|
|
246
|
+
for (let [filePath, moduleData] of Object.entries(data())) {
|
|
247
|
+
let callPath = getProxyPath(() => data()[filePath].Calls);
|
|
248
|
+
triggerPaths.add(callPath);
|
|
249
|
+
|
|
250
|
+
// ALSO, Result changes (result rejections), can trigger a run
|
|
251
|
+
let resultPath = getProxyPath(() => data()[filePath].Results);
|
|
252
|
+
triggerPaths.add(resultPath);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let callPathLength = 0;
|
|
256
|
+
if (triggerPaths.size > 0) {
|
|
257
|
+
callPathLength = getPathDepth(Array.from(triggerPaths)[0]);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let newCalls: { call: CallSpec; functionSpec: FunctionSpec }[] = [];
|
|
261
|
+
|
|
262
|
+
if (watcher.triggeredByChanges) {
|
|
263
|
+
for (let path of watcher.triggeredByChanges.paths) {
|
|
264
|
+
let callPathEquivalent = trimPathStrToDepth(path, callPathLength);
|
|
265
|
+
if (triggerPaths.has(callPathEquivalent)) {
|
|
266
|
+
const callId = getPathIndex(path, callPathLength);
|
|
267
|
+
if (!callId) continue;
|
|
268
|
+
let moduleId = getPathIndex(path, callPathLength - 2);
|
|
269
|
+
if (!moduleId) continue;
|
|
270
|
+
|
|
271
|
+
callsToCall.delete(callId);
|
|
272
|
+
|
|
273
|
+
let moduleData = data()[moduleId];
|
|
274
|
+
|
|
275
|
+
let result = atomicObjectRead(moduleData.Results[callId]);
|
|
276
|
+
if (result) continue;
|
|
277
|
+
|
|
278
|
+
let callData = atomicObjectRead(moduleData.Calls[callId]);
|
|
279
|
+
if (!callData) continue;
|
|
280
|
+
|
|
281
|
+
if (callData.DomainName !== domainName) continue;
|
|
282
|
+
if (callData.ModuleId !== moduleId) continue;
|
|
283
|
+
if (callData.CallId !== callId) continue;
|
|
284
|
+
|
|
285
|
+
let functionSpec = atomicObjectRead(moduleData.Sources[callData.FunctionId]);
|
|
286
|
+
if (!functionSpec) {
|
|
287
|
+
if (isSynced(moduleData.Sources[callData.FunctionId])) {
|
|
288
|
+
console.warn(yellow(`Cannot call ${callData.DomainName}.${callData.ModuleId}.Sources.${callData.FunctionId} because it is not deployed`));
|
|
289
|
+
}
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// If we haven't synced the result, we can't know if it is ready or not
|
|
294
|
+
// (we'll run anyways once it is synced). Most other parts are immutable,
|
|
295
|
+
// but the result is by definition not, so... it is one of the values we check!
|
|
296
|
+
if (!isSynced(moduleData.Results[callId])) continue;
|
|
297
|
+
|
|
298
|
+
if (runningCalls.has(callId)) continue;
|
|
299
|
+
runningCalls.add(callId);
|
|
300
|
+
|
|
301
|
+
let limitCount = queueLimitCounts.get(callId) || 0;
|
|
302
|
+
limitCount++;
|
|
303
|
+
queueLimitCounts.set(callId, limitCount);
|
|
304
|
+
// NOTE: Calls are event writes, so... they should just clean themselves up, after we ignore them for long enough.
|
|
305
|
+
if (limitCount >= MAX_QUEUE_COUNT) {
|
|
306
|
+
// Only error the first time, as we don't need need that many errors
|
|
307
|
+
if (limitCount === MAX_QUEUE_COUNT) {
|
|
308
|
+
console.error(`Queue limit reached (${limitCount}) for ${getDebugName(callData, functionSpec, true)}`);
|
|
309
|
+
}
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (PathFunctionRunner.DEBUG_CALLS) {
|
|
314
|
+
console.log(`QUEUING ${getDebugName(callData, functionSpec, true)}`);
|
|
315
|
+
let resultsPath = getProxyPath(() => moduleData.Results[callId]);
|
|
316
|
+
let history = authorityStorage.getValuePlusHistory(resultsPath);
|
|
317
|
+
console.log(` History: ${history.length}`);
|
|
318
|
+
for (let { valid, time, canGCValue } of history) {
|
|
319
|
+
console.log(` ${valid ? "✅" : "❌"} ${debugTime(time)} ${canGCValue ? "value is undefined" : ""}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
newCalls.push({ call: callData, functionSpec });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Better run first calls first, or else we will just pointlessly cause rejections.
|
|
328
|
+
newCalls.sort((a, b) => compareTime(a.call.runAtTime, b.call.runAtTime));
|
|
329
|
+
for (let { call, functionSpec } of newCalls) {
|
|
330
|
+
// We don't need wait for all pending synced accesses to finish.
|
|
331
|
+
// Worst case we have an old functionSpec, and then runCall has to loop again.
|
|
332
|
+
// Otherwise the call request is (or should be) atomic, so it this should just work...
|
|
333
|
+
// - Or if it is rejected, we will detect it has no result, and run it again.
|
|
334
|
+
// NOTE: We run all calls in parallel, so we can load data in parallel. This is a bit dangerous, and might
|
|
335
|
+
// result in out of order execution, but... we commit at runTimes, so, this should eventually
|
|
336
|
+
// resolve itself due to rejections (which will change the call result, which SHOULD cause us to rerun again).
|
|
337
|
+
// - This is a bit dangerous, as we might overwhelm ourself. We should address this one we support
|
|
338
|
+
// FunctionRunner sharding.
|
|
339
|
+
outstandingCalls++;
|
|
340
|
+
let runCallPromise = self.runCall(call, functionSpec);
|
|
341
|
+
void runCallPromise.finally(() => {
|
|
342
|
+
outstandingCalls--;
|
|
343
|
+
runningCalls.delete(call.CallId);
|
|
344
|
+
});
|
|
345
|
+
logErrors(runCallPromise);
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
registerDynamicResource("paths|PathFunctionRunner.watches", () => watcher.lastWatches.paths.size);
|
|
350
|
+
registerDynamicResource("paths|PathFunctionRunner.outstandingCalls", () => outstandingCalls);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@measureFnc
|
|
355
|
+
private async getExportsFromSpec(spec: FunctionSpec): Promise<unknown> {
|
|
356
|
+
let module = await getModuleFromSpec(spec);
|
|
357
|
+
return module.exports;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private getSecondaryShardDelay(callPath: CallSpec): number {
|
|
361
|
+
let { shardRange, secondaryShardRange } = this.config;
|
|
362
|
+
if (!secondaryShardRange) return 0;
|
|
363
|
+
let fraction = __getRoutingHash(callPath.CallId);
|
|
364
|
+
// It isn't secondary if it is primary
|
|
365
|
+
if (shardRange.startFraction <= fraction && fraction < shardRange.endFraction) return 0;
|
|
366
|
+
if (!(secondaryShardRange.startFraction <= fraction && fraction < secondaryShardRange.endFraction)) return 0;
|
|
367
|
+
let fractionDistanceToPrimary = Math.min(
|
|
368
|
+
Math.abs(fraction - shardRange.startFraction),
|
|
369
|
+
Math.abs(fraction - shardRange.endFraction),
|
|
370
|
+
);
|
|
371
|
+
return Math.min(
|
|
372
|
+
// No more than 50% of accepted change, otherwise it might not be accepted
|
|
373
|
+
MAX_ACCEPTED_CHANGE_AGE * 0.5,
|
|
374
|
+
// Default values in case MAX_ACCEPTED_CHANGE_AGE is VERY high (minutes, or even hours)
|
|
375
|
+
30_000 + 10_000 * fractionDistanceToPrimary,
|
|
376
|
+
// Wait 5000 seconds, then start
|
|
377
|
+
5000 + MAX_ACCEPTED_CHANGE_AGE * fractionDistanceToPrimary * 0.05 / (shardRange.endFraction - shardRange.startFraction),
|
|
378
|
+
);
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
private callsRemainingInTick = cache((wait: true) => {
|
|
382
|
+
// NOTE: This gives us 100K calls per second, which is more than we can run anyways.
|
|
383
|
+
// We just need to wait a bit, otherwise the IO loop can never run, and so our finished calls
|
|
384
|
+
// can never actually finish, until eventually they are too late to finish and get rejected!
|
|
385
|
+
let tickDone = delay(10);
|
|
386
|
+
void tickDone.finally(() => {
|
|
387
|
+
this.callsRemainingInTick.clear(true);
|
|
388
|
+
});
|
|
389
|
+
return { calls: 1000, tickDone };
|
|
390
|
+
});
|
|
391
|
+
private async letIOClear(): Promise<void> {
|
|
392
|
+
while (true) {
|
|
393
|
+
let callsObj = this.callsRemainingInTick(true);
|
|
394
|
+
callsObj.calls--;
|
|
395
|
+
if (callsObj.calls >= 0) return;
|
|
396
|
+
await callsObj.tickDone;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private async runCall(callPath: CallSpec, functionSpec: FunctionSpec): Promise<void> {
|
|
401
|
+
const PermissionsChecker = this.config.PermissionsChecker;
|
|
402
|
+
let skipPermissions = <T>(code: () => T) => code();
|
|
403
|
+
if (PermissionsChecker) {
|
|
404
|
+
skipPermissions = PermissionsChecker.skipPermissionsChecks.bind(PermissionsChecker);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
await this.letIOClear();
|
|
408
|
+
|
|
409
|
+
let secondaryDelay = this.getSecondaryShardDelay(callPath);
|
|
410
|
+
if (secondaryDelay) {
|
|
411
|
+
async function getIsFinished() {
|
|
412
|
+
return await proxyWatcher.commitFunction({
|
|
413
|
+
watchFunction() {
|
|
414
|
+
let syncedModule = skipPermissions(() =>
|
|
415
|
+
functionSchema()[callPath.DomainName].PathFunctionRunner[callPath.ModuleId]
|
|
416
|
+
);
|
|
417
|
+
return !!atomicObjectRead(syncedModule.Results[callPath.CallId]);
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
// Call once, so we are already watching the
|
|
422
|
+
await delay(secondaryDelay);
|
|
423
|
+
let isFinished = await getIsFinished();
|
|
424
|
+
if (isFinished) return;
|
|
425
|
+
if (PathFunctionRunner.DEBUG_CALLS) {
|
|
426
|
+
let fraction = __getRoutingHash(callPath.CallId);
|
|
427
|
+
console.log(`${yellow("Function run fallback")} (primary servery failed to run function) after ${formatTime(secondaryDelay)}. Fraction ${fraction.toFixed(3)}. Primary ${this.config.shardRange.startFraction.toFixed(3)} to ${this.config.shardRange.endFraction.toFixed(3)}. Secondary ${this.config.secondaryShardRange?.startFraction.toFixed(3)} to ${this.config.secondaryShardRange?.endFraction.toFixed(3)}. for ${getDebugName(callPath, functionSpec, true)}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
PathFunctionRunner.RUN_START_COUNT++;
|
|
432
|
+
if (PathFunctionRunner.DEBUG_CALLS) {
|
|
433
|
+
console.log(`STARTING ${getDebugName(callPath, functionSpec, true)}`);
|
|
434
|
+
}
|
|
435
|
+
let startTime = Date.now();
|
|
436
|
+
let runCount = 0;
|
|
437
|
+
|
|
438
|
+
let finalWrites: PathValue[] | undefined;
|
|
439
|
+
|
|
440
|
+
let evalTime = 0;
|
|
441
|
+
let retries = 0;
|
|
442
|
+
|
|
443
|
+
let nooped = false;
|
|
444
|
+
|
|
445
|
+
while (true) {
|
|
446
|
+
let isFunctionSpecOutdated = false;
|
|
447
|
+
try {
|
|
448
|
+
|
|
449
|
+
let moduleExports = await this.getExportsFromSpec(functionSpec);
|
|
450
|
+
|
|
451
|
+
// ALWAYS get the permissions as well. This isn't perfect, as we could very well access data in other files, but... it helps
|
|
452
|
+
// reduce extra iterations inside of commitFunction...
|
|
453
|
+
await getModuleFromSpec({ ...functionSpec, exportPathStr: getExportPath(PERMISSIONS_FUNCTION_ID), FunctionId: PERMISSIONS_FUNCTION_ID, });
|
|
454
|
+
|
|
455
|
+
let exportPath = getPathFromStr(functionSpec.exportPathStr);
|
|
456
|
+
let exportObj = moduleExports as any;
|
|
457
|
+
for (let path of exportPath) {
|
|
458
|
+
exportObj = exportObj[path];
|
|
459
|
+
}
|
|
460
|
+
let baseFunction = exportObj as Function;
|
|
461
|
+
|
|
462
|
+
if (typeof baseFunction !== "function") {
|
|
463
|
+
throw new Error(`Export at ${JSON.stringify(exportPath.join("."))} was not a function (it was ${typeof baseFunction})`);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
await proxyWatcher.commitFunction({
|
|
467
|
+
canWrite: true,
|
|
468
|
+
debugName: getDebugName(callPath, functionSpec),
|
|
469
|
+
runAtTime: callPath.runAtTime,
|
|
470
|
+
getPermissionsCheck: PermissionsChecker && (() => new PermissionsChecker(callPath)),
|
|
471
|
+
nestedCalls: "inline",
|
|
472
|
+
watchFunction: function runCallWatcher() {
|
|
473
|
+
runCount++;
|
|
474
|
+
if (PathFunctionRunner.DEBUG_CALLS) {
|
|
475
|
+
console.log(`Evaluating (try count ${runCount}) ${getDebugName(callPath, functionSpec, true)}`);
|
|
476
|
+
console.log(` RUNNING AT REAL TIME ${debugTime(proxyWatcher.getTriggeredWatcher().currentReadTime || callPath.runAtTime)}`);
|
|
477
|
+
}
|
|
478
|
+
if (PathFunctionRunner.DEBUG_CALL_TRIGGERS && runCount > PathFunctionRunner.DEBUG_WATCHES_THRESHOLD) {
|
|
479
|
+
// NOTE: If this happens a few times during initial loading it is fine. If it happens a lot... and with values MUCH
|
|
480
|
+
// greater than 2... it is likely a problem, and your functions might be doing cascading loading, which can take
|
|
481
|
+
// a very long time (ex, `for(let i = 0; i < 1000; i++) if (!arr[i]) return`)
|
|
482
|
+
console.log(`> 2 call evaluations (try count ${runCount}) ${getDebugName(callPath, functionSpec, true)}`);
|
|
483
|
+
console.log(yellow(` Paths changed`));
|
|
484
|
+
for (let path of proxyWatcher.getTriggeredWatcher().triggeredByChanges?.paths || []) {
|
|
485
|
+
console.log(yellow(` ${path}`));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (runCount > PathFunctionRunner.MAX_WATCH_LOOPS) {
|
|
489
|
+
throw new Error(`MAX_WATCH_LOOPS exceeded for ${getDebugName(callPath, functionSpec, true)}`);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// We NEED to depend on the function spec, so spec updates can be atomic (which is needed to deploy an update
|
|
493
|
+
// to many nodes at once, without having any period where multiple versions both commit values).
|
|
494
|
+
let syncedModule = skipPermissions(() =>
|
|
495
|
+
functionSchema()[callPath.DomainName].PathFunctionRunner[callPath.ModuleId]
|
|
496
|
+
);
|
|
497
|
+
let syncedSpec = skipPermissions(() =>
|
|
498
|
+
atomicObjectRead(syncedModule.Sources[callPath.FunctionId])
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
// (We also need to depend on the RIGHT function spec).
|
|
502
|
+
if (
|
|
503
|
+
syncedSpec && (
|
|
504
|
+
syncedSpec.DomainName !== functionSpec.DomainName
|
|
505
|
+
|| syncedSpec.ModuleId !== functionSpec.ModuleId
|
|
506
|
+
|| syncedSpec.FilePath !== functionSpec.FilePath
|
|
507
|
+
|| syncedSpec.FunctionId !== functionSpec.FunctionId
|
|
508
|
+
|| syncedSpec.exportPathStr !== functionSpec.exportPathStr
|
|
509
|
+
|| syncedSpec.FilePath !== functionSpec.FilePath
|
|
510
|
+
|| syncedSpec.gitRef !== functionSpec.gitRef
|
|
511
|
+
|| syncedSpec.gitURL !== functionSpec.gitURL
|
|
512
|
+
)
|
|
513
|
+
) {
|
|
514
|
+
isFunctionSpecOutdated = true;
|
|
515
|
+
functionSpec = {
|
|
516
|
+
DomainName: syncedSpec.DomainName,
|
|
517
|
+
ModuleId: syncedSpec.ModuleId,
|
|
518
|
+
FilePath: syncedSpec.FilePath,
|
|
519
|
+
FunctionId: syncedSpec.FunctionId,
|
|
520
|
+
exportPathStr: syncedSpec.exportPathStr,
|
|
521
|
+
gitRef: syncedSpec.gitRef,
|
|
522
|
+
gitURL: syncedSpec.gitURL,
|
|
523
|
+
};
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
let evalTimeStart = Date.now();
|
|
528
|
+
let args = parseArgs(callPath);
|
|
529
|
+
overrideCurrentCall(callPath, () => {
|
|
530
|
+
baseFunction(...args);
|
|
531
|
+
});
|
|
532
|
+
evalTime += Date.now() - evalTimeStart;
|
|
533
|
+
|
|
534
|
+
// NOTE: The results are only temporarily stored, but serve as a way to let clients
|
|
535
|
+
// know figure out the dependencies for the call, which makes function call prediction
|
|
536
|
+
// possible.
|
|
537
|
+
skipPermissions(() => {
|
|
538
|
+
let result = doProxyOptions({ forceReadLatest: true }, () => atomicObjectRead(syncedModule.Results[callPath.CallId]));
|
|
539
|
+
if (result) {
|
|
540
|
+
nooped = true;
|
|
541
|
+
// It is important to NOOP if it has already been written. Otherwise there could be multiple
|
|
542
|
+
// runs of the same function, which could easily vary (if they use Math.random(), etc).
|
|
543
|
+
// WHICH, as the write time is predetermined, would cause ValuePaths with equal times, but
|
|
544
|
+
// different values (which breaks a lot of assumptions much of the code depends on! such
|
|
545
|
+
// as locks, etc).
|
|
546
|
+
if (!secondaryDelay && Querysub.isAllSynced()) {
|
|
547
|
+
console.warn(`Skipping function write, as it has already been written to by another FunctionRunner.`);
|
|
548
|
+
}
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
nooped = false;
|
|
552
|
+
syncedModule.Results[callPath.CallId] = atomicObjectWrite({
|
|
553
|
+
runCount: runCount,
|
|
554
|
+
timeTaken: Date.now() - evalTimeStart,
|
|
555
|
+
evalTime,
|
|
556
|
+
});
|
|
557
|
+
proxyWatcher.setEventPath(() => syncedModule.Results[callPath.CallId]);
|
|
558
|
+
});
|
|
559
|
+
},
|
|
560
|
+
}, {
|
|
561
|
+
onWritesCommitted(writes) {
|
|
562
|
+
finalWrites = writes;
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
} catch (e: any) {
|
|
566
|
+
if (isFunctionSpecOutdated) continue;
|
|
567
|
+
|
|
568
|
+
// NOTE: We are changing our paradigm to reject early, rather than retry. This is more stable. You can't really commit
|
|
569
|
+
// important values and then just leave, unless those values have no locks. This helps prevent values from being
|
|
570
|
+
// in different states on different machines, by just rejecting the call.
|
|
571
|
+
// if (e.message.includes("MAX_CHANGE_AGE_EXCEEDED") && retries < 10 && evalTime < MAX_ACCEPTED_CHANGE_AGE) {
|
|
572
|
+
// // NOTE: This should never really happen. It is only really if our PathValueServer goes down for a minute or so,
|
|
573
|
+
// // so we don't just drop all of our function calls.
|
|
574
|
+
// // - This is why we log every time, because no user actions should be able to result in this happening frequently.
|
|
575
|
+
// console.error(red(`RETRYING due to MAX_CHANGE_AGE_EXCEEDED. Hopefully the server will be more responsive this time (retry ${retries}): ${getDebugName(callPath, functionSpec, true)}`));
|
|
576
|
+
// // NOTE: Updating the time is fine, and won't break prediction rejections, as we reject based on path, not time.
|
|
577
|
+
// // (It might result in the prediction being wrong, but it is better than the user action being completely removed).
|
|
578
|
+
// callPath = { ...callPath, runAtTime: getNextTime() };
|
|
579
|
+
// retries++;
|
|
580
|
+
// continue;
|
|
581
|
+
// }
|
|
582
|
+
//if (PathFunctionRunner.DEBUG_CALLS)
|
|
583
|
+
{
|
|
584
|
+
console.log(`ERROR ${getDebugName(callPath, functionSpec, true)}`, e.stack);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// NOTE: commitFunction should never throw? If the server is dead, it still won't throw, so... we aren't try/catching it,
|
|
588
|
+
// as I don't see how retrying would help (the same error would almost certainly happen again and again).
|
|
589
|
+
await proxyWatcher.commitFunction({
|
|
590
|
+
canWrite: true,
|
|
591
|
+
// NOTE: We don't set runAtTime, because the rejection might be due to the call being too old
|
|
592
|
+
debugName: `error (${getDebugName(callPath, functionSpec)})`,
|
|
593
|
+
eventWrite: true,
|
|
594
|
+
doNotStoreWritesAsPredictions: true,
|
|
595
|
+
watchFunction() {
|
|
596
|
+
let syncedModule = functionSchema()[callPath.DomainName].PathFunctionRunner[callPath.ModuleId];
|
|
597
|
+
syncedModule.Results[callPath.CallId] = atomicObjectWrite({
|
|
598
|
+
error: e.stack || "",
|
|
599
|
+
runCount: runCount,
|
|
600
|
+
timeTaken: Date.now() - startTime,
|
|
601
|
+
evalTime,
|
|
602
|
+
});
|
|
603
|
+
},
|
|
604
|
+
}, {
|
|
605
|
+
onWritesCommitted(writes) {
|
|
606
|
+
finalWrites = writes;
|
|
607
|
+
},
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
if (isFunctionSpecOutdated) continue;
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (PathFunctionRunner.DEBUG_CALLS) {
|
|
615
|
+
console.log(`FINISHED${nooped ? " (skipped)" : ""} ${getDebugName(callPath, functionSpec, true)}, writes: ${finalWrites?.length}`);
|
|
616
|
+
}
|
|
617
|
+
PathFunctionRunner.RUN_FINISH_COUNT++;
|
|
618
|
+
}
|
|
619
|
+
}
|