querysub 0.403.0 → 0.405.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/.cursorrules +2 -0
- package/bin/audit-imports.js +4 -0
- package/bin/join.js +1 -1
- package/package.json +7 -4
- package/spec.txt +77 -0
- package/src/-a-archives/archiveCache.ts +9 -4
- package/src/-a-archives/archivesBackBlaze.ts +1039 -1039
- package/src/-a-auth/certs.ts +0 -12
- package/src/-c-identity/IdentityController.ts +12 -3
- package/src/-f-node-discovery/NodeDiscovery.ts +32 -26
- package/src/-g-core-values/NodeCapabilities.ts +12 -2
- package/src/0-path-value-core/AuthorityLookup.ts +239 -0
- package/src/0-path-value-core/LockWatcher2.ts +150 -0
- package/src/0-path-value-core/PathRouter.ts +543 -0
- package/src/0-path-value-core/PathRouterRouteOverride.ts +72 -0
- package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +73 -0
- package/src/0-path-value-core/PathValueCommitter.ts +222 -488
- package/src/0-path-value-core/PathValueController.ts +277 -239
- package/src/0-path-value-core/PathWatcher.ts +534 -0
- package/src/0-path-value-core/ShardPrefixes.ts +31 -0
- package/src/0-path-value-core/ValidStateComputer.ts +303 -0
- package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +1 -1
- package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +80 -44
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +13 -16
- package/src/0-path-value-core/auditLogs.ts +2 -0
- package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +97 -0
- package/src/0-path-value-core/pathValueArchives.ts +491 -492
- package/src/0-path-value-core/pathValueCore.ts +195 -1496
- package/src/0-path-value-core/startupAuthority.ts +74 -0
- package/src/1-path-client/RemoteWatcher.ts +90 -82
- package/src/1-path-client/pathValueClientWatcher.ts +808 -815
- package/src/2-proxy/PathValueProxyWatcher.ts +10 -8
- package/src/2-proxy/archiveMoveHarness.ts +182 -214
- package/src/2-proxy/garbageCollection.ts +9 -8
- package/src/2-proxy/schema2.ts +21 -1
- package/src/3-path-functions/PathFunctionHelpers.ts +206 -180
- package/src/3-path-functions/PathFunctionRunner.ts +943 -766
- package/src/3-path-functions/PathFunctionRunnerMain.ts +5 -3
- package/src/3-path-functions/pathFunctionLoader.ts +2 -2
- package/src/3-path-functions/syncSchema.ts +596 -521
- package/src/4-deploy/deployFunctions.ts +19 -4
- package/src/4-deploy/deployGetFunctionsInner.ts +8 -2
- package/src/4-deploy/deployMain.ts +51 -68
- package/src/4-deploy/edgeClientWatcher.tsx +6 -1
- package/src/4-deploy/edgeNodes.ts +2 -2
- package/src/4-dom/qreact.tsx +2 -4
- package/src/4-dom/qreactTest.tsx +7 -13
- package/src/4-querysub/Querysub.ts +21 -8
- package/src/4-querysub/QuerysubController.ts +45 -29
- package/src/4-querysub/permissions.ts +2 -2
- package/src/4-querysub/querysubPrediction.ts +80 -70
- package/src/4-querysub/schemaHelpers.ts +5 -1
- package/src/5-diagnostics/GenericFormat.tsx +14 -9
- package/src/archiveapps/archiveGCEntry.tsx +9 -2
- package/src/archiveapps/archiveJoinEntry.ts +96 -84
- package/src/bits.ts +19 -0
- package/src/config.ts +21 -3
- package/src/config2.ts +23 -48
- package/src/deployManager/components/DeployPage.tsx +7 -3
- package/src/deployManager/machineSchema.ts +4 -1
- package/src/diagnostics/ActionsHistory.ts +3 -8
- package/src/diagnostics/AuditLogPage.tsx +2 -3
- package/src/diagnostics/FunctionCallInfo.tsx +141 -0
- package/src/diagnostics/FunctionCallInfoState.ts +162 -0
- package/src/diagnostics/MachineThreadInfo.tsx +1 -1
- package/src/diagnostics/NodeViewer.tsx +37 -48
- package/src/diagnostics/SyncTestPage.tsx +241 -0
- package/src/diagnostics/auditImportViolations.ts +185 -0
- package/src/diagnostics/listenOnDebugger.ts +3 -3
- package/src/diagnostics/logs/IndexedLogs/BufferUnitSet.ts +10 -4
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +2 -2
- package/src/diagnostics/logs/IndexedLogs/LogViewer3.tsx +24 -22
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +1 -1
- package/src/diagnostics/logs/diskLogGlobalContext.ts +1 -0
- package/src/diagnostics/logs/errorNotifications2/logWatcher.ts +1 -3
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryEditor.tsx +34 -16
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleEntryReadMode.tsx +4 -6
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleInstanceTableView.tsx +36 -5
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCyclePage.tsx +19 -5
- package/src/diagnostics/logs/lifeCycleAnalysis/LifeCycleRenderer.tsx +15 -7
- package/src/diagnostics/logs/lifeCycleAnalysis/NestedLifeCycleInfo.tsx +28 -106
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleMatching.ts +2 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleMisc.ts +0 -0
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycleSearch.tsx +18 -7
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +3 -0
- package/src/diagnostics/managementPages.tsx +10 -3
- package/src/diagnostics/misc-pages/ArchiveViewer.tsx +20 -26
- package/src/diagnostics/misc-pages/ArchiveViewerTree.tsx +6 -4
- package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +2 -2
- package/src/diagnostics/misc-pages/LocalWatchViewer.tsx +7 -9
- package/src/diagnostics/misc-pages/SnapshotViewer.tsx +23 -12
- package/src/diagnostics/misc-pages/archiveViewerShared.tsx +1 -1
- package/src/diagnostics/pathAuditer.ts +486 -0
- package/src/diagnostics/pathAuditerCallback.ts +20 -0
- package/src/diagnostics/watchdog.ts +8 -1
- package/src/library-components/URLParam.ts +1 -1
- package/src/misc/hash.ts +1 -0
- package/src/path.ts +21 -7
- package/src/server.ts +54 -47
- package/src/user-implementation/loginEmail.tsx +1 -1
- package/tempnotes.txt +65 -0
- package/test.ts +298 -97
- package/src/0-path-value-core/NodePathAuthorities.ts +0 -1057
- package/src/0-path-value-core/PathController.ts +0 -1
- package/src/5-diagnostics/diskValueAudit.ts +0 -218
- package/src/5-diagnostics/memoryValueAudit.ts +0 -438
- package/src/archiveapps/archiveMergeEntry.tsx +0 -48
- package/src/archiveapps/lockTest.ts +0 -127
|
@@ -1,766 +1,943 @@
|
|
|
1
|
-
import { delay, runInfinitePoll } from "socket-function/src/batching";
|
|
2
|
-
import { cache } from "socket-function/src/caching";
|
|
3
|
-
import { blue, magenta,
|
|
4
|
-
import {
|
|
5
|
-
import { measureFnc } from "socket-function/src/profiling/measure";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
export function
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
let
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
//
|
|
317
|
-
if (
|
|
318
|
-
|
|
319
|
-
if (
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
let
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
)
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
private async
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
let
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
let
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
);
|
|
1
|
+
import { delay, runInfinitePoll } from "socket-function/src/batching";
|
|
2
|
+
import { cache } from "socket-function/src/caching";
|
|
3
|
+
import { blue, magenta, yellow } from "socket-function/src/formatting/logColors";
|
|
4
|
+
import { timeInHour, timeInSecond } from "socket-function/src/misc";
|
|
5
|
+
import { measureFnc } from "socket-function/src/profiling/measure";
|
|
6
|
+
import { registerDynamicResource } from "../diagnostics/trackResources";
|
|
7
|
+
import { errorToUndefined, logErrors } from "../errors";
|
|
8
|
+
import { getPathFromStr, getPathIndex } from "../path";
|
|
9
|
+
import { rawSchema } from "../2-proxy/pathDatabaseProxyBase";
|
|
10
|
+
import { getProxyPath } from "../2-proxy/pathValueProxy";
|
|
11
|
+
import { atomicObjectRead, atomicObjectWrite, doProxyOptions, isProxyBlockedByOrder, isSynced, proxyWatcher } from "../2-proxy/PathValueProxyWatcher";
|
|
12
|
+
import { authorityStorage, compareTime, debugTime, MAX_ACCEPTED_CHANGE_AGE, PathValue, Time } from "../0-path-value-core/pathValueCore";
|
|
13
|
+
import { getModuleFromSpec } from "./pathFunctionLoader";
|
|
14
|
+
import debugbreak from "debugbreak";
|
|
15
|
+
import { parseArgs } from "./PathFunctionHelpers";
|
|
16
|
+
import { FunctionMetadata, PERMISSIONS_FUNCTION_ID, addRoutingPrefixForDeploy, getAllDevelopmentModulesIds, getDevelopmentModule, getExportPath, getModuleRelativePath, getSchemaObject } from "./syncSchema";
|
|
17
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
18
|
+
import { getControllerNodeIdList, set_debug_getFunctionRunnerShards } from "../-g-core-values/NodeCapabilities";
|
|
19
|
+
import { FilterSelector, Filterable, doesMatch } from "../misc/filterable";
|
|
20
|
+
import { SocketFunction } from "socket-function/SocketFunction";
|
|
21
|
+
import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
|
|
22
|
+
import { getDomain, isPublic } from "../config";
|
|
23
|
+
import { getGitRefSync, getGitURLSync } from "../4-deploy/git";
|
|
24
|
+
import type { DeployProgress } from "../4-deploy/deployFunctions";
|
|
25
|
+
import { getRoutingOverride, getRoutingOverridePart } from "../0-path-value-core/PathRouterRouteOverride";
|
|
26
|
+
setImmediate(() => import("../4-querysub/Querysub"));
|
|
27
|
+
|
|
28
|
+
export const functionSchema = rawSchema<{
|
|
29
|
+
[domainName: string]: {
|
|
30
|
+
PathFunctionRunner: {
|
|
31
|
+
[ModuleId: string]: {
|
|
32
|
+
Data: unknown;
|
|
33
|
+
Sources: { [FunctionId: string]: FunctionSpec | undefined; };
|
|
34
|
+
Calls: { [CallId: string]: CallSpec | undefined; };
|
|
35
|
+
Results: { [CallId: string]: FunctionResult | undefined; };
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
}>();
|
|
40
|
+
void Promise.resolve().then(() => {
|
|
41
|
+
for (let moduleId of getAllDevelopmentModulesIds()) {
|
|
42
|
+
addRoutingPrefixForDeploy(getProxyPath(() => functionSchema()[getDomain()].PathFunctionRunner[moduleId].Calls));
|
|
43
|
+
addRoutingPrefixForDeploy(getProxyPath(() => functionSchema()[getDomain()].PathFunctionRunner[moduleId].Results));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export function commitCall(call: CallSpec) {
|
|
48
|
+
let domainName = call.DomainName;
|
|
49
|
+
let moduleId = call.ModuleId;
|
|
50
|
+
let callId = call.CallId;
|
|
51
|
+
proxyWatcher.writeOnly({
|
|
52
|
+
canWrite: true,
|
|
53
|
+
eventWrite: true,
|
|
54
|
+
doNotStoreWritesAsPredictions: true,
|
|
55
|
+
// NOTE: We write the call in the present, it is callSpec.runAtTime that is in the past. No one cares
|
|
56
|
+
// when the call was added, only when it wants to run at.
|
|
57
|
+
watchFunction: function writeCall() {
|
|
58
|
+
functionSchema()[domainName].PathFunctionRunner[moduleId].Calls[callId] = atomicObjectWrite(call);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const DEPTH_TO_DATA = 4;
|
|
64
|
+
export const DOMAIN_INDEX = 0;
|
|
65
|
+
export const MODULE_INDEX = 2;
|
|
66
|
+
|
|
67
|
+
export function getSchemaPartsFromPath(path: string): { domainName: string; moduleId: string } | undefined {
|
|
68
|
+
let parts = getPathFromStr(path);
|
|
69
|
+
let domainName = parts[0];
|
|
70
|
+
let moduleId = parts[2];
|
|
71
|
+
if (!domainName || !moduleId) return undefined;
|
|
72
|
+
return { domainName, moduleId };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type FunctionSpec = {
|
|
76
|
+
DomainName: string;
|
|
77
|
+
ModuleId: string;
|
|
78
|
+
FunctionId: string;
|
|
79
|
+
exportPathStr: string;
|
|
80
|
+
FilePath: string;
|
|
81
|
+
gitURL: string;
|
|
82
|
+
/** Ex, the hash */
|
|
83
|
+
gitRef: string;
|
|
84
|
+
|
|
85
|
+
maxLocksOverride?: number;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export interface CallSpec {
|
|
89
|
+
DomainName: string;
|
|
90
|
+
ModuleId: string;
|
|
91
|
+
CallId: string;
|
|
92
|
+
FunctionId: string;
|
|
93
|
+
// NOTE: This is no longer JSON. Use parseArgs to decode it
|
|
94
|
+
argsEncoded: string;
|
|
95
|
+
callerMachineId: string;
|
|
96
|
+
callerIP: string;
|
|
97
|
+
runAtTime: Time;
|
|
98
|
+
|
|
99
|
+
// Not just used for debugging, also used to add special proxy-related warnings.
|
|
100
|
+
fromProxy?: string;
|
|
101
|
+
|
|
102
|
+
filterable?: Filterable;
|
|
103
|
+
}
|
|
104
|
+
export function debugCallSpec(spec: CallSpec): string {
|
|
105
|
+
return `${spec.DomainName}/${spec.ModuleId}/${spec.FunctionId}`;
|
|
106
|
+
}
|
|
107
|
+
export type FunctionResult = ({
|
|
108
|
+
|
|
109
|
+
} | {
|
|
110
|
+
error: string;
|
|
111
|
+
}) & {
|
|
112
|
+
timeTaken: number;
|
|
113
|
+
evalTime: number;
|
|
114
|
+
lastInternalLoopCount: number;
|
|
115
|
+
outerLoopCount: number;
|
|
116
|
+
totalInternalLoopCount: number;
|
|
117
|
+
totalTime: number;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
let currentCallSpec: { spec: CallSpec; fnc: FunctionSpec; } | undefined;
|
|
121
|
+
export function getCurrentCall() {
|
|
122
|
+
if (!currentCallSpec) {
|
|
123
|
+
debugbreak(2);
|
|
124
|
+
debugger;
|
|
125
|
+
throw new Error("Not presently in a call, and so cannot get call");
|
|
126
|
+
}
|
|
127
|
+
return currentCallSpec.spec;
|
|
128
|
+
}
|
|
129
|
+
export function getCurrentCallAllowUndefined() {
|
|
130
|
+
return currentCallSpec?.spec;
|
|
131
|
+
}
|
|
132
|
+
export function getCurrentCallObj() {
|
|
133
|
+
return currentCallSpec;
|
|
134
|
+
}
|
|
135
|
+
/** NOTE: We require the FunctionSpec so we know what has is loaded, to identify the
|
|
136
|
+
* correct schema. This should be available, but if it isn't... we MIGHT be able
|
|
137
|
+
* to work around it, if we can be assured the schema won't be used?
|
|
138
|
+
* - This is so pre-loading of functions (which might happen without a deploy, if the deploy script
|
|
139
|
+
* is killed part of the way through), doesn't change schemas until the deploy actually
|
|
140
|
+
* updates the underlying functions.
|
|
141
|
+
*/
|
|
142
|
+
export function overrideCurrentCall<T>(config: { spec: CallSpec; fnc: FunctionSpec; }, code: () => T) {
|
|
143
|
+
let prev = currentCallSpec;
|
|
144
|
+
currentCallSpec = config;
|
|
145
|
+
try {
|
|
146
|
+
return code();
|
|
147
|
+
} finally {
|
|
148
|
+
currentCallSpec = prev;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getDebugName(call: CallSpec, functionSpec: FunctionSpec | undefined, colored = false) {
|
|
153
|
+
if (!functionSpec) {
|
|
154
|
+
return `${call.DomainName}:${call.ModuleId}|${call.FunctionId}|${debugTime(call.runAtTime)}`;
|
|
155
|
+
}
|
|
156
|
+
let pathName = getPathFromStr(functionSpec.exportPathStr).slice(1).join(".");
|
|
157
|
+
let mainPart = `${call.DomainName}:${functionSpec.FilePath}|${pathName}|${debugTime(call.runAtTime)}`;
|
|
158
|
+
if (colored) {
|
|
159
|
+
return `${magenta(mainPart)}`;
|
|
160
|
+
} else {
|
|
161
|
+
return `${mainPart}`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
type PermissionsCheckType = {
|
|
166
|
+
new(callerMachineId: { callerMachineId: string; callerIP: string; }): {
|
|
167
|
+
checkPermissions(path: string): { permissionsPath: string; allowed: boolean; };
|
|
168
|
+
};
|
|
169
|
+
skipPermissionsChecks: <T>(code: () => T) => T;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export type DebugFunctionShardInfo = {
|
|
173
|
+
domainName: string;
|
|
174
|
+
shardRange: { startFraction: number, endFraction: number };
|
|
175
|
+
secondaryShardRange?: { startFraction: number, endFraction: number };
|
|
176
|
+
};
|
|
177
|
+
let debugFunctionRunnerShards: DebugFunctionShardInfo[] = [];
|
|
178
|
+
export function debug_getFunctionRunnerShards() {
|
|
179
|
+
return debugFunctionRunnerShards;
|
|
180
|
+
}
|
|
181
|
+
setImmediate(() => {
|
|
182
|
+
set_debug_getFunctionRunnerShards(debug_getFunctionRunnerShards);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
type CallStats = {
|
|
186
|
+
outerLoopCount: number;
|
|
187
|
+
totalInternalLoopCount: number;
|
|
188
|
+
lastInternalLoopCount: number;
|
|
189
|
+
isInsideRunCall: boolean;
|
|
190
|
+
timeTaken: number;
|
|
191
|
+
evalTime: number;
|
|
192
|
+
firstQueuedTime: number;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export class PathFunctionRunner {
|
|
196
|
+
public static RUN_START_COUNT = 0;
|
|
197
|
+
public static RUN_FINISH_COUNT = 0;
|
|
198
|
+
public static DEBUG_CALLS = false;
|
|
199
|
+
public static DEBUG_CALL_TRIGGERS = false;
|
|
200
|
+
public static DEBUG_WATCHES_THRESHOLD = 5;
|
|
201
|
+
public static MAX_WATCH_LOOPS = 1000;
|
|
202
|
+
|
|
203
|
+
constructor(private config: {
|
|
204
|
+
domainName: string;
|
|
205
|
+
shardRange: { startFraction: number, endFraction: number };
|
|
206
|
+
secondaryShardRange?: { startFraction: number, endFraction: number };
|
|
207
|
+
PermissionsChecker: PermissionsCheckType | undefined;
|
|
208
|
+
filterSelector?: FilterSelector;
|
|
209
|
+
}) {
|
|
210
|
+
SocketFunction.expose(FunctionPreloadController);
|
|
211
|
+
debugFunctionRunnerShards.push({
|
|
212
|
+
domainName: config.domainName,
|
|
213
|
+
shardRange: config.shardRange,
|
|
214
|
+
secondaryShardRange: config.secondaryShardRange,
|
|
215
|
+
});
|
|
216
|
+
logErrors(this.startWatching());
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Calls runCall on any calls that match our domain + shard range
|
|
220
|
+
private async startWatching() {
|
|
221
|
+
const { Querysub } = await import("../4-querysub/Querysub");
|
|
222
|
+
let { shardRange, secondaryShardRange } = this.config;
|
|
223
|
+
|
|
224
|
+
// We will use PathValueProxyWatcher to watch the paths
|
|
225
|
+
// Maintain only one outstanding runCall per callId
|
|
226
|
+
// - Once it finishes, we can trigger another call, if we notice the result is not set.
|
|
227
|
+
// - AS we should get write prediction for the result, we can rerun our proxy check
|
|
228
|
+
// loop immediately.
|
|
229
|
+
|
|
230
|
+
let fullFraction = shardRange;
|
|
231
|
+
if (secondaryShardRange) {
|
|
232
|
+
fullFraction = {
|
|
233
|
+
startFraction: Math.min(shardRange.startFraction, secondaryShardRange.startFraction),
|
|
234
|
+
endFraction: Math.max(shardRange.endFraction, secondaryShardRange.endFraction),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
let config = this.config;
|
|
239
|
+
|
|
240
|
+
let self = this;
|
|
241
|
+
|
|
242
|
+
let outstandingCalls = 0;
|
|
243
|
+
|
|
244
|
+
let watchModuleCalls = cache((moduleId: string) => {
|
|
245
|
+
// Object.keys(functionSchema()[domainName].PathFunctionRunner[moduleId].Calls);
|
|
246
|
+
// Object.keys(functionSchema()[domainName].PathFunctionRunner[moduleId].Results);
|
|
247
|
+
Querysub.keys(functionSchema()[domainName].PathFunctionRunner[moduleId].Calls, fullFraction);
|
|
248
|
+
Querysub.keys(functionSchema()[domainName].PathFunctionRunner[moduleId].Results, fullFraction);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
let runningCalls = new Set<string>();
|
|
252
|
+
|
|
253
|
+
// Rarely we might need to queue a function multiple times, when we are late to receive rejections.
|
|
254
|
+
// HOWEVER, after enough times, we should stop, as we will probably just infinitely queue it.
|
|
255
|
+
// For debugging, we should never really be having to re-queue it. And if we requeue it a lot, it makes the logs larger, which makes it harder to debug it!
|
|
256
|
+
const MAX_QUEUE_COUNT = isPublic() ? 100 : 14;
|
|
257
|
+
let queueLimitCounts = new Map<string, number>();
|
|
258
|
+
// Clear every hour, so we don't leave
|
|
259
|
+
runInfinitePoll(timeInHour, () => queueLimitCounts.clear());
|
|
260
|
+
|
|
261
|
+
const domainName = config.domainName;
|
|
262
|
+
const watcher = proxyWatcher.createWatcher({
|
|
263
|
+
trackTriggers: true,
|
|
264
|
+
skipPermissionsCheck: true,
|
|
265
|
+
hasSideEffects: true,
|
|
266
|
+
// Maybe allow running with unsynced reads? This might be good, because we don't need all reads
|
|
267
|
+
// to detect calls. BUT, it might also be bad, because it might cause us to rerun in a cascading
|
|
268
|
+
// fashion? (As in, be really slow).
|
|
269
|
+
// - Shouldn't be needed, because writes don't take that long
|
|
270
|
+
// - Could fix bugs where we never get a read, and therefore all function calls fails to run?
|
|
271
|
+
// allowUnsyncedReads: true
|
|
272
|
+
watchFunction: function findFunctionsToCall() {
|
|
273
|
+
// TODO: Maybe abstract the "preserve watchers" stuff,
|
|
274
|
+
// so that it is automatically done at the start of our trigger.
|
|
275
|
+
const watcher = proxyWatcher.getTriggeredWatcher();
|
|
276
|
+
|
|
277
|
+
// Preserve previous watches explicitly (so we just do a diff, instead of accessing every
|
|
278
|
+
// value every time).
|
|
279
|
+
proxyWatcher.reuseLastWatches();
|
|
280
|
+
|
|
281
|
+
let data = () => functionSchema()[domainName].PathFunctionRunner;
|
|
282
|
+
|
|
283
|
+
let moduleIds = Object.keys(data());
|
|
284
|
+
if (!isPublic()) {
|
|
285
|
+
moduleIds.push(...getAllDevelopmentModulesIds());
|
|
286
|
+
moduleIds = Array.from(new Set(moduleIds));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
for (let moduleId of moduleIds) {
|
|
290
|
+
watchModuleCalls(moduleId);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let newCalls: { call: CallSpec; functionSpec: FunctionSpec }[] = [];
|
|
294
|
+
|
|
295
|
+
if (watcher.triggeredByChanges) {
|
|
296
|
+
for (let path of watcher.triggeredByChanges.paths) {
|
|
297
|
+
let callsOrResult = getPathIndex(path, 3);
|
|
298
|
+
if (callsOrResult !== "Calls" && callsOrResult !== "Results") continue;
|
|
299
|
+
if (getPathIndex(path, 1) !== "PathFunctionRunner") continue;
|
|
300
|
+
if (getPathIndex(path, 0) !== domainName) continue;
|
|
301
|
+
|
|
302
|
+
const callId = getPathIndex(path, 4);
|
|
303
|
+
if (!callId) continue;
|
|
304
|
+
let moduleId = getPathIndex(path, 2);
|
|
305
|
+
if (!moduleId) continue;
|
|
306
|
+
if (!moduleIds.includes(moduleId)) continue;
|
|
307
|
+
|
|
308
|
+
let moduleData = data()[moduleId];
|
|
309
|
+
|
|
310
|
+
let result = atomicObjectRead(moduleData.Results[callId]);
|
|
311
|
+
if (result) continue;
|
|
312
|
+
|
|
313
|
+
let callData = atomicObjectRead(moduleData.Calls[callId]);
|
|
314
|
+
if (!callData) continue;
|
|
315
|
+
|
|
316
|
+
// Ignore invalid data
|
|
317
|
+
if (callData.DomainName !== domainName) continue;
|
|
318
|
+
if (callData.ModuleId !== moduleId) continue;
|
|
319
|
+
if (callData.CallId !== callId) continue;
|
|
320
|
+
|
|
321
|
+
const functionId = callData.FunctionId;
|
|
322
|
+
let functionSpec = atomicObjectRead(moduleData.Sources[functionId]);
|
|
323
|
+
if (!functionSpec) {
|
|
324
|
+
if (!isSynced(moduleData.Sources[callData.FunctionId])) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (!isPublic()) {
|
|
328
|
+
functionSpec = getDevFunctionSpecFromCall(callData);
|
|
329
|
+
if (!functionSpec) continue;
|
|
330
|
+
} else {
|
|
331
|
+
console.warn(yellow(`Cannot call ${callData.DomainName}.${callData.ModuleId}.Sources.${callData.FunctionId} because it is not deployed`));
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// If we haven't synced the result, we can't know if it is ready or not
|
|
337
|
+
// (we'll run anyways once it is synced). Most other parts are immutable,
|
|
338
|
+
// but the result is by definition not, so... it is one of the values we check!
|
|
339
|
+
if (!isSynced(moduleData.Results[callId])) continue;
|
|
340
|
+
|
|
341
|
+
if (runningCalls.has(callId)) continue;
|
|
342
|
+
runningCalls.add(callId);
|
|
343
|
+
|
|
344
|
+
let limitCount = queueLimitCounts.get(callId) || 0;
|
|
345
|
+
limitCount++;
|
|
346
|
+
queueLimitCounts.set(callId, limitCount);
|
|
347
|
+
// NOTE: Calls are event writes, so... they should just clean themselves up, after we ignore them for long enough.
|
|
348
|
+
if (limitCount >= MAX_QUEUE_COUNT) {
|
|
349
|
+
// Only error the first time, as we don't need need that many errors
|
|
350
|
+
if (limitCount === MAX_QUEUE_COUNT) {
|
|
351
|
+
console.error(`(Likely a missing synced value) Tried to requeue a function to run too many times (${limitCount}) for ${getDebugName(callData, functionSpec, true)}. This is NOT due to cascading reads. It might be due to repeated rejections?`, {
|
|
352
|
+
callId
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
let stats = self.callStats.get(callId);
|
|
356
|
+
void self.finishCallWithError({
|
|
357
|
+
callSpec: callData,
|
|
358
|
+
functionSpec,
|
|
359
|
+
error: `Tried to requeue a function to run too many times (${limitCount}) for ${getDebugName(callData, functionSpec, true)}. This is NOT due to cascading reads. It might be due to repeated rejections?`,
|
|
360
|
+
runCount: stats?.lastInternalLoopCount || 0,
|
|
361
|
+
startTime: Date.now(),
|
|
362
|
+
evalTime: 0,
|
|
363
|
+
outerLoopCount: stats?.outerLoopCount || 0,
|
|
364
|
+
totalInternalLoopCount: stats?.totalInternalLoopCount || 0,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (PathFunctionRunner.DEBUG_CALLS) {
|
|
371
|
+
console.log(`QUEUING ${getDebugName(callData, functionSpec, true)}`);
|
|
372
|
+
let resultsPath = getProxyPath(() => moduleData.Results[callId]);
|
|
373
|
+
let history = authorityStorage.getValuePlusHistory(resultsPath);
|
|
374
|
+
console.log(` History: ${history.length}`);
|
|
375
|
+
for (let { valid, time, canGCValue } of history) {
|
|
376
|
+
console.log(` ${valid ? "✅" : "❌"} ${debugTime(time)} ${canGCValue ? "value is undefined" : ""}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
newCalls.push({ call: callData, functionSpec });
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Better run first calls first, or else we will just pointlessly cause rejections.
|
|
384
|
+
newCalls.sort((a, b) => compareTime(a.call.runAtTime, b.call.runAtTime));
|
|
385
|
+
for (let { call, functionSpec } of newCalls) {
|
|
386
|
+
// We don't need wait for all pending synced accesses to finish.
|
|
387
|
+
// Worst case we have an old functionSpec, and then runCall has to loop again.
|
|
388
|
+
// Otherwise the call request is (or should be) atomic, so it this should just work...
|
|
389
|
+
// - Or if it is rejected, we will detect it has no result, and run it again.
|
|
390
|
+
// NOTE: We run all calls in parallel, so we can load data in parallel. This is a bit dangerous, and might
|
|
391
|
+
// result in out of order execution, but... we commit at runTimes, so, this should eventually
|
|
392
|
+
// resolve itself due to rejections (which will change the call result, which SHOULD cause us to rerun again).
|
|
393
|
+
// - This is a bit dangerous, as we might overwhelm ourself. We should address this one we support
|
|
394
|
+
// FunctionRunner sharding.
|
|
395
|
+
outstandingCalls++;
|
|
396
|
+
let runCallPromise = self.runCall(call, functionSpec);
|
|
397
|
+
void runCallPromise.finally(() => {
|
|
398
|
+
outstandingCalls--;
|
|
399
|
+
// IMPORTANT! We remove the call, NOT for GC, but! Because if the call writes are rejected (due to contention), then the Result will also be rejected, changing it back to undefined, so (as long as little enough time has passed), we will run the call again! Or rather, we will try to, as long as runningCalls doesn't have the value!
|
|
400
|
+
runningCalls.delete(call.CallId);
|
|
401
|
+
});
|
|
402
|
+
logErrors(runCallPromise);
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
registerDynamicResource("paths|PathFunctionRunner.watches", () => watcher.lastWatches.paths.size);
|
|
407
|
+
registerDynamicResource("paths|PathFunctionRunner.outstandingCalls", () => outstandingCalls);
|
|
408
|
+
|
|
409
|
+
// If local, periodically check all modules
|
|
410
|
+
// NOTE: `yarn deploy` commits the moduleIds to the database, but calls do not, as this would require either an extra
|
|
411
|
+
// read, or an extra write. So this is somewhat a hack, but also fairly safe.
|
|
412
|
+
if (!isPublic()) {
|
|
413
|
+
let prevModuleIds = new Set<string>();
|
|
414
|
+
runInfinitePoll(timeInSecond * 15, () => {
|
|
415
|
+
let moduleIds = getAllDevelopmentModulesIds();
|
|
416
|
+
let changed = false;
|
|
417
|
+
for (let moduleId of moduleIds) {
|
|
418
|
+
if (!prevModuleIds.has(moduleId)) {
|
|
419
|
+
changed = true;
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
prevModuleIds = new Set(moduleIds);
|
|
424
|
+
if (changed) {
|
|
425
|
+
watcher.explicitlyTrigger();
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
@measureFnc
|
|
433
|
+
private async getExportsFromSpec(spec: FunctionSpec): Promise<unknown> {
|
|
434
|
+
let module = await getModuleFromSpec(spec);
|
|
435
|
+
return module.exports;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
private getSecondaryShardDelay(callPath: CallSpec): number {
|
|
439
|
+
let { shardRange, secondaryShardRange } = this.config;
|
|
440
|
+
if (!secondaryShardRange) return 0;
|
|
441
|
+
let fraction = getRoutingOverridePart(callPath.CallId)?.route;
|
|
442
|
+
if (fraction === undefined) throw new Error(`No routing override found for callId, so we can't determine if we should handle it. CallId: ${callPath.CallId}`);
|
|
443
|
+
// It isn't secondary if it is primary
|
|
444
|
+
if (shardRange.startFraction <= fraction && fraction < shardRange.endFraction) return 0;
|
|
445
|
+
if (!(secondaryShardRange.startFraction <= fraction && fraction < secondaryShardRange.endFraction)) return 0;
|
|
446
|
+
let fractionDistanceToPrimary = Math.min(
|
|
447
|
+
Math.abs(fraction - shardRange.startFraction),
|
|
448
|
+
Math.abs(fraction - shardRange.endFraction),
|
|
449
|
+
);
|
|
450
|
+
return Math.min(
|
|
451
|
+
// No more than 50% of accepted change, otherwise it might not be accepted
|
|
452
|
+
MAX_ACCEPTED_CHANGE_AGE * 0.5,
|
|
453
|
+
// Default values in case MAX_ACCEPTED_CHANGE_AGE is VERY high (minutes, or even hours)
|
|
454
|
+
30_000 + 10_000 * fractionDistanceToPrimary,
|
|
455
|
+
// Wait 5000 seconds, then start
|
|
456
|
+
5000 + MAX_ACCEPTED_CHANGE_AGE * fractionDistanceToPrimary * 0.05 / (shardRange.endFraction - shardRange.startFraction),
|
|
457
|
+
);
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
private callsRemainingInTick = cache((wait: true) => {
|
|
461
|
+
// NOTE: This gives us 100K calls per second, which is more than we can run anyways.
|
|
462
|
+
// We just need to wait a bit, otherwise the IO loop can never run, and so our finished calls
|
|
463
|
+
// can never actually finish, until eventually they are too late to finish and get rejected!
|
|
464
|
+
let tickDone = delay(10);
|
|
465
|
+
void tickDone.finally(() => {
|
|
466
|
+
this.callsRemainingInTick.clear(true);
|
|
467
|
+
});
|
|
468
|
+
return { calls: 1000, tickDone };
|
|
469
|
+
});
|
|
470
|
+
private async letIOClear(): Promise<void> {
|
|
471
|
+
while (true) {
|
|
472
|
+
let callsObj = this.callsRemainingInTick(true);
|
|
473
|
+
callsObj.calls--;
|
|
474
|
+
if (callsObj.calls >= 0) return;
|
|
475
|
+
await callsObj.tickDone;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private callStats = new Map<string, CallStats>();
|
|
480
|
+
|
|
481
|
+
private async runCall(callSpec: CallSpec, functionSpec: FunctionSpec): Promise<void> {
|
|
482
|
+
let callId = callSpec.CallId;
|
|
483
|
+
let stats = this.callStats.get(callId);
|
|
484
|
+
if (!stats) {
|
|
485
|
+
// HACK: Just completely reset the map after it gets big enough. This prevents us from crashing due to the map getting too large, as there's a limit to how large maps can get.
|
|
486
|
+
if (this.callStats.size > 1000 * 1000) {
|
|
487
|
+
this.callStats.clear();
|
|
488
|
+
}
|
|
489
|
+
stats = {
|
|
490
|
+
outerLoopCount: 0,
|
|
491
|
+
totalInternalLoopCount: 0,
|
|
492
|
+
lastInternalLoopCount: 0,
|
|
493
|
+
isInsideRunCall: false,
|
|
494
|
+
timeTaken: 0,
|
|
495
|
+
evalTime: 0,
|
|
496
|
+
firstQueuedTime: Date.now(),
|
|
497
|
+
};
|
|
498
|
+
this.callStats.set(callId, stats);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (stats.isInsideRunCall) {
|
|
502
|
+
console.error(`Attempted to re-enter runCall for ${callId} while already inside runCall`);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
stats.isInsideRunCall = true;
|
|
507
|
+
stats.outerLoopCount++;
|
|
508
|
+
try {
|
|
509
|
+
await this.runCallBase(callSpec, functionSpec, stats);
|
|
510
|
+
} finally {
|
|
511
|
+
stats.isInsideRunCall = false;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private async runCallBase(callSpec: CallSpec, functionSpec: FunctionSpec, stats: CallStats): Promise<void> {
|
|
516
|
+
const { Querysub } = await import("../4-querysub/Querysub");
|
|
517
|
+
|
|
518
|
+
const PermissionsChecker = this.config.PermissionsChecker;
|
|
519
|
+
let skipPermissions = <T>(code: () => T) => code();
|
|
520
|
+
if (PermissionsChecker) {
|
|
521
|
+
skipPermissions = PermissionsChecker.skipPermissionsChecks.bind(PermissionsChecker);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (!doesMatch(callSpec.filterable, this.config.filterSelector)) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
console.info(`New function call: ${getDebugName(callSpec, functionSpec, true)}`, {
|
|
529
|
+
callId: callSpec.CallId,
|
|
530
|
+
timeId: callSpec.runAtTime.time,
|
|
531
|
+
functionId: callSpec.FunctionId,
|
|
532
|
+
moduleId: callSpec.ModuleId,
|
|
533
|
+
gitRef: functionSpec.gitRef,
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
await this.letIOClear();
|
|
537
|
+
|
|
538
|
+
let secondaryDelay = this.getSecondaryShardDelay(callSpec);
|
|
539
|
+
if (secondaryDelay) {
|
|
540
|
+
async function getIsFinished() {
|
|
541
|
+
return await proxyWatcher.commitFunction({
|
|
542
|
+
watchFunction() {
|
|
543
|
+
let syncedModule = skipPermissions(() =>
|
|
544
|
+
functionSchema()[callSpec.DomainName].PathFunctionRunner[callSpec.ModuleId]
|
|
545
|
+
);
|
|
546
|
+
return !!atomicObjectRead(syncedModule.Results[callSpec.CallId]);
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
// Call once, so we are already watching the
|
|
551
|
+
await delay(secondaryDelay);
|
|
552
|
+
let isFinished = await getIsFinished();
|
|
553
|
+
if (isFinished) {
|
|
554
|
+
console.info(`Function run finished before we could run it (another server must have ran it)`, {
|
|
555
|
+
callId: callSpec.CallId,
|
|
556
|
+
});
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
if (PathFunctionRunner.DEBUG_CALLS) {
|
|
560
|
+
let fraction = getRoutingOverridePart(callSpec.CallId)?.route;
|
|
561
|
+
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(callSpec, functionSpec, true)}`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
PathFunctionRunner.RUN_START_COUNT++;
|
|
566
|
+
if (PathFunctionRunner.DEBUG_CALLS) {
|
|
567
|
+
console.log(`STARTING ${getDebugName(callSpec, functionSpec, true)}`);
|
|
568
|
+
}
|
|
569
|
+
let startTime = Date.now();
|
|
570
|
+
let runCount = 0;
|
|
571
|
+
stats.lastInternalLoopCount = 0;
|
|
572
|
+
|
|
573
|
+
let finalWrites: PathValue[] | undefined;
|
|
574
|
+
|
|
575
|
+
let evalTime = 0;
|
|
576
|
+
let retries = 0;
|
|
577
|
+
|
|
578
|
+
let nooped = false;
|
|
579
|
+
|
|
580
|
+
while (true) {
|
|
581
|
+
let isFunctionSpecOutdated = false;
|
|
582
|
+
try {
|
|
583
|
+
if (retries > 10) {
|
|
584
|
+
console.error(`Could not sync stable function spec, aborting call. Retries: ${retries}`, {
|
|
585
|
+
callId: callSpec.CallId,
|
|
586
|
+
});
|
|
587
|
+
throw new Error(`Could not sync stable function spec, aborting call. Retries: ${retries}`);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
let moduleExports = await this.getExportsFromSpec(functionSpec);
|
|
591
|
+
|
|
592
|
+
// ALWAYS get the permissions as well. This isn't perfect, as we could very well access data in other files, but... it helps
|
|
593
|
+
// reduce extra iterations inside of commitFunction...
|
|
594
|
+
await getModuleFromSpec({ ...functionSpec, exportPathStr: getExportPath(PERMISSIONS_FUNCTION_ID), FunctionId: PERMISSIONS_FUNCTION_ID, });
|
|
595
|
+
|
|
596
|
+
let exportPath = getPathFromStr(functionSpec.exportPathStr);
|
|
597
|
+
let exportObj = moduleExports as any;
|
|
598
|
+
for (let path of exportPath) {
|
|
599
|
+
exportObj = exportObj[path];
|
|
600
|
+
}
|
|
601
|
+
let baseFunction = exportObj as Function;
|
|
602
|
+
|
|
603
|
+
if (typeof baseFunction !== "function") {
|
|
604
|
+
throw new Error(`Export at ${JSON.stringify(exportPath.join("."))} was not a function (it was ${typeof baseFunction}). If in development, you must restart the development function runner server when you add new functions.`);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
let devFunctionMetadata: FunctionMetadata | undefined;
|
|
608
|
+
|
|
609
|
+
let devModule = getDevelopmentModule(functionSpec.ModuleId);
|
|
610
|
+
if (devModule) {
|
|
611
|
+
let schemaObj = getSchemaObject(devModule);
|
|
612
|
+
devFunctionMetadata = schemaObj?.functionMetadata?.[functionSpec.FunctionId];
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
console.info(`Running function: ${getDebugName(callSpec, functionSpec, true)}`, {
|
|
616
|
+
callId: callSpec.CallId,
|
|
617
|
+
outerLoop: retries,
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
await proxyWatcher.commitFunction({
|
|
621
|
+
canWrite: true,
|
|
622
|
+
debugName: getDebugName(callSpec, functionSpec),
|
|
623
|
+
runAtTime: callSpec.runAtTime,
|
|
624
|
+
getPermissionsCheck: PermissionsChecker && (() => new PermissionsChecker(callSpec)),
|
|
625
|
+
nestedCalls: "inline",
|
|
626
|
+
temporary: true,
|
|
627
|
+
maxLocksOverride: devFunctionMetadata ? devFunctionMetadata.maxLocksOverride : functionSpec.maxLocksOverride,
|
|
628
|
+
source: callSpec.CallId,
|
|
629
|
+
watchFunction: function runCallWatcher() {
|
|
630
|
+
runCount++;
|
|
631
|
+
stats.lastInternalLoopCount = runCount;
|
|
632
|
+
stats.totalInternalLoopCount++;
|
|
633
|
+
if (PathFunctionRunner.DEBUG_CALLS) {
|
|
634
|
+
console.log(`Evaluating (try count ${runCount}) ${getDebugName(callSpec, functionSpec, true)}`);
|
|
635
|
+
}
|
|
636
|
+
if (runCount > PathFunctionRunner.MAX_WATCH_LOOPS) {
|
|
637
|
+
let errorMessage = `MAX_WATCH_LOOPS exceeded for ${getDebugName(callSpec, functionSpec, true)}. All accesses have to be consistent. So Querysub.time() instead of Date.now() and Querysub.nextId() instead of nextId() / Math.random(). If you need multiple random numbers, keep track of an index, and pass it to Querysub.nextId() for the nth random number.`;
|
|
638
|
+
console.error(errorMessage, {
|
|
639
|
+
callId: callSpec.CallId,
|
|
640
|
+
runCount,
|
|
641
|
+
limit: PathFunctionRunner.MAX_WATCH_LOOPS,
|
|
642
|
+
});
|
|
643
|
+
throw new Error(errorMessage);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// We NEED to depend on the function spec, so spec updates can be atomic (which is needed to deploy an update
|
|
647
|
+
// to many nodes at once, without having any period where multiple versions both commit values).
|
|
648
|
+
let syncedModule = skipPermissions(() =>
|
|
649
|
+
functionSchema()[callSpec.DomainName].PathFunctionRunner[callSpec.ModuleId]
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
// Make sure we are running the latest function (by checking it here, we also lock the values,
|
|
653
|
+
// so if it is updated we atomically rerun / reject all calls which were racing with us).
|
|
654
|
+
if (!!isPublic()) {
|
|
655
|
+
let syncedSpec = skipPermissions(() =>
|
|
656
|
+
atomicObjectRead(syncedModule.Sources[callSpec.FunctionId])
|
|
657
|
+
);
|
|
658
|
+
if (!syncedSpec) {
|
|
659
|
+
throw new Error(`Function spec not found for ${getDebugName(callSpec, functionSpec, true)}`);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// (We also need to depend on the RIGHT function spec).
|
|
663
|
+
if (
|
|
664
|
+
syncedSpec && (
|
|
665
|
+
syncedSpec.DomainName !== functionSpec.DomainName
|
|
666
|
+
|| syncedSpec.ModuleId !== functionSpec.ModuleId
|
|
667
|
+
|| syncedSpec.FilePath !== functionSpec.FilePath
|
|
668
|
+
|| syncedSpec.FunctionId !== functionSpec.FunctionId
|
|
669
|
+
|| syncedSpec.exportPathStr !== functionSpec.exportPathStr
|
|
670
|
+
|| syncedSpec.FilePath !== functionSpec.FilePath
|
|
671
|
+
|| syncedSpec.gitRef !== functionSpec.gitRef
|
|
672
|
+
|| syncedSpec.gitURL !== functionSpec.gitURL
|
|
673
|
+
|| syncedSpec.maxLocksOverride !== functionSpec.maxLocksOverride
|
|
674
|
+
)
|
|
675
|
+
) {
|
|
676
|
+
isFunctionSpecOutdated = true;
|
|
677
|
+
functionSpec = {
|
|
678
|
+
DomainName: syncedSpec.DomainName,
|
|
679
|
+
ModuleId: syncedSpec.ModuleId,
|
|
680
|
+
FilePath: syncedSpec.FilePath,
|
|
681
|
+
FunctionId: syncedSpec.FunctionId,
|
|
682
|
+
exportPathStr: syncedSpec.exportPathStr,
|
|
683
|
+
gitRef: syncedSpec.gitRef,
|
|
684
|
+
gitURL: syncedSpec.gitURL,
|
|
685
|
+
};
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
let evalTimeStart = Date.now();
|
|
691
|
+
try {
|
|
692
|
+
let args = parseArgs(callSpec);
|
|
693
|
+
overrideCurrentCall({ spec: callSpec, fnc: functionSpec, }, () => {
|
|
694
|
+
baseFunction(...args);
|
|
695
|
+
});
|
|
696
|
+
} finally {
|
|
697
|
+
let evalTimeDelta = Date.now() - evalTimeStart;
|
|
698
|
+
evalTime += evalTimeDelta;
|
|
699
|
+
stats.evalTime += evalTimeDelta;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// NOTE: The results are only temporarily stored, but serve as a way to let clients
|
|
703
|
+
// know figure out the dependencies for the call, which makes function call prediction
|
|
704
|
+
// possible.
|
|
705
|
+
skipPermissions(() => {
|
|
706
|
+
let result = doProxyOptions({ forceReadLatest: true }, () => atomicObjectRead(syncedModule.Results[callSpec.CallId]));
|
|
707
|
+
if (result) {
|
|
708
|
+
nooped = true;
|
|
709
|
+
// It is important to NOOP if it has already been written. Otherwise there could be multiple
|
|
710
|
+
// runs of the same function, which could easily vary (if they use Math.random(), etc).
|
|
711
|
+
// WHICH, as the write time is predetermined, would cause ValuePaths with equal times, but
|
|
712
|
+
// different values (which breaks a lot of assumptions much of the code depends on! such
|
|
713
|
+
// as locks, etc).
|
|
714
|
+
if (!secondaryDelay && Querysub.isAllSynced()) {
|
|
715
|
+
console.warn(`Skipping function write, as it has already been written to by another FunctionRunner.`);
|
|
716
|
+
}
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
nooped = false;
|
|
720
|
+
let currentTimeTaken = Date.now() - startTime;
|
|
721
|
+
stats.timeTaken = currentTimeTaken;
|
|
722
|
+
let totalTime = Date.now() - stats.firstQueuedTime;
|
|
723
|
+
syncedModule.Results[callSpec.CallId] = atomicObjectWrite({
|
|
724
|
+
lastInternalLoopCount: runCount,
|
|
725
|
+
outerLoopCount: stats.outerLoopCount,
|
|
726
|
+
totalInternalLoopCount: stats.totalInternalLoopCount,
|
|
727
|
+
timeTaken: currentTimeTaken,
|
|
728
|
+
evalTime,
|
|
729
|
+
totalTime,
|
|
730
|
+
});
|
|
731
|
+
proxyWatcher.setEventPath(() => syncedModule.Results[callSpec.CallId]);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
let watcher = proxyWatcher.getTriggeredWatcher();
|
|
735
|
+
if (watcher.hasAnyUnsyncedAccesses()) {
|
|
736
|
+
console.info(`Function run, unsynced values`, {
|
|
737
|
+
callId: callSpec.CallId,
|
|
738
|
+
count: watcher.pendingUnsyncedAccesses.size,
|
|
739
|
+
countParents: watcher.pendingUnsyncedParentAccesses.size,
|
|
740
|
+
promiseUnsynced: watcher.specialPromiseUnsynced,
|
|
741
|
+
blockedByOrder: isProxyBlockedByOrder(watcher),
|
|
742
|
+
path: Array.from(watcher.pendingUnsyncedAccesses)[0],
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
}, {
|
|
747
|
+
onWritesCommitted(writes) {
|
|
748
|
+
finalWrites = writes;
|
|
749
|
+
},
|
|
750
|
+
});
|
|
751
|
+
} catch (e: any) {
|
|
752
|
+
|
|
753
|
+
retries++;
|
|
754
|
+
if (isFunctionSpecOutdated) continue;
|
|
755
|
+
|
|
756
|
+
// NOTE: We are changing our paradigm to reject early, rather than retry. This is more stable. You can't really commit
|
|
757
|
+
// important values and then just leave, unless those values have no locks. This helps prevent values from being
|
|
758
|
+
// in different states on different machines, by just rejecting the call.
|
|
759
|
+
// if (e.message.includes("MAX_CHANGE_AGE_EXCEEDED") && retries < 10 && evalTime < MAX_ACCEPTED_CHANGE_AGE) {
|
|
760
|
+
// // NOTE: This should never really happen. It is only really if our PathValueServer goes down for a minute or so,
|
|
761
|
+
// // so we don't just drop all of our function calls.
|
|
762
|
+
// // - This is why we log every time, because no user actions should be able to result in this happening frequently.
|
|
763
|
+
// 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)}`));
|
|
764
|
+
// // NOTE: Updating the time is fine, and won't break prediction rejections, as we reject based on path, not time.
|
|
765
|
+
// // (It might result in the prediction being wrong, but it is better than the user action being completely removed).
|
|
766
|
+
// callPath = { ...callPath, runAtTime: getNextTime() };
|
|
767
|
+
// retries++;
|
|
768
|
+
// continue;
|
|
769
|
+
// }
|
|
770
|
+
//if (PathFunctionRunner.DEBUG_CALLS)
|
|
771
|
+
{
|
|
772
|
+
console.log(`Function run error ${getDebugName(callSpec, functionSpec, true)}`, {
|
|
773
|
+
callId: callSpec.CallId,
|
|
774
|
+
error: e.stack,
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
finalWrites = await this.finishCallWithError({
|
|
779
|
+
callSpec,
|
|
780
|
+
functionSpec,
|
|
781
|
+
error: e.stack || "",
|
|
782
|
+
runCount,
|
|
783
|
+
startTime,
|
|
784
|
+
evalTime,
|
|
785
|
+
outerLoopCount: stats.outerLoopCount,
|
|
786
|
+
totalInternalLoopCount: stats.totalInternalLoopCount,
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
retries++;
|
|
791
|
+
if (isFunctionSpecOutdated) continue;
|
|
792
|
+
break;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (PathFunctionRunner.DEBUG_CALLS) {
|
|
796
|
+
console.log(`FINISHED${nooped ? " (skipped)" : ""} ${getDebugName(callSpec, functionSpec, true)}, writes: ${finalWrites?.length}, took ${blue(formatTime(Date.now() - startTime))}`);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
let wallTime = Date.now() - startTime;
|
|
800
|
+
let syncTime = wallTime - evalTime;
|
|
801
|
+
|
|
802
|
+
for (let write of finalWrites || []) {
|
|
803
|
+
console.info(`Function path write`, {
|
|
804
|
+
callId: callSpec.CallId,
|
|
805
|
+
path: write.path,
|
|
806
|
+
timeId: write.time.time,
|
|
807
|
+
isTransparent: write.isTransparent,
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
console.info(`Finished function evaluation: ${getDebugName(callSpec, functionSpec, true)}`, {
|
|
812
|
+
callId: callSpec.CallId,
|
|
813
|
+
outerLoop: retries,
|
|
814
|
+
finalWrites: finalWrites?.length,
|
|
815
|
+
argsEncoded: "", functionSpec,
|
|
816
|
+
wallTime, syncTime, evalTime,
|
|
817
|
+
loops: runCount,
|
|
818
|
+
});
|
|
819
|
+
PathFunctionRunner.RUN_FINISH_COUNT++;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// NOTE: This does and a
|
|
823
|
+
public async finishCallWithError(config: {
|
|
824
|
+
callSpec: CallSpec;
|
|
825
|
+
functionSpec: FunctionSpec | undefined;
|
|
826
|
+
error: string;
|
|
827
|
+
runCount: number;
|
|
828
|
+
startTime: number;
|
|
829
|
+
evalTime: number;
|
|
830
|
+
outerLoopCount: number;
|
|
831
|
+
totalInternalLoopCount: number;
|
|
832
|
+
}): Promise<PathValue[] | undefined> {
|
|
833
|
+
|
|
834
|
+
const { Querysub } = await import("../4-querysub/Querysub");
|
|
835
|
+
if (Querysub.isInSyncedCall()) {
|
|
836
|
+
Querysub.onCommitFinished(() => {
|
|
837
|
+
void this.finishCallWithError(config);
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
let { callSpec, functionSpec, error, runCount, startTime, evalTime, outerLoopCount, totalInternalLoopCount } = config;
|
|
842
|
+
|
|
843
|
+
let finalWrites: PathValue[] | undefined;
|
|
844
|
+
|
|
845
|
+
let callStats = this.callStats.get(callSpec.CallId);
|
|
846
|
+
let totalTime = callStats ? Date.now() - callStats.firstQueuedTime : Date.now() - startTime;
|
|
847
|
+
|
|
848
|
+
await proxyWatcher.commitFunction({
|
|
849
|
+
canWrite: true,
|
|
850
|
+
// NOTE: We don't set runAtTime, because the rejection might be due to the call being too old
|
|
851
|
+
debugName: `error (${getDebugName(callSpec, functionSpec)})`,
|
|
852
|
+
eventWrite: true,
|
|
853
|
+
doNotStoreWritesAsPredictions: true,
|
|
854
|
+
watchFunction() {
|
|
855
|
+
let syncedModule = functionSchema()[callSpec.DomainName].PathFunctionRunner[callSpec.ModuleId];
|
|
856
|
+
// Don't clobber the value. I'm pretty sure event writes still read values, so we should still do a synchronization to see if there is an existing result.
|
|
857
|
+
let result = doProxyOptions({ forceReadLatest: true }, () => atomicObjectRead(syncedModule.Results[callSpec.CallId]));
|
|
858
|
+
if (result) return;
|
|
859
|
+
syncedModule.Results[callSpec.CallId] = atomicObjectWrite({
|
|
860
|
+
error,
|
|
861
|
+
lastInternalLoopCount: runCount,
|
|
862
|
+
outerLoopCount,
|
|
863
|
+
totalInternalLoopCount,
|
|
864
|
+
timeTaken: Date.now() - startTime,
|
|
865
|
+
evalTime,
|
|
866
|
+
totalTime,
|
|
867
|
+
});
|
|
868
|
+
},
|
|
869
|
+
}, {
|
|
870
|
+
onWritesCommitted(writes) {
|
|
871
|
+
finalWrites = writes;
|
|
872
|
+
},
|
|
873
|
+
});
|
|
874
|
+
return finalWrites;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
export async function preloadFunctions(specs: FunctionSpec[], progress?: DeployProgress) {
|
|
879
|
+
progress?.({ section: "Finding FunctionPreloadControllers", progress: 0 });
|
|
880
|
+
let nodeIds = await getControllerNodeIdList(FunctionPreloadController);
|
|
881
|
+
progress?.({ section: "Finding FunctionPreloadControllers", progress: 1 });
|
|
882
|
+
await Promise.allSettled(nodeIds.map(async nodeObj => {
|
|
883
|
+
let nodeId = nodeObj.nodeId;
|
|
884
|
+
let controller = FunctionPreloadController.nodes[nodeId];
|
|
885
|
+
let section = `${nodeObj.entryPoint}:${nodeId}|Preloading Functions`;
|
|
886
|
+
progress?.({ section, progress: 0 });
|
|
887
|
+
console.log(blue(`Preloading functions on ${String(nodeId)}`));
|
|
888
|
+
await errorToUndefined(controller.preloadFunctions(specs));
|
|
889
|
+
progress?.({ section, progress: 1 });
|
|
890
|
+
console.log(blue(`Finished preloading functions on ${String(nodeId)}`));
|
|
891
|
+
}));
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
export function getDevFunctionSpecFromCall(call: {
|
|
895
|
+
DomainName: string;
|
|
896
|
+
ModuleId: string;
|
|
897
|
+
FunctionId: string;
|
|
898
|
+
}): FunctionSpec | undefined {
|
|
899
|
+
let domainName = call.DomainName;
|
|
900
|
+
let moduleId = call.ModuleId;
|
|
901
|
+
let functionId = call.FunctionId;
|
|
902
|
+
|
|
903
|
+
let devModule = getDevelopmentModule(moduleId);
|
|
904
|
+
if (!devModule) {
|
|
905
|
+
console.warn(yellow(`Cannot call ${domainName}.${moduleId}.Sources.${functionId} because the module is not deployed and not referenced in deploy.ts`));
|
|
906
|
+
return undefined;
|
|
907
|
+
}
|
|
908
|
+
let filePath = getModuleRelativePath(devModule);
|
|
909
|
+
let gitURL = getGitURLSync(undefined);
|
|
910
|
+
let gitRef = getGitRefSync(undefined);
|
|
911
|
+
return {
|
|
912
|
+
DomainName: domainName,
|
|
913
|
+
ModuleId: moduleId,
|
|
914
|
+
FunctionId: functionId,
|
|
915
|
+
exportPathStr: getExportPath(functionId),
|
|
916
|
+
FilePath: filePath,
|
|
917
|
+
gitURL,
|
|
918
|
+
gitRef,
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
class FunctionPreloaderBase {
|
|
923
|
+
async preloadFunctions(specs: FunctionSpec[]) {
|
|
924
|
+
if (!isPublic()) return;
|
|
925
|
+
for (let spec of specs) {
|
|
926
|
+
await getModuleFromSpec(spec);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const FunctionPreloadController = SocketFunction.register(
|
|
932
|
+
"FunctionPreloader-c966a1a6-5d3f-4453-bd03-ae84b574ec00",
|
|
933
|
+
new FunctionPreloaderBase(),
|
|
934
|
+
() => ({
|
|
935
|
+
preloadFunctions: {},
|
|
936
|
+
}),
|
|
937
|
+
() => ({
|
|
938
|
+
hooks: [requiresNetworkTrustHook],
|
|
939
|
+
}),
|
|
940
|
+
{
|
|
941
|
+
noAutoExpose: true,
|
|
942
|
+
}
|
|
943
|
+
);
|