querysub 0.193.0 → 0.195.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/package.json +1 -1
- package/src/-f-node-discovery/NodeDiscovery.ts +7 -7
- package/src/2-proxy/PathValueProxyWatcher.ts +3 -1
- package/src/4-deploy/edgeNodes.ts +1 -1
- package/src/deployManager/DeployPage.tsx +3 -15
- package/src/deployManager/machineApplyMain.ts +3 -12
- package/src/diagnostics/managementPages.tsx +0 -1
- package/src/library-components/SyncedController.ts +27 -1
- package/src/library-components/SyncedControllerLoadingIndicator.tsx +50 -0
package/package.json
CHANGED
|
@@ -20,7 +20,7 @@ import { isDefined } from "../misc";
|
|
|
20
20
|
import { diskLog } from "../diagnostics/logs/diskLogger";
|
|
21
21
|
import { getBootedEdgeNode } from "../-0-hooks/hooks";
|
|
22
22
|
|
|
23
|
-
let HEARTBEAT_INTERVAL = timeInMinute *
|
|
23
|
+
let HEARTBEAT_INTERVAL = timeInMinute * 15;
|
|
24
24
|
// Interval which we check other heartbeats
|
|
25
25
|
let CHECK_INTERVAL = HEARTBEAT_INTERVAL;
|
|
26
26
|
// If the heartbeat is older than thing, it fails the dead check
|
|
@@ -30,19 +30,19 @@ let DEAD_THRESHOLD = HEARTBEAT_INTERVAL * 2;
|
|
|
30
30
|
let DEAD_CHECK_COUNT = 4;
|
|
31
31
|
// If we find we are unable to write our heartbeat, we have to kill our own process. Otherwise it may
|
|
32
32
|
// be too out of sync, and might commit unverified data to the disk.
|
|
33
|
-
let SUICIDE_HEARTBEAT_THRESHOLD =
|
|
33
|
+
let SUICIDE_HEARTBEAT_THRESHOLD = HEARTBEAT_INTERVAL * 4;
|
|
34
34
|
|
|
35
|
-
let CLIENTSIDE_POLL_RATE = timeInMinute *
|
|
35
|
+
let CLIENTSIDE_POLL_RATE = timeInMinute * 30;
|
|
36
36
|
|
|
37
37
|
// We can't poll backblaze too often. One a minute starts to cost around 10 cents per month.
|
|
38
38
|
// So once a second would cost 6 USD per month, per service... which starts to get expensive.
|
|
39
|
-
let DISK_AUDIT_RATE = timeInMinute *
|
|
39
|
+
let DISK_AUDIT_RATE = timeInMinute * 15;
|
|
40
40
|
// We CAN poll our API frequently. The overhead for calls should be far less than 1ms, and
|
|
41
41
|
// the bandwidth should also be effectively nothing. Maybe 2.5GB per month if we send a
|
|
42
42
|
// request per second, and each request is 1000 bytes (which as we use websockets, it
|
|
43
|
-
// probably is less than that). Which is around 2.5 cents on
|
|
43
|
+
// probably is less than that). Which is around 2.5 cents on digital ocean IF we go over
|
|
44
44
|
// our 1TB/month allowance.
|
|
45
|
-
let API_AUDIT_RATE = timeInSecond *
|
|
45
|
+
let API_AUDIT_RATE = timeInSecond * 30;
|
|
46
46
|
let API_AUDIT_COUNT = 12;
|
|
47
47
|
|
|
48
48
|
|
|
@@ -392,7 +392,7 @@ async function runMainSyncLoops(discoveryReady: PromiseObj<void>) {
|
|
|
392
392
|
|
|
393
393
|
console.log(magenta(`Node discovery is loaded`));
|
|
394
394
|
|
|
395
|
-
await
|
|
395
|
+
await runInfinitePollCallAtStart(HEARTBEAT_INTERVAL, async function nodeDiscoverHeartbeat() {
|
|
396
396
|
// If we waited too long, other nodes might think we are dead. In which case, we SHOULD terminate.
|
|
397
397
|
if (!isNoNetwork()) {
|
|
398
398
|
// FIRST, verify we didn't delay too long (to make sure we kill any nodes that were disconnected
|
|
@@ -759,9 +759,11 @@ export class PathValueProxyWatcher {
|
|
|
759
759
|
) {
|
|
760
760
|
if (!watcher.pendingWrites.has(pathStr)) {
|
|
761
761
|
let parentPathStr = pathStr;
|
|
762
|
+
let isLocal = pathStr.startsWith(LOCAL_DOMAIN_PATH);
|
|
763
|
+
let depthToData = isLocal ? 1 : DEPTH_TO_DATA;
|
|
762
764
|
while (true) {
|
|
763
765
|
parentPathStr = getParentPathStr(parentPathStr);
|
|
764
|
-
if (getPathDepth(parentPathStr) <
|
|
766
|
+
if (getPathDepth(parentPathStr) < depthToData) break;
|
|
765
767
|
// We don't need to check all parents, as if any value is set, all parents will
|
|
766
768
|
// likely have their specialObjectWriteValue set, due to a previous run of this function!
|
|
767
769
|
if (watcher.pendingWrites.has(parentPathStr)) break;
|
|
@@ -26,7 +26,7 @@ import { onEdgeNodesChanged } from "./edgeBootstrap";
|
|
|
26
26
|
import { startEdgeNotifier } from "./edgeClientWatcher";
|
|
27
27
|
import { getGitRefLive, getGitURLLive } from "./git";
|
|
28
28
|
|
|
29
|
-
const UPDATE_POLL_INTERVAL = timeInMinute;
|
|
29
|
+
const UPDATE_POLL_INTERVAL = timeInMinute * 15;
|
|
30
30
|
const DEAD_NODE_COUNT_THRESHOLD = 15;
|
|
31
31
|
|
|
32
32
|
const edgeNodeStorage = isNodeTrue() && nestArchives("edgenodes/", getArchivesBackblazePublic(getDomain()));
|
|
@@ -2,14 +2,16 @@ import { SocketFunction } from "socket-function/SocketFunction";
|
|
|
2
2
|
import { qreact } from "../../src/4-dom/qreact";
|
|
3
3
|
import { DeployController } from "./deploySchema";
|
|
4
4
|
import { css } from "typesafecss";
|
|
5
|
+
import { syncedIsAnyLoading } from "../library-components/SyncedController";
|
|
5
6
|
|
|
6
7
|
export class DeployPage extends qreact.Component {
|
|
7
8
|
render() {
|
|
8
9
|
let controller = DeployController(SocketFunction.browserNodeId());
|
|
10
|
+
let allInfo = controller.getAllInfo();
|
|
9
11
|
return <div className={css.vbox(10)}>
|
|
10
12
|
<h1>DeployPage</h1>
|
|
11
13
|
<div className={css.whiteSpace("pre-wrap")}>
|
|
12
|
-
{JSON.stringify(
|
|
14
|
+
{JSON.stringify(allInfo, null, 4)}
|
|
13
15
|
</div>
|
|
14
16
|
</div>;
|
|
15
17
|
}
|
|
@@ -18,20 +20,6 @@ export class DeployPage extends qreact.Component {
|
|
|
18
20
|
|
|
19
21
|
todonext
|
|
20
22
|
|
|
21
|
-
# Domain config file
|
|
22
|
-
- Have the domain configured via a file in the repo, so we don't have to set it on every single script
|
|
23
|
-
- This also makes it possible to use binary scripts directly in querysub without having to add a script in our package.json to set the domain parameter
|
|
24
|
-
- FORWARD the main scripts
|
|
25
|
-
function
|
|
26
|
-
function-public
|
|
27
|
-
server
|
|
28
|
-
server-public
|
|
29
|
-
deploy
|
|
30
|
-
... anything that uses node_modules, which is a lot...
|
|
31
|
-
- And remove all --domain arguments (which is a lot as well)
|
|
32
|
-
- ALSO, support setting arbitrary config parameter in this config file?
|
|
33
|
-
- Maybe on a per script basis, giving scripts names.
|
|
34
|
-
|
|
35
23
|
5) synced controller loading indicator
|
|
36
24
|
- Probably which wataches all controllers (on all nodes), for any pending, shown globally
|
|
37
25
|
6) Validate that our loading indicator work, as well as our notifications on changed configuration, etc
|
|
@@ -28,7 +28,7 @@ const getLiveMachineInfo = measureWrap(async function getLiveMachineInfo() {
|
|
|
28
28
|
machineInfo.info.lscpu = await errorToUndefinedSilent(runPromise("lscpu")) || "";
|
|
29
29
|
machineInfo.info.id = await errorToUndefinedSilent(runPromise("id")) || await errorToUndefinedSilent(runPromise("whoami")) || "";
|
|
30
30
|
|
|
31
|
-
// TODO: Populate services via checking tmux for special keywords ("-
|
|
31
|
+
// TODO: Populate services via checking tmux for special keywords ("-apply", probably...)
|
|
32
32
|
|
|
33
33
|
return machineInfo;
|
|
34
34
|
});
|
|
@@ -38,20 +38,11 @@ const updateMachineInfo = measureWrap(async function updateMachineInfo() {
|
|
|
38
38
|
await new DeployControllerBase().setMachineInfo(machineInfo.machineId, machineInfo);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
//todonext
|
|
42
|
-
// The child process will be run with shell, and then we'll watch it (don't use runPromise)
|
|
43
|
-
|
|
44
|
-
function getTotalTime(module: NodeJS.Module, now: number) {
|
|
45
|
-
return (module.evalEndTime || now) - (module.evalStartTime || now);
|
|
46
|
-
}
|
|
47
41
|
|
|
48
42
|
|
|
49
43
|
export async function machineApplyMain() {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// await Querysub.hostService("machine-apply");
|
|
53
|
-
|
|
54
|
-
// await updateMachineInfo();
|
|
44
|
+
await Querysub.hostService("machine-apply");
|
|
45
|
+
await updateMachineInfo();
|
|
55
46
|
}
|
|
56
47
|
|
|
57
48
|
machineApplyMain().catch(console.error);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { atomic, atomicObjectWrite, atomicObjectWriteNoFreeze, doAtomicWrites, proxyWatcher } from "../../src/2-proxy/PathValueProxyWatcher";
|
|
1
|
+
import { atomic, atomicObjectWrite, atomicObjectWriteNoFreeze, doAtomicWrites, isTransparentValue, proxyWatcher, specialObjectWriteValue } from "../../src/2-proxy/PathValueProxyWatcher";
|
|
2
2
|
import { Querysub } from "../../src/4-querysub/Querysub";
|
|
3
3
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
4
4
|
import { FullCallType, SocketRegistered } from "socket-function/SocketFunctionTypes";
|
|
5
5
|
import { onHotReload } from "socket-function/hot/HotReloadController";
|
|
6
6
|
import { cache } from "socket-function/src/caching";
|
|
7
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
7
8
|
import { nextId } from "socket-function/src/misc";
|
|
8
9
|
import { getCallObj } from "socket-function/src/nodeProxy";
|
|
9
10
|
import { MaybePromise } from "socket-function/src/types";
|
|
@@ -31,6 +32,22 @@ onHotReload(() => {
|
|
|
31
32
|
});
|
|
32
33
|
});
|
|
33
34
|
|
|
35
|
+
export function syncedIsAnyLoading() {
|
|
36
|
+
return Querysub.fastRead(() => {
|
|
37
|
+
for (let controllerId in syncedData()) {
|
|
38
|
+
for (let nodeId in syncedData()[controllerId]) {
|
|
39
|
+
for (let fnc in syncedData()[controllerId][nodeId]) {
|
|
40
|
+
for (let argsHash in syncedData()[controllerId][nodeId][fnc]) {
|
|
41
|
+
if (atomic(syncedData()[controllerId][nodeId][fnc][argsHash].promise)) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
34
51
|
let syncedData = Querysub.createLocalSchema<{
|
|
35
52
|
[controllerId: string]: {
|
|
36
53
|
[nodeId: string]: {
|
|
@@ -166,12 +183,20 @@ export function getSyncedController<T extends SocketRegistered>(
|
|
|
166
183
|
|
|
167
184
|
// NOTE: If we are invalidated when the promise is running, nothing happens (as we don't watch invalidated if we are running with a promise). BUT, if the promise isn't running, we will run again, and start running again. In this way we don't queue up a lot if we invalidate a lot, but we do always run again after the invalidation to get the latest result!
|
|
168
185
|
if (!promise && (!result || atomic(obj.invalidated))) {
|
|
186
|
+
let timeStart = Date.now();
|
|
187
|
+
function logFinished() {
|
|
188
|
+
let duration = Date.now() - timeStart;
|
|
189
|
+
if (duration > 500) {
|
|
190
|
+
console.warn(`Slow call ${fncName} took ${formatTime(duration)}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
169
193
|
let promise = controller.nodes[nodeId][fncName](...args) as Promise<unknown>;
|
|
170
194
|
doAtomicWrites(() => {
|
|
171
195
|
obj.promise = promise;
|
|
172
196
|
});
|
|
173
197
|
promise.then(
|
|
174
198
|
result => {
|
|
199
|
+
logFinished();
|
|
175
200
|
Querysub.commitLocal(() => {
|
|
176
201
|
obj.result = atomicObjectWriteNoFreeze({ result });
|
|
177
202
|
obj.promise = undefined;
|
|
@@ -192,6 +217,7 @@ export function getSyncedController<T extends SocketRegistered>(
|
|
|
192
217
|
});
|
|
193
218
|
},
|
|
194
219
|
error => {
|
|
220
|
+
logFinished();
|
|
195
221
|
Querysub.commitLocal(() => {
|
|
196
222
|
obj.result = atomicObjectWriteNoFreeze({ error });
|
|
197
223
|
obj.promise = undefined;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { css } from "typesafecss";
|
|
2
|
+
import { qreact } from "../4-dom/qreact";
|
|
3
|
+
import { syncedIsAnyLoading } from "./SyncedController";
|
|
4
|
+
import { formatTime } from "socket-function/src/formatting/format";
|
|
5
|
+
|
|
6
|
+
export class SyncedControllerLoadingIndicator extends qreact.Component {
|
|
7
|
+
timeLastLoaded = Date.now();
|
|
8
|
+
lastWasLoaded = false;
|
|
9
|
+
render() {
|
|
10
|
+
if (!syncedIsAnyLoading()) {
|
|
11
|
+
if (!this.lastWasLoaded) {
|
|
12
|
+
console.log(`Loaded all SyncedController calls in ${formatTime(Date.now() - this.timeLastLoaded)}`);
|
|
13
|
+
}
|
|
14
|
+
this.timeLastLoaded = Date.now();
|
|
15
|
+
this.lastWasLoaded = true;
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
this.lastWasLoaded = false;
|
|
19
|
+
const spinAnimationClass = `SyncedControllerLoadingIndicator-spin`;
|
|
20
|
+
|
|
21
|
+
return <div className={
|
|
22
|
+
css.position("fixed").bottom(0).left(0).zIndex(1000)
|
|
23
|
+
.padding("12px 16px")
|
|
24
|
+
.background("rgba(0, 0, 0, 0.8)")
|
|
25
|
+
.color("white")
|
|
26
|
+
.borderTopRightRadius(8)
|
|
27
|
+
.hbox(8)
|
|
28
|
+
}>
|
|
29
|
+
<div className={
|
|
30
|
+
css.width("16px").height("16px")
|
|
31
|
+
.border("2px solid rgba(255, 255, 255, 0.3)")
|
|
32
|
+
.borderTop("2px solid white")
|
|
33
|
+
.borderRadius("50%")
|
|
34
|
+
+ " " + spinAnimationClass
|
|
35
|
+
}></div>
|
|
36
|
+
<span>Syncing data...</span>
|
|
37
|
+
<style>
|
|
38
|
+
{`
|
|
39
|
+
@keyframes ${spinAnimationClass} {
|
|
40
|
+
0% { transform: rotate(0deg); }
|
|
41
|
+
100% { transform: rotate(360deg); }
|
|
42
|
+
}
|
|
43
|
+
.${spinAnimationClass} {
|
|
44
|
+
animation: ${spinAnimationClass} 1s linear infinite;
|
|
45
|
+
}
|
|
46
|
+
`}
|
|
47
|
+
</style>
|
|
48
|
+
</div>;
|
|
49
|
+
}
|
|
50
|
+
}
|