querysub 0.9.0 → 0.10.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
CHANGED
|
@@ -9,7 +9,6 @@ import debugbreak from "debugbreak";
|
|
|
9
9
|
import cborx from "cbor-x";
|
|
10
10
|
import { lazy } from "socket-function/src/caching";
|
|
11
11
|
import { secureRandom } from "../misc/random";
|
|
12
|
-
import { onCallPredict } from "../4-querysub/QuerysubController";
|
|
13
12
|
import { isDefined } from "../misc";
|
|
14
13
|
import { blue, green, red } from "socket-function/src/formatting/logColors";
|
|
15
14
|
import { getPathStr2 } from "../path";
|
|
@@ -50,6 +49,7 @@ export async function replaceFunctions(config: {
|
|
|
50
49
|
let currentHashes = new Set(currentFunctions.map(x => JSON.stringify(x)));
|
|
51
50
|
|
|
52
51
|
let prevFunctionNames = new Set(previousFunctions.map(getFunctionName));
|
|
52
|
+
let currentFunctionNames = new Set(currentFunctions.map(getFunctionName));
|
|
53
53
|
|
|
54
54
|
let messages: {
|
|
55
55
|
text: string;
|
|
@@ -57,10 +57,10 @@ export async function replaceFunctions(config: {
|
|
|
57
57
|
}[] = [];
|
|
58
58
|
|
|
59
59
|
for (let previous of previousFunctions) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
if (!currentFunctionNames.has(getFunctionName(previous))) {
|
|
61
|
+
messages.push({ text: `Removing ${red(debugFunction(previous))}`, order: 3 });
|
|
62
|
+
base[previous.ModuleId].Sources[previous.FunctionId] = undefined;
|
|
63
|
+
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
console.log();
|
|
@@ -78,9 +78,11 @@ export async function replaceFunctions(config: {
|
|
|
78
78
|
base[func.ModuleId].Sources[func.FunctionId] = atomicObjectWrite(func);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
if (proxyWatcher.isAllSynced()) {
|
|
82
|
+
sort(messages, x => -x.order);
|
|
83
|
+
for (let message of messages) {
|
|
84
|
+
console.log(message.text);
|
|
85
|
+
}
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
88
|
});
|
|
@@ -16,6 +16,8 @@ import yargs from "yargs";
|
|
|
16
16
|
import { isPublic } from "../config";
|
|
17
17
|
import { deployBlock } from "./deployBlock";
|
|
18
18
|
import { LOCAL_DOMAIN } from "../0-path-value-core/PathController";
|
|
19
|
+
import { preloadFunctions } from "./pathFunctionLoader";
|
|
20
|
+
import { magenta } from "socket-function/src/formatting/logColors";
|
|
19
21
|
let yargObj = yargs(process.argv)
|
|
20
22
|
.option("domain", { type: "string", required: true, desc: "Domain to deploy to" })
|
|
21
23
|
.option("nogit", { type: "boolean", default: false, desc: `Disable git operations. Useful for debugging, when using --local` })
|
|
@@ -107,7 +109,17 @@ export async function deployMain() {
|
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
console.log();
|
|
113
|
+
console.log();
|
|
114
|
+
console.log(magenta(`Preloading ${currentFunctions.length} functions`));
|
|
115
|
+
await preloadFunctions(currentFunctions);
|
|
116
|
+
|
|
117
|
+
console.log();
|
|
118
|
+
console.log();
|
|
119
|
+
console.log(magenta(`Deploying ${currentFunctions.length} functions`));
|
|
110
120
|
await replaceFunctions({ domainName, functions: currentFunctions, });
|
|
121
|
+
|
|
122
|
+
console.log(magenta(`FINISHED DEPLOY`));
|
|
111
123
|
}
|
|
112
124
|
function getGitURL(gitDir: string) {
|
|
113
125
|
if (yargObj.nogit) return "git@github.com:nogit/nogit.git";
|
|
@@ -5,19 +5,22 @@ import fs from "fs";
|
|
|
5
5
|
import { blue, magenta, red } from "socket-function/src/formatting/logColors";
|
|
6
6
|
import debugbreak from "debugbreak";
|
|
7
7
|
import { cache, lazy } from "socket-function/src/caching";
|
|
8
|
-
import { batchFunction, runInfinitePoll } from "socket-function/src/batching";
|
|
9
|
-
import { logErrors } from "../errors";
|
|
8
|
+
import { batchFunction, delay, runInfinitePoll } from "socket-function/src/batching";
|
|
9
|
+
import { errorToUndefined, logErrors } from "../errors";
|
|
10
10
|
import crypto from "crypto";
|
|
11
11
|
import { MaybePromise } from "socket-function/src/types";
|
|
12
12
|
import yargs from "yargs";
|
|
13
13
|
import { SyncWatcher } from "../2-proxy/PathValueProxyWatcher";
|
|
14
14
|
import { MAX_CHANGE_AGE } from "../0-path-value-core/pathValueCore";
|
|
15
|
-
import { isNode, isNodeTrue } from "socket-function/src/misc";
|
|
15
|
+
import { isNode, isNodeTrue, nextId, timeInSecond } from "socket-function/src/misc";
|
|
16
16
|
import { getPathStr2, getPathStr3 } from "../path";
|
|
17
17
|
import { consistentHash } from "../misc/hash";
|
|
18
18
|
import { setExternalHotReloading } from "socket-function/hot/HotReloadController";
|
|
19
19
|
import { isPublic } from "../config";
|
|
20
20
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
21
|
+
import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
|
|
22
|
+
import { getControllerNodeId, getControllerNodeIdList } from "../-g-core-values/NodeCapabilities";
|
|
23
|
+
import { sha256 } from "js-sha256";
|
|
21
24
|
|
|
22
25
|
// Get localPathRemappings using yargs, so it is easy to configure in multiple entry points
|
|
23
26
|
let yargObj = isNodeTrue() && yargs(process.argv)
|
|
@@ -138,13 +141,31 @@ let moduleResolver = async (spec: FunctionSpec) => {
|
|
|
138
141
|
urlForPath = urlForPath.slice(0, -".git".length);
|
|
139
142
|
}
|
|
140
143
|
let repoPath = getSubFolder("synced_repos") + urlForPath + "/" + spec.gitRef + "/";
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
+
|
|
145
|
+
const lockFolder = getSubFolder("synced_repos_locks");
|
|
146
|
+
let lockPath = lockFolder + sha256(repoPath).slice(0, 16) + ".loadinglock";
|
|
147
|
+
let exists = fs.existsSync(repoPath);
|
|
148
|
+
if (exists) {
|
|
149
|
+
if (!await isRepoCloned(repoPath)) {
|
|
150
|
+
exists = false;
|
|
151
|
+
}
|
|
144
152
|
}
|
|
145
|
-
if (!
|
|
146
|
-
await
|
|
153
|
+
if (!exists) {
|
|
154
|
+
await getFileLock(lockPath, async () => {
|
|
155
|
+
if (fs.existsSync(repoPath) && !await isRepoCloned(repoPath)) {
|
|
156
|
+
// Just move it, and try again
|
|
157
|
+
await fs.promises.rename(repoPath, repoPath + "_" + Date.now());
|
|
158
|
+
}
|
|
159
|
+
if (!fs.existsSync(repoPath)) {
|
|
160
|
+
await executeCommand("git", ["clone", gitURL, repoPath]);
|
|
161
|
+
await executeCommand("git", ["reset", "--hard", spec.gitRef], { cwd: repoPath });
|
|
162
|
+
}
|
|
163
|
+
if (!fs.existsSync(repoPath + "node_modules")) {
|
|
164
|
+
await executeCommand("yarn", ["install"], { cwd: repoPath });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
147
167
|
}
|
|
168
|
+
|
|
148
169
|
let querysubPath = repoPath + "node_modules/querysub";
|
|
149
170
|
if (fs.existsSync(querysubPath)) {
|
|
150
171
|
// By moving querysub, it forces the repo to use the parent querysub. This is nice, as it even
|
|
@@ -157,6 +178,57 @@ let moduleResolver = async (spec: FunctionSpec) => {
|
|
|
157
178
|
return repoPath;
|
|
158
179
|
};
|
|
159
180
|
|
|
181
|
+
const lockHeartbeatInterval = timeInSecond * 15;
|
|
182
|
+
const lockExpiryTime = timeInSecond * 60;
|
|
183
|
+
const lockCheckTime = 500;
|
|
184
|
+
async function tryGetFileLock(file: string) {
|
|
185
|
+
let expiryTime = Date.now() - lockExpiryTime;
|
|
186
|
+
try {
|
|
187
|
+
let lastHeartbeat = Number((await fs.promises.readFile(file, "utf8")).split("-")[0]) || 0;
|
|
188
|
+
if (lastHeartbeat > expiryTime) return false;
|
|
189
|
+
} catch { }
|
|
190
|
+
let heartbeatWrite = Date.now() + "-" + nextId();
|
|
191
|
+
await fs.promises.writeFile(file, heartbeatWrite);
|
|
192
|
+
await delay(lockCheckTime);
|
|
193
|
+
let readBack = await fs.promises.readFile(file, "utf8");
|
|
194
|
+
if (readBack !== heartbeatWrite) return false;
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
async function getFileLock(file: string, callback: () => Promise<void>) {
|
|
198
|
+
console.log(magenta(`Getting file lock: ${file}`));
|
|
199
|
+
while (true) {
|
|
200
|
+
if (await tryGetFileLock(file)) {
|
|
201
|
+
let done = false;
|
|
202
|
+
logErrors((async () => {
|
|
203
|
+
while (!done) {
|
|
204
|
+
await delay(lockHeartbeatInterval);
|
|
205
|
+
console.log(magenta(`Not done with file lock, writing heartbeat: ${file}`));
|
|
206
|
+
await fs.promises.writeFile(file, Date.now() + "-" + nextId());
|
|
207
|
+
}
|
|
208
|
+
})());
|
|
209
|
+
try {
|
|
210
|
+
await callback();
|
|
211
|
+
} finally {
|
|
212
|
+
await fs.promises.unlink(file);
|
|
213
|
+
done = true;
|
|
214
|
+
console.log(magenta(`Releasing file lock: ${file}`));
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
console.log(magenta(`Waiting for lock file lock: ${file}`));
|
|
219
|
+
await delay(lockHeartbeatInterval);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function isRepoCloned(path: string): Promise<boolean> {
|
|
224
|
+
try {
|
|
225
|
+
await executeCommand("git", ["-C", path, "rev-parse", "--is-inside-work-tree"]);
|
|
226
|
+
return true;
|
|
227
|
+
} catch {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
160
232
|
export function isDynamicModule(module: NodeJS.Module): boolean {
|
|
161
233
|
return isDynamicModulePath(module.filename);
|
|
162
234
|
}
|
|
@@ -185,6 +257,7 @@ function getCallstackFiles(): string[] {
|
|
|
185
257
|
async function getModuleFromSpecBase(
|
|
186
258
|
spec: FunctionSpec
|
|
187
259
|
): Promise<NodeJS.Module> {
|
|
260
|
+
everLoadedRepos.add(spec.gitURL);
|
|
188
261
|
let hotReloadPackagePath = "";
|
|
189
262
|
let path = gitURLRefMappings.get(getSpecKey(spec));
|
|
190
263
|
let deployPath = "";
|
|
@@ -333,4 +406,39 @@ async function executeCommand(command: string, args: string[], options?: {
|
|
|
333
406
|
stdio: "inherit",
|
|
334
407
|
cwd: options?.cwd
|
|
335
408
|
});
|
|
336
|
-
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
// Helps us exclude non-function loaders (PathValueServer), which really shouldn't
|
|
413
|
+
// be loading code anyways!
|
|
414
|
+
let everLoadedRepos = new Set<string>();
|
|
415
|
+
|
|
416
|
+
export async function preloadFunctions(specs: FunctionSpec[]) {
|
|
417
|
+
let nodeIds = await getControllerNodeIdList(functionPreloadController);
|
|
418
|
+
await Promise.all(nodeIds.map(async nodeId => {
|
|
419
|
+
let controller = functionPreloadController.nodes[nodeId];
|
|
420
|
+
console.log(blue(`Preloading functions on ${nodeId}`));
|
|
421
|
+
await errorToUndefined(controller.preloadFunctions(specs));
|
|
422
|
+
console.log(blue(`Finished preloading functions on ${nodeId}`));
|
|
423
|
+
}));
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
class FunctionPreloaderBase {
|
|
427
|
+
async preloadFunctions(specs: FunctionSpec[]) {
|
|
428
|
+
specs = specs.filter(spec => everLoadedRepos.has(spec.gitURL));
|
|
429
|
+
for (let spec of specs) {
|
|
430
|
+
await getModuleFromSpec(spec);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const functionPreloadController = SocketFunction.register(
|
|
436
|
+
"FunctionPreloader-c966a1a6-5d3f-4453-bd03-ae84b574ec00",
|
|
437
|
+
new FunctionPreloaderBase(),
|
|
438
|
+
() => ({
|
|
439
|
+
preloadFunctions: {},
|
|
440
|
+
}),
|
|
441
|
+
() => ({
|
|
442
|
+
hooks: [requiresNetworkTrustHook],
|
|
443
|
+
})
|
|
444
|
+
);
|