querysub 0.591.0 → 0.593.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/.claude/settings.local.json +5 -1
- package/bin/stop-machine.js +4 -0
- package/package.json +6 -3
- package/spec.txt +12 -0
- package/src/-a-archives/archiveCache.ts +28 -7
- package/src/-a-archives/archiveCache2.ts +4 -1
- package/src/-a-archives/archives.ts +2 -63
- package/src/-a-archives/archives2.ts +21 -17
- package/src/-a-archives/archivesDisk.ts +2 -13
- package/src/-a-archives/archivesMemoryCache.ts +27 -17
- package/src/-a-archives/archivesMemoryCache2.ts +23 -0
- package/src/-d-trust/NetworkTrust2.ts +3 -3
- package/src/-e-certs/certAuthority.ts +2 -2
- package/src/-f-node-discovery/NodeDiscovery.ts +3 -3
- package/src/0-path-value-core/AuthorityLookup.ts +0 -4
- package/src/0-path-value-core/PathRouter.ts +17 -16
- package/src/0-path-value-core/ShardPrefixes.ts +2 -4
- package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +133 -38
- package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +0 -1
- package/src/0-path-value-core/pathValueArchives.ts +9 -5
- package/src/2-proxy/archiveMoveHarness.ts +1 -1
- package/src/4-deploy/edgeBootstrap.ts +50 -11
- package/src/4-deploy/edgeNodes.ts +7 -13
- package/src/4-querysub/Querysub.ts +2 -2
- package/src/archiveapps/archiveJoinEntry.ts +1 -1
- package/src/config2.ts +1 -7
- package/src/deployManager/components/ServicesListPage.tsx +27 -8
- package/src/deployManager/machineDaemonShared.ts +2 -0
- package/src/deployManager/machineSchema.ts +5 -6
- package/src/deployManager/setupMachineMain.ts +1 -2
- package/src/deployManager/stopMachineMain.ts +45 -0
- package/src/diagnostics/logs/IndexedLogs/IndexedLogs.ts +34 -21
- package/src/diagnostics/logs/IndexedLogs/MCPIndexedLogs.ts +3 -3
- package/src/diagnostics/logs/IndexedLogs/TimeFileTree.ts +11 -2
- package/src/diagnostics/logs/IndexedLogs/moveIndexLogsToPublic.ts +3 -4
- package/src/diagnostics/logs/errorTickets/tickets.ts +3 -4
- package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +2 -4
- package/src/storageSetup.ts +1 -1
- package/src/-a-archives/archivesBackBlaze.ts +0 -156
- package/src/-a-archives/archivesCborT.ts +0 -52
- package/src/-a-archives/archivesLimitedCache.ts +0 -307
- package/src/-a-archives/archivesPrivateFileSystem.ts +0 -326
- package/src/-a-archives/copyLocalToBackblaze.ts +0 -24
- package/src/-b-authorities/cdnAuthority.ts +0 -53
|
@@ -31,7 +31,11 @@
|
|
|
31
31
|
"WebFetch(domain:developers.cloudflare.com)",
|
|
32
32
|
"PowerShell(yarn type *)",
|
|
33
33
|
"PowerShell(Remove-Item -Confirm:$false \"C:\\\\Users\\\\quent\\\\.claude\\\\projects\\\\D--repos-querysub\\\\memory\\\\no-naming-new-bins.md\")",
|
|
34
|
-
"Bash(python -)"
|
|
34
|
+
"Bash(python -)",
|
|
35
|
+
"PowerShell(yarn upreal *)",
|
|
36
|
+
"PowerShell($env:COMPUTERNAME; $env:USERNAME)",
|
|
37
|
+
"PowerShell($env:COMPUTERNAME)",
|
|
38
|
+
"PowerShell($env:USERNAME)"
|
|
35
39
|
]
|
|
36
40
|
}
|
|
37
41
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "querysub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.593.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
"mc2": "yarn typenode ./src/diagnostics/debugger/mcp-server.ts --cwd D:/repos/qs-cyoa/",
|
|
27
27
|
"autofix": "yarn typenode ./src/diagnostics/logs/errorTickets/autoFixerEntry.ts",
|
|
28
28
|
"parameters-timeline": "yarn typenode ./src/deployManager/parametersTimelineMain.ts",
|
|
29
|
+
"setup-machine": "yarn typenode ./src/deployManager/setupMachineMain.ts",
|
|
30
|
+
"stop-machine": "yarn typenode ./src/deployManager/stopMachineMain.ts",
|
|
29
31
|
"ssh-a-claude": "ssh root@a.querysubtest.com"
|
|
30
32
|
},
|
|
31
33
|
"bin": {
|
|
@@ -38,6 +40,7 @@
|
|
|
38
40
|
"machine": "./bin/machine.js",
|
|
39
41
|
"machine-alwaysup": "./bin/machine-alwaysup.js",
|
|
40
42
|
"setup-machine": "./bin/setup-machine.js",
|
|
43
|
+
"stop-machine": "./bin/stop-machine.js",
|
|
41
44
|
"gc": "./bin/gc.js",
|
|
42
45
|
"gc-watch": "./bin/gc-watch.js",
|
|
43
46
|
"gc-watch-public": "./bin/gc-watch-public.js",
|
|
@@ -71,8 +74,8 @@
|
|
|
71
74
|
"node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
|
|
72
75
|
"pako": "^2.1.0",
|
|
73
76
|
"peggy": "^5.0.6",
|
|
74
|
-
"sliftutils": "^1.7.
|
|
75
|
-
"socket-function": "^1.2.
|
|
77
|
+
"sliftutils": "^1.7.72",
|
|
78
|
+
"socket-function": "^1.2.29",
|
|
76
79
|
"terser": "^5.31.0",
|
|
77
80
|
"typenode": "^6.6.1",
|
|
78
81
|
"typesafecss": "^0.32.0",
|
package/spec.txt
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
It could be it's just taking a while to get valid. Let's check it two times to see. Although if it is, that's a bug, but it's probably an easy bug to fix.
|
|
2
|
+
|
|
3
|
+
Wait, what transaction is it applying now? If it's not a valid transaction and we don't even write it, so what?
|
|
4
|
+
|
|
5
|
+
Well, we might have to restore from a snapshot if it breaks it, but I'm pretty confident we're not going to lose all of our data.
|
|
6
|
+
|
|
7
|
+
Ugh... I mean it seems like our storage is just really out of sync. Like, we're reading files and it changes every time, etc.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
1
13
|
Trigger values
|
|
2
14
|
1) validStateComputer.ingestValuesAndValidStates
|
|
3
15
|
1.5) authorityStorage.ingestValues
|
|
@@ -138,11 +138,17 @@ const getDiskMetricsBase = async () => {
|
|
|
138
138
|
// We require our input to be a temp file, so our update can be atomic
|
|
139
139
|
async function addCacheFile(archives: CacheKeySource, key: string, sourceTempFile: string): Promise<void> {
|
|
140
140
|
let path = getArchiveCachePath(archives, key);
|
|
141
|
+
let sourceTempFileStat = await fs.promises.stat(sourceTempFile);
|
|
142
|
+
if (sourceTempFileStat.size === 0) {
|
|
143
|
+
try {
|
|
144
|
+
await fs.promises.unlink(sourceTempFile);
|
|
145
|
+
} catch { }
|
|
146
|
+
throw new Error(`Expected non-empty data when caching ${key}, was 0 bytes. Empty files are treated as deletes by the storage layer, so they must never be written or cached.`);
|
|
147
|
+
}
|
|
141
148
|
let hasFile = fileSizes.some(x => x.path === path);
|
|
142
149
|
if (hasFile) {
|
|
143
150
|
removeFile(path);
|
|
144
151
|
}
|
|
145
|
-
let sourceTempFileStat = await fs.promises.stat(sourceTempFile);
|
|
146
152
|
availableBytes -= sourceTempFileStat.size;
|
|
147
153
|
availableFiles--;
|
|
148
154
|
sort(fileSizes, x => x.time);
|
|
@@ -196,14 +202,19 @@ const getDiskMetricsBase = async () => {
|
|
|
196
202
|
let path = getArchiveCachePath(archives, key);
|
|
197
203
|
let buffer: Buffer | undefined;
|
|
198
204
|
let handle: fs.promises.FileHandle | undefined;
|
|
205
|
+
let isEmptyFile = false;
|
|
199
206
|
try {
|
|
200
207
|
handle = await fs.promises.open(path, "r");
|
|
201
208
|
let stats = await handle.stat();
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
209
|
+
if (stats.size === 0) {
|
|
210
|
+
isEmptyFile = true;
|
|
211
|
+
} else {
|
|
212
|
+
let start = config?.range?.start ?? 0;
|
|
213
|
+
let end = config?.range?.end ?? stats.size;
|
|
214
|
+
let readBuffer = Buffer.alloc(end - start);
|
|
215
|
+
let read = await handle.read(readBuffer, 0, end - start, start);
|
|
216
|
+
buffer = readBuffer.slice(0, read.bytesRead);
|
|
217
|
+
}
|
|
207
218
|
}
|
|
208
219
|
catch { }
|
|
209
220
|
finally {
|
|
@@ -211,6 +222,13 @@ const getDiskMetricsBase = async () => {
|
|
|
211
222
|
await handle.close();
|
|
212
223
|
}
|
|
213
224
|
}
|
|
225
|
+
if (isEmptyFile) {
|
|
226
|
+
removeFile(path);
|
|
227
|
+
try {
|
|
228
|
+
await fs.promises.unlink(path);
|
|
229
|
+
} catch { }
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
214
232
|
if (buffer) {
|
|
215
233
|
updateAccessTime(path);
|
|
216
234
|
bumpAtimeIfStale(path);
|
|
@@ -355,7 +373,9 @@ export function wrapArchivesWithCache(archives: Archives, rootConfig?: {
|
|
|
355
373
|
if (rootConfig?.immutable) {
|
|
356
374
|
try {
|
|
357
375
|
let stat = await fs.promises.stat(getArchiveCachePath(archives, fileName));
|
|
358
|
-
|
|
376
|
+
if (stat.size > 0) {
|
|
377
|
+
return { writeTime: stat.mtimeMs, size: stat.size };
|
|
378
|
+
}
|
|
359
379
|
} catch {
|
|
360
380
|
// Not in our cache yet — fall through to the source.
|
|
361
381
|
}
|
|
@@ -363,6 +383,7 @@ export function wrapArchivesWithCache(archives: Archives, rootConfig?: {
|
|
|
363
383
|
return archives.getInfo(fileName);
|
|
364
384
|
},
|
|
365
385
|
set: async (fileName: string, data: Buffer) => {
|
|
386
|
+
if (data.length === 0) throw new Error(`Expected non-empty data when setting ${fileName}, was 0 bytes. Empty writes are treated as deletes by the storage layer.`);
|
|
366
387
|
let metrics = await getDiskMetrics();
|
|
367
388
|
const tempPath = getTempFilePath();
|
|
368
389
|
await fs.promises.writeFile(tempPath, data);
|
|
@@ -127,7 +127,9 @@ export function wrapArchivesWithCache2(archives: IArchives, rootConfig?: {
|
|
|
127
127
|
if (rootConfig?.immutable) {
|
|
128
128
|
try {
|
|
129
129
|
let stat = await fs.promises.stat(getArchiveCachePath(archives, fileName));
|
|
130
|
-
|
|
130
|
+
if (stat.size > 0) {
|
|
131
|
+
return { writeTime: stat.mtimeMs, size: stat.size };
|
|
132
|
+
}
|
|
131
133
|
} catch {
|
|
132
134
|
// Not in our cache yet — fall through to the source.
|
|
133
135
|
}
|
|
@@ -135,6 +137,7 @@ export function wrapArchivesWithCache2(archives: IArchives, rootConfig?: {
|
|
|
135
137
|
return archives.getInfo(fileName);
|
|
136
138
|
},
|
|
137
139
|
set: async (fileName: string, data: Buffer, config?: SetConfig) => {
|
|
140
|
+
if (data.length === 0) throw new Error(`Expected non-empty data when setting ${fileName}, was 0 bytes. Empty writes are treated as deletes by the storage layer.`);
|
|
138
141
|
let writtenKey = await archives.set(fileName, data, config);
|
|
139
142
|
// VARIABLE_SHARD keys materialize into a different key on write, in which case
|
|
140
143
|
// fileName isn't the readable key, so caching under it would be wrong.
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
import { isNode } from "socket-function/src/misc";
|
|
2
|
-
import { getDomain, isNoNetwork } from "../config";
|
|
3
|
-
import { getStorageDir, getSubFolder } from "../fs";
|
|
4
|
-
import { getArchivesBackblaze, hasBackblazePermissions } from "./archivesBackBlaze";
|
|
5
|
-
import { getArchivesLocal } from "./archivesDisk";
|
|
6
|
-
import { magenta } from "socket-function/src/formatting/logColors";
|
|
7
|
-
import debugbreak from "debugbreak";
|
|
8
|
-
import { isClient } from "../config2";
|
|
9
|
-
import { wrapArchivesWithCache } from "./archiveCache";
|
|
10
1
|
import { Args } from "socket-function/src/types";
|
|
11
|
-
import { IArchives } from "sliftutils/storage/IArchives";
|
|
12
2
|
|
|
13
3
|
export interface Archives {
|
|
14
4
|
getDebugName(): string;
|
|
@@ -105,8 +95,8 @@ export function createArchivesOverride<T extends Partial<Archives>>(
|
|
|
105
95
|
};
|
|
106
96
|
}
|
|
107
97
|
|
|
108
|
-
export function nestArchives(path: string, archives: Archives): Archives
|
|
109
|
-
if (!path) return archives
|
|
98
|
+
export function nestArchives(path: string, archives: Archives): Archives {
|
|
99
|
+
if (!path) return archives;
|
|
110
100
|
if (!path.endsWith("/")) {
|
|
111
101
|
path = path + "/";
|
|
112
102
|
}
|
|
@@ -116,7 +106,6 @@ export function nestArchives(path: string, archives: Archives): Archives & IArch
|
|
|
116
106
|
}
|
|
117
107
|
return file;
|
|
118
108
|
}
|
|
119
|
-
// NOTE: Just ignore the errors in here. We're going to be getting rid of this soon enough.
|
|
120
109
|
return {
|
|
121
110
|
getDebugName: () => archives.getDebugName() + "/" + path,
|
|
122
111
|
get: (fileName: string, config) => archives.get(path + stripFilePrefix(fileName), config),
|
|
@@ -153,53 +142,3 @@ export function nestArchives(path: string, archives: Archives): Archives & IArch
|
|
|
153
142
|
};
|
|
154
143
|
}
|
|
155
144
|
|
|
156
|
-
let archivesLogging = false;
|
|
157
|
-
export function enableArchivesLogging() {
|
|
158
|
-
archivesLogging = true;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
let cached = new Map<string, Archives>();
|
|
162
|
-
export function getArchives(rootPath = ""): Archives {
|
|
163
|
-
let value = cached.get(rootPath);
|
|
164
|
-
if (!value) {
|
|
165
|
-
value = getArchivesBase(rootPath);
|
|
166
|
-
cached.set(rootPath, value);
|
|
167
|
-
}
|
|
168
|
-
return value;
|
|
169
|
-
}
|
|
170
|
-
export function getArchivesBase(rootPath = ""): Archives {
|
|
171
|
-
let archives: Archives;
|
|
172
|
-
let domain = getDomain();
|
|
173
|
-
if (isNoNetwork()) {
|
|
174
|
-
archives = getArchivesLocal(domain);
|
|
175
|
-
archives = nestArchives(rootPath, archives);
|
|
176
|
-
} else {
|
|
177
|
-
archives = getArchivesBackblaze(domain);
|
|
178
|
-
archives = nestArchives(rootPath, archives);
|
|
179
|
-
// Why did we think we should always cache? We absolutely should not. Even if we were checking
|
|
180
|
-
// file hashes on every read (which we do not), it would still potentially fill the cache up
|
|
181
|
-
// with large temporary files we have no intention of using (which might evict entries we
|
|
182
|
-
// intend to use).
|
|
183
|
-
//archives = wrapArchivesWithCache(archives);
|
|
184
|
-
}
|
|
185
|
-
if (archivesLogging) {
|
|
186
|
-
archives.enableLogging();
|
|
187
|
-
}
|
|
188
|
-
return archives;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export function hasArchivesPermissions() {
|
|
192
|
-
if (isNoNetwork()) {
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
return hasBackblazePermissions();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (isNode()) {
|
|
199
|
-
if (isNoNetwork()) {
|
|
200
|
-
console.log("Primary data storage: " + magenta(getStorageDir()));
|
|
201
|
-
} else {
|
|
202
|
-
console.log("Config storage path: " + magenta(getStorageDir()));
|
|
203
|
-
console.log("Primary data storage: " + magenta(`backblaze:${getDomain()}`));
|
|
204
|
-
}
|
|
205
|
-
}
|
|
@@ -4,6 +4,7 @@ import { cache, cacheJSONArgsEqual } from "socket-function/src/caching";
|
|
|
4
4
|
import { formatDateTime } from "socket-function/src/formatting/format";
|
|
5
5
|
import { getDomain } from "../config";
|
|
6
6
|
import { timeInMinute } from "socket-function/src/misc";
|
|
7
|
+
import { FindConfig } from "sliftutils/storage/IArchives";
|
|
7
8
|
|
|
8
9
|
export const STORAGE_ACCOUNT = "root";
|
|
9
10
|
|
|
@@ -44,24 +45,27 @@ function archiveBuilder(bucket: string, overrides: Partial<RemoteConfigBase>) {
|
|
|
44
45
|
const ONTARIO = `https://99-250-124-91.querysubtest.com:5234/file/${STORAGE_ACCOUNT}/${bucket}/storage/storagerouting.json`;
|
|
45
46
|
const HETZNER = `https://65-109-93-113.querysubtest.com:5234/file/${STORAGE_ACCOUNT}/${bucket}/storage/storagerouting.json`;
|
|
46
47
|
const BACKBLAZE = `https://f002.backblazeb2.com/file/${bucket}/storage/storagerouting.json`;
|
|
48
|
+
|
|
49
|
+
function stripeSources(urls: string[]) {
|
|
50
|
+
let sources: Omit<Exclude<RemoteConfigBase, string>, "validWindow">[] = [];
|
|
51
|
+
for (let j = 0; j < urls.length; j++) {
|
|
52
|
+
// Shift by j
|
|
53
|
+
let currentUrls = urls.slice(j).concat(urls.slice(0, j));
|
|
54
|
+
for (let i = 0; i < urls.length; i++) {
|
|
55
|
+
sources.push({ type: "remote", url: currentUrls[i], route: [i / urls.length, (i + 1) / urls.length], });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return sources;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
47
62
|
let sources = createSourceWindows(overrides, [
|
|
48
63
|
{
|
|
49
|
-
startTime: +new Date("2026-07-20
|
|
50
|
-
sources: [
|
|
51
|
-
{ type: "remote", url: ONTARIO, route: [0.5, 1], },
|
|
52
|
-
{ type: "remote", url: HETZNER, route: [0, 0.5], },
|
|
53
|
-
{ type: "remote", url: ONTARIO, },
|
|
54
|
-
{ type: "remote", url: HETZNER, },
|
|
55
|
-
{ type: "backblaze", url: BACKBLAZE, },
|
|
56
|
-
]
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
startTime: +new Date("2026-07-21 02:59:00-04:00"),
|
|
64
|
+
startTime: +new Date("2026-07-20 00:00:00-04:00"),
|
|
60
65
|
sources: [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
{ type: "remote", url: HETZNER, },
|
|
66
|
+
// NOTE: Once we add new sources, we need to add something at a new start time in the future so we have time to move the files.
|
|
67
|
+
// TODO: EVENTUALLY, Once we have enough data, instead of restriping, we need to bisect the data. That way we can slowly move over the data instead of trying to rewrite all of our data all at once. However, for the moment, moving all the data is probably fine. On the data center, we should be able to move terabytes of data every hour, so we should be able to handle quite a bit, especially if we have a few days to run the restriping.
|
|
68
|
+
...stripeSources([ONTARIO, HETZNER]),
|
|
65
69
|
{ type: "backblaze", url: BACKBLAZE, },
|
|
66
70
|
]
|
|
67
71
|
},
|
|
@@ -89,8 +93,8 @@ function nestBucket(folder: string, archives: ReturnType<typeof createArchives>)
|
|
|
89
93
|
get: (path: string, config?: GetConfig) => archives.get(folder + path, config),
|
|
90
94
|
set: (path: string, data: Buffer, config?: SetConfig) => archives.set(folder + path, data, config),
|
|
91
95
|
del: (path: string) => archives.del(folder + path),
|
|
92
|
-
find: async (prefix: string, config?:
|
|
93
|
-
findInfo: async (prefix: string, config?:
|
|
96
|
+
find: async (prefix: string, config?: FindConfig) => (await archives.find(folder + prefix, config)).map(x => x.slice(folder.length)),
|
|
97
|
+
findInfo: async (prefix: string, config?: FindConfig) => (await archives.findInfo(folder + prefix, config)).map(x => ({ ...x, path: x.path.slice(folder.length) })),
|
|
94
98
|
get2: (path: string, config?: GetConfig) => archives.get2(folder + path, config),
|
|
95
99
|
setLargeFile: (config: { path: string; lastModified?: number; getNextData(): Promise<Buffer | undefined>; }) => archives.setLargeFile({ ...config, path: folder + config.path }),
|
|
96
100
|
getInfo: (path: string) => archives.getInfo(folder + path),
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { isNode } from "socket-function/src/misc";
|
|
2
2
|
import os from "os";
|
|
3
|
-
import { fsExistsAsync
|
|
3
|
+
import { fsExistsAsync } from "../fs";
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import { blue, red, yellow } from "socket-function/src/formatting/logColors";
|
|
6
6
|
import { measureFnc } from "socket-function/src/profiling/measure";
|
|
7
7
|
import { delay } from "socket-function/src/batching";
|
|
8
8
|
import debugbreak from "debugbreak";
|
|
9
|
-
import { Archives
|
|
9
|
+
import { Archives } from "./archives";
|
|
10
10
|
import { cache, lazy } from "socket-function/src/caching";
|
|
11
11
|
import { isDefined } from "../misc";
|
|
12
12
|
|
|
@@ -433,17 +433,6 @@ function unescapeFileName(fileName: string): string {
|
|
|
433
433
|
return fileName;
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
-
export const getArchivesLocal = cache((domain: string): Archives => {
|
|
437
|
-
|
|
438
|
-
const archivesLocal = new ArchivesDisk();
|
|
439
|
-
|
|
440
|
-
if (isNode()) {
|
|
441
|
-
archivesLocal.LOCAL_ARCHIVE_FOLDER = getSubFolder(domain).replaceAll("\\", "/");
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
return archivesLocal;
|
|
445
|
-
});
|
|
446
|
-
|
|
447
436
|
export const getArchivesHome = cache((domain: string): Archives => {
|
|
448
437
|
const archivesLocal = new ArchivesDisk();
|
|
449
438
|
|
|
@@ -24,21 +24,29 @@ export function testDisableCache() {
|
|
|
24
24
|
cacheDisabled = true;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export type MemoryCacheConfig = {
|
|
28
|
+
extraReadSize?: number;
|
|
29
|
+
maxSize?: number;
|
|
30
|
+
maxCount?: number;
|
|
31
|
+
// If the files are guaranteed to be immutable, then all reads, including reading entire file and reading any range, can be cached.
|
|
32
|
+
// - Otherwise, we can't cache when the entire file is read, as there might be further appends after it.
|
|
33
|
+
// - Also, it means if there's a range read that happens, if that range read doesn't return enough bytes, for example, we try to read from 0 to 100, but we only get 50 bytes back, we can only cache the 0 to 50, and then if they read from 0 to 100 again, we can use the cache, but we have to also read 50 to 100 from the source, because the file might have gotten larger.
|
|
34
|
+
fullyImmutable?: boolean;
|
|
35
|
+
stats?: ArchivesMemoryCacheStats;
|
|
36
|
+
sizeCache?: Map<string, number>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** The operations the cache actually needs from its source, so any archives flavor (old Archives or IArchives) can be cached. */
|
|
40
|
+
export type MemoryCacheSource = {
|
|
41
|
+
get(path: string, config?: { range?: { start: number; end: number; } }): Promise<Buffer | undefined>;
|
|
42
|
+
getInfo(path: string): Promise<{ size: number } | undefined>;
|
|
43
|
+
};
|
|
44
|
+
|
|
27
45
|
/** Creates an in-memory cache for the archives. We expect that the values will only have values appended to them or be deleted. Also, if a value is deleted, we won't erase our in-memory cache, and so we will still allow reading from deleted values. */
|
|
28
|
-
export function
|
|
29
|
-
archives:
|
|
30
|
-
config?:
|
|
31
|
-
|
|
32
|
-
maxSize?: number;
|
|
33
|
-
maxCount?: number;
|
|
34
|
-
// If the files are guaranteed to be immutable, then all reads, including reading entire file and reading any range, can be cached.
|
|
35
|
-
// - Otherwise, we can't cache when the entire file is read, as there might be further appends after it.
|
|
36
|
-
// - Also, it means if there's a range read that happens, if that range read doesn't return enough bytes, for example, we try to read from 0 to 100, but we only get 50 bytes back, we can only cache the 0 to 50, and then if they read from 0 to 100 again, we can use the cache, but we have to also read 50 to 100 from the source, because the file might have gotten larger.
|
|
37
|
-
fullyImmutable?: boolean;
|
|
38
|
-
stats?: ArchivesMemoryCacheStats;
|
|
39
|
-
sizeCache?: Map<string, number>;
|
|
40
|
-
}
|
|
41
|
-
): Archives {
|
|
46
|
+
export function createMemoryCachedGet(
|
|
47
|
+
archives: MemoryCacheSource,
|
|
48
|
+
config?: MemoryCacheConfig
|
|
49
|
+
) {
|
|
42
50
|
let {
|
|
43
51
|
maxSize = 1024 * 1024 * 1024 * 4,
|
|
44
52
|
maxCount = 1000 * 1000,
|
|
@@ -221,8 +229,6 @@ export function createArchivesMemoryCache(
|
|
|
221
229
|
|
|
222
230
|
async function cachedGet(path: string, getConfig?: {
|
|
223
231
|
range?: { start: number; end: number; };
|
|
224
|
-
retryCount?: number;
|
|
225
|
-
fastRead?: boolean;
|
|
226
232
|
}): Promise<Buffer | undefined> {
|
|
227
233
|
if (cacheDisabled) {
|
|
228
234
|
let result = await archives.get(path, getConfig);
|
|
@@ -374,7 +380,11 @@ export function createArchivesMemoryCache(
|
|
|
374
380
|
return result;
|
|
375
381
|
}
|
|
376
382
|
|
|
383
|
+
return cachedGet;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function createArchivesMemoryCache(archives: Archives, config?: MemoryCacheConfig): Archives {
|
|
377
387
|
return createArchivesOverride(archives, {
|
|
378
|
-
get:
|
|
388
|
+
get: createMemoryCachedGet(archives, config),
|
|
379
389
|
});
|
|
380
390
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { IArchives } from "sliftutils/storage/IArchives";
|
|
2
|
+
import { createMemoryCachedGet, MemoryCacheConfig } from "./archivesMemoryCache";
|
|
3
|
+
|
|
4
|
+
/** See createMemoryCachedGet — the same in-memory range cache, wrapping an IArchives (archives2). */
|
|
5
|
+
export function createArchivesMemoryCache2(archives: IArchives, config?: MemoryCacheConfig): IArchives {
|
|
6
|
+
let cachedGet = createMemoryCachedGet(archives, config);
|
|
7
|
+
return {
|
|
8
|
+
get: cachedGet,
|
|
9
|
+
getDebugName: () => archives.getDebugName(),
|
|
10
|
+
hasWriteAccess: () => archives.hasWriteAccess(),
|
|
11
|
+
get2: (fileName, getConfig) => archives.get2(fileName, getConfig),
|
|
12
|
+
set: (fileName, data, setConfig) => archives.set(fileName, data, setConfig),
|
|
13
|
+
del: (fileName) => archives.del(fileName),
|
|
14
|
+
setLargeFile: (largeConfig) => archives.setLargeFile(largeConfig),
|
|
15
|
+
getInfo: (fileName) => archives.getInfo(fileName),
|
|
16
|
+
find: (prefix, findConfig) => archives.find(prefix, findConfig),
|
|
17
|
+
findInfo: (prefix, findConfig) => archives.findInfo(prefix, findConfig),
|
|
18
|
+
getURL: (path) => archives.getURL(path),
|
|
19
|
+
getConfig: () => archives.getConfig(),
|
|
20
|
+
getChangesAfter2: (changesConfig) => archives.getChangesAfter2(changesConfig),
|
|
21
|
+
getSyncStatus: archives.getSyncStatus && (() => archives.getSyncStatus!()),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { measureWrap } from "socket-function/src/profiling/measure";
|
|
2
2
|
import { getIdentityCA, getMachineId, getOwnMachineId } from "sliftutils/misc/https/certs";
|
|
3
|
-
import {
|
|
3
|
+
import { getArchives2 } from "../-a-archives/archives2";
|
|
4
4
|
import { isNode, throttleFunction, timeInHour, timeInSecond } from "socket-function/src/misc";
|
|
5
5
|
import { SocketFunctionHook } from "socket-function/SocketFunctionTypes";
|
|
6
6
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
@@ -21,7 +21,7 @@ import { magenta } from "socket-function/src/formatting/logColors";
|
|
|
21
21
|
const UNTRUST_CACHE_TIME = 30 * timeInSecond;
|
|
22
22
|
const TRUSTED_CACHE_RESET_INTERVAL = timeInHour;
|
|
23
23
|
|
|
24
|
-
const archives = lazy(() =>
|
|
24
|
+
const archives = lazy(() => getArchives2("trust2/"));
|
|
25
25
|
|
|
26
26
|
export const requiresNetworkTrustHook: SocketFunctionHook = async config => {
|
|
27
27
|
// HACK: On the clientside we strip the domain process and machine id, so we can no longer determine
|
|
@@ -87,7 +87,7 @@ export const isTrusted = measureWrap(async function isTrusted(machineId: string)
|
|
|
87
87
|
}
|
|
88
88
|
});
|
|
89
89
|
let populateTrustedCache = lazy(async () => {
|
|
90
|
-
let trustedMachineIds = await archives().find("");
|
|
90
|
+
let trustedMachineIds = await archives().find("", { fallbacks: true });
|
|
91
91
|
lastArchivesTrusted = trustedMachineIds.slice();
|
|
92
92
|
for (let trustedMachineId of trustedMachineIds) {
|
|
93
93
|
trustedCache.add(trustedMachineId);
|
|
@@ -5,13 +5,13 @@ import { cache, lazy } from "socket-function/src/caching";
|
|
|
5
5
|
import { hasDNSWritePermissions, setRecord } from "../-b-authorities/dnsAuthority";
|
|
6
6
|
import { magenta } from "socket-function/src/formatting/logColors";
|
|
7
7
|
import { createKeyStore } from "../persistentLocalStore";
|
|
8
|
-
import {
|
|
8
|
+
import { getArchives2 } from "../-a-archives/archives2";
|
|
9
9
|
import { isNoNetwork } from "../config";
|
|
10
10
|
import { formatDateTime, formatTime } from "socket-function/src/formatting/format";
|
|
11
11
|
import { delay } from "socket-function/src/batching";
|
|
12
12
|
import { timeInMinute } from "socket-function/src/misc";
|
|
13
13
|
|
|
14
|
-
const archives = lazy(() =>
|
|
14
|
+
const archives = lazy(() => getArchives2(`https_certs_3/`));
|
|
15
15
|
// Expire EXPIRATION_THRESHOLD% of the way through the certificate's lifetime
|
|
16
16
|
const EXPIRATION_THRESHOLD = 0.4;
|
|
17
17
|
// Let's Encrypt only accepts RSA or ECDSA account keys (not Ed25519, which is all sliftutils/certs generates).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
2
|
-
import {
|
|
2
|
+
import { getArchives2 } from "../-a-archives/archives2";
|
|
3
3
|
import { getDomain, isDevDebugbreak, isNoNetwork, isPublic } from "../config";
|
|
4
4
|
import { measureBlock } from "socket-function/src/profiling/measure";
|
|
5
5
|
import { isNode, keyByArray, sha256Hash, throttleFunction, timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
@@ -56,7 +56,7 @@ let DEAD_NODE_POLL_COOLDOWN = timeInMinute * 5;
|
|
|
56
56
|
|
|
57
57
|
let shutdown = false;
|
|
58
58
|
|
|
59
|
-
const archives = lazy(() =>
|
|
59
|
+
const archives = lazy(() => getArchives2("nodes/"));
|
|
60
60
|
|
|
61
61
|
let logging = true;
|
|
62
62
|
export function enableNodeDiscoveryLogging() {
|
|
@@ -342,7 +342,7 @@ async function syncArchives() {
|
|
|
342
342
|
if (isServer()) {
|
|
343
343
|
// Make sure we are present
|
|
344
344
|
await writeHeartbeat();
|
|
345
|
-
let nodeIds = await archives().find("");
|
|
345
|
+
let nodeIds = await archives().find("", { fallbacks: true });
|
|
346
346
|
console.info(`Synced node ids from archives`, { nodeIds });
|
|
347
347
|
await setNodeIds(nodeIds);
|
|
348
348
|
} else {
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import { sort, timeInMinute, timeInSecond } from "socket-function/src/misc";
|
|
2
|
-
import { nestArchives } from "../-a-archives/archives";
|
|
3
|
-
import { getArchivesBackblaze } from "../-a-archives/archivesBackBlaze";
|
|
4
|
-
import { archiveJSONT } from "../-a-archives/archivesJSONT";
|
|
5
|
-
import { getDomain, isPublic } from "../config";
|
|
6
2
|
import { cache, lazy } from "socket-function/src/caching";
|
|
7
3
|
import { SocketFunction } from "socket-function/SocketFunction";
|
|
8
4
|
import { delay, runInSerial, runInfinitePollCallAtStart } from "socket-function/src/batching";
|
|
@@ -13,6 +13,7 @@ import { sha256 } from "js-sha256";
|
|
|
13
13
|
import { rangesOverlap, removeRange } from "../rangeMath";
|
|
14
14
|
import { decodeParentFilter } from "./hackedPackedPathParentFiltering";
|
|
15
15
|
import { getBufferInt } from "socket-function/src/bits";
|
|
16
|
+
import { safeLoop } from "socket-function/src/batching";
|
|
16
17
|
|
|
17
18
|
import { LOCAL_DOMAIN, LOCAL_DOMAIN_PATH } from "./PathRouterConstants";
|
|
18
19
|
export { LOCAL_DOMAIN, LOCAL_DOMAIN_PATH };
|
|
@@ -320,7 +321,7 @@ export class PathRouter {
|
|
|
320
321
|
// NOTE: Encodes all the data, even if it matches our spec or not. If you don't want this, you have to filter first
|
|
321
322
|
// - If this becomes an issue we COULD filter, as we can do it quickly, but I don't think it is required, as all the present usecases prefilter anyways.
|
|
322
323
|
@measureFnc
|
|
323
|
-
public static getPathIdentifierTargets(values: PathValue[], ourSpec: AuthoritySpec): Map<string, PathValue[]
|
|
324
|
+
public static async getPathIdentifierTargets(values: PathValue[], ourSpec: AuthoritySpec): Promise<Map<string, PathValue[]>> {
|
|
324
325
|
// NOTE: The file size limit is 1024 bytes. But we also have our folder, etc, so we want to add enough buffer
|
|
325
326
|
// - Shorter hashes means we can store more, but there's a point when the collisions make it less useful.
|
|
326
327
|
const MAX_PREFIXES_PER_FILE = 50;
|
|
@@ -333,17 +334,17 @@ export class PathRouter {
|
|
|
333
334
|
let prefixes = ourSpec.prefixes.slice();
|
|
334
335
|
sort(prefixes, x => x.prefixPartLength);
|
|
335
336
|
|
|
336
|
-
// NOTE: If there are few enough path values for a prefix, we don't even need to calculate the routing hash.
|
|
337
|
+
// NOTE: If there are few enough path values for a prefix, we don't even need to calculate the routing hash.
|
|
337
338
|
let byPrefix = new Map<PrefixMatcher | undefined, PathValue[]>();
|
|
338
|
-
|
|
339
|
+
await safeLoop({ data: values, name: "PathRouter.getPathIdentifierTargets|groupByPrefix" }, value => {
|
|
339
340
|
let prefix = getMatchingPrefix(ourSpec, value.path);
|
|
340
|
-
let
|
|
341
|
-
if (!
|
|
342
|
-
|
|
343
|
-
byPrefix.set(prefix,
|
|
341
|
+
let prefixValues = byPrefix.get(prefix);
|
|
342
|
+
if (!prefixValues) {
|
|
343
|
+
prefixValues = [];
|
|
344
|
+
byPrefix.set(prefix, prefixValues);
|
|
344
345
|
}
|
|
345
|
-
|
|
346
|
-
}
|
|
346
|
+
prefixValues.push(value);
|
|
347
|
+
});
|
|
347
348
|
|
|
348
349
|
let prefixGroups = Array.from(byPrefix.entries()).map(([prefix, values]) => ({
|
|
349
350
|
prefix,
|
|
@@ -391,17 +392,17 @@ export class PathRouter {
|
|
|
391
392
|
}
|
|
392
393
|
|
|
393
394
|
let finalFiles = new Map<string, PathValue[]>();
|
|
394
|
-
|
|
395
|
+
await safeLoop({ data: groups, name: "PathRouter.getPathIdentifierTargets|shardGroups" }, async group => {
|
|
395
396
|
if (group.count < SHARD_THRESHOLD) {
|
|
396
397
|
let identifier = this.encodeIdentifier({ prefixes: group.prefixes, rangeStart: 0, rangeEnd: 1 });
|
|
397
398
|
finalFiles.set(identifier, group.values.flat());
|
|
398
|
-
|
|
399
|
+
return;
|
|
399
400
|
}
|
|
400
401
|
// Split by routing hash
|
|
401
|
-
let
|
|
402
|
-
let splitCount = Math.max(MIN_SHARD_FILE_COUNT, Math.ceil(
|
|
402
|
+
let groupValues = group.values.flat();
|
|
403
|
+
let splitCount = Math.max(MIN_SHARD_FILE_COUNT, Math.ceil(groupValues.length / TARGET_SHARD_SIZE));
|
|
403
404
|
let byRouteGroup = new Map<number, PathValue[]>();
|
|
404
|
-
|
|
405
|
+
await safeLoop({ data: groupValues, name: "PathRouter.getPathIdentifierTargets|routeValues" }, value => {
|
|
405
406
|
let route = this.getRouteFull({
|
|
406
407
|
path: value.path,
|
|
407
408
|
spec: {
|
|
@@ -418,14 +419,14 @@ export class PathRouter {
|
|
|
418
419
|
byRouteGroup.set(routeIndex, routeValues);
|
|
419
420
|
}
|
|
420
421
|
routeValues.push(value);
|
|
421
|
-
}
|
|
422
|
+
});
|
|
422
423
|
for (let [routeIndex, routeValues] of byRouteGroup) {
|
|
423
424
|
let rangeStart = routeIndex / splitCount;
|
|
424
425
|
let rangeEnd = (routeIndex + 1) / splitCount;
|
|
425
426
|
let identifier = this.encodeIdentifier({ prefixes: group.prefixes, rangeStart, rangeEnd });
|
|
426
427
|
finalFiles.set(identifier, routeValues);
|
|
427
428
|
}
|
|
428
|
-
}
|
|
429
|
+
});
|
|
429
430
|
|
|
430
431
|
// NOTE: There could be a huge number of prefixes and we can't pack them all into one file because of the prefix limit, so this will write any remaining values.
|
|
431
432
|
if (remainingValues.length > 0) {
|
|
@@ -10,10 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { isNode } from "typesafecss";
|
|
13
|
-
import {
|
|
14
|
-
import { getArchivesBackblaze } from "../-a-archives/archivesBackBlaze";
|
|
13
|
+
import { getArchives2 } from "../-a-archives/archives2";
|
|
15
14
|
import { archiveJSONT } from "../-a-archives/archivesJSONT";
|
|
16
|
-
import { getDomain } from "../config";
|
|
17
15
|
import { sort } from "socket-function/src/misc";
|
|
18
16
|
import { getPathFromStr, getPathStr } from "../path";
|
|
19
17
|
import { green, red } from "socket-function/src/formatting/logColors";
|
|
@@ -22,7 +20,7 @@ type PrefixObj = {
|
|
|
22
20
|
prefixes: string[];
|
|
23
21
|
};
|
|
24
22
|
|
|
25
|
-
let prefixes = archiveJSONT<PrefixObj>(() =>
|
|
23
|
+
let prefixes = archiveJSONT<PrefixObj>(() => getArchives2("shard-prefixes/"));
|
|
26
24
|
// We don't actually need a collection, we only need a single value. However, the collection interface is a lot more convenient than manually dealing with backplays files.
|
|
27
25
|
const key = "all";
|
|
28
26
|
|