querysub 0.451.0 → 0.453.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.
@@ -1,35 +1,50 @@
1
- import { getStorageDir, getSubFolder } from "../fs";
2
1
  import { Archives, createArchivesOverride } from "./archives";
3
2
  import fs from "fs";
4
3
  import os from "os";
5
4
 
6
- import { list, nextId, timeInHour, timeInMinute } from "socket-function/src/misc";
5
+ import { nextId, sort, timeInHour, timeInMinute } from "socket-function/src/misc";
7
6
  import { cache, lazy } from "socket-function/src/caching";
8
7
  import { runInParallel, runInSerial, runInfinitePoll } from "socket-function/src/batching";
9
8
  import { sha256 } from "js-sha256";
10
- import child_process from "child_process";
11
- import { getPPID } from "../misc/getParentProcessId";
12
9
  import { Args } from "socket-function/src/types";
13
- import { getArchivesBackblaze } from "./archivesBackBlaze";
14
- import { formatNumber } from "socket-function/src/formatting/format";
15
10
  import { SizeLimiter } from "../diagnostics/SizeLimiter";
16
11
  import { isPublic } from "../config";
17
- import { measureWrap } from "socket-function/src/profiling/measure";
18
12
 
19
- const SIZE_LIMIT = new SizeLimiter({
20
- diskRoot: getStorageDir(),
13
+ const SIZE_LIMIT = lazy(() => new SizeLimiter({
14
+ diskRoot: getCacheDir(),
21
15
  maxBytes: isPublic() ? 1024 * 1024 * 1024 * 250 : 1024 * 1024 * 1024 * 100,
22
16
  // Anything less than this and we can't even load enough weights models for a single task
23
17
  minBytes: 1024 * 1024 * 1024 * 8,
24
18
  maxDiskFraction: 0.3,
25
- maxTotalDiskFraction: 0.95,
19
+ // Add margin, as multiple processes don't sync the cache state often.
20
+ maxTotalDiskFraction: 0.75,
26
21
  maxFiles: 1000 * 25,
27
- });
22
+ }));
28
23
 
29
24
  const UPDATE_METRICS_INTERVAL = timeInMinute * 30;
30
- const cacheArchives2 = getSubFolder("cache");
25
+ const ATIME_BUMP_INTERVAL = timeInMinute * 5;
26
+
27
+ const lastAtimeBump = new Map<string, number>();
28
+ function bumpAtimeIfStale(path: string) {
29
+ let now = Date.now();
30
+ let last = lastAtimeBump.get(path) ?? 0;
31
+ if (now - last < ATIME_BUMP_INTERVAL) return;
32
+ lastAtimeBump.set(path, now);
33
+ let when = new Date(now);
34
+ fs.promises.utimes(path, when, when).catch((e: any) => {
35
+ if (e?.code === "ENOENT") return;
36
+ console.warn(`Failed to bump atime for ${path}:`, (e as Error).stack ?? e);
37
+ });
38
+ }
39
+
40
+ const getCacheDir = lazy(() => {
41
+ let dir = os.homedir().replaceAll("\\", "/") + "/.querysub-cache/";
42
+ if (!fs.existsSync(dir)) {
43
+ fs.mkdirSync(dir, { recursive: true });
44
+ }
45
+ return dir;
46
+ });
31
47
 
32
- const LOCK_SUFFIX = ".lock";
33
48
  const TEMP_SUFFIX = ".tmp";
34
49
  const TEMP_THRESHOLD = timeInHour * 3;
35
50
 
@@ -47,7 +62,7 @@ export function getArchiveCachePath(archives: Archives, key: string): string {
47
62
  let name = fullFileName.replace(/[<>:"\/\\|?*\x00-\x1F]/g, "_");
48
63
  // Remove spaces, as I think they are causing some issues if they are at the end
49
64
  name = name.replaceAll(" ", "");
50
- return cacheArchives2 + hash.slice(0, 16) + "." + name.slice(0, 128) + CACHE_SUFFIX;
65
+ return getCacheDir() + hash.slice(0, 16) + "." + name.slice(0, 128) + CACHE_SUFFIX;
51
66
  }
52
67
 
53
68
  const getDiskMetricsBase = async () => {
@@ -61,19 +76,9 @@ const getDiskMetricsBase = async () => {
61
76
 
62
77
  let usedCacheBytes = 0;
63
78
  let usedCacheFiles = 0;
64
- let cacheFiles = await fs.promises.readdir(cacheArchives2);
79
+ let cacheFiles = await fs.promises.readdir(getCacheDir());
65
80
  async function processFile(file: string) {
66
- if (file.endsWith(LOCK_SUFFIX)) {
67
- let base = file.slice(0, -LOCK_SUFFIX.length);
68
- if (!fs.existsSync(base) && !await isLocked(cacheArchives2 + base)) {
69
- try {
70
- // NOTE: This races, it might not be locked when we check, but a lock
71
- // might be added right here. But... what else can we do? We need
72
- // to cleanup unused locks at some point...
73
- await fs.promises.unlink(cacheArchives2 + file);
74
- } catch { }
75
- }
76
- } else if (file.endsWith(CACHE_SUFFIX)) {
81
+ if (file.endsWith(CACHE_SUFFIX)) {
77
82
  let info: fs.Stats | undefined;
78
83
  try {
79
84
  info = await fs.promises.stat(file);
@@ -90,11 +95,11 @@ const getDiskMetricsBase = async () => {
90
95
  try {
91
96
  // TEMP files, and... any files?
92
97
  // If it's too old, delete it
93
- let stat = await fs.promises.stat(cacheArchives2 + file);
98
+ let stat = await fs.promises.stat(getCacheDir() + file);
94
99
  let threshold = Date.now() - TEMP_THRESHOLD;
95
100
  if (stat.mtimeMs < threshold) {
96
101
  try {
97
- await fs.promises.unlink(cacheArchives2 + file);
102
+ await fs.promises.unlink(getCacheDir() + file);
98
103
  } catch { }
99
104
  }
100
105
  // If we can't stat it, someone else deleted it, so that's fine...
@@ -104,10 +109,10 @@ const getDiskMetricsBase = async () => {
104
109
  let processFileParallel = runInParallel({ parallelCount: 32 }, processFile);
105
110
  await Promise.all(cacheFiles.map(processFileParallel));
106
111
 
107
- let { remove, availableBytes, availableFiles } = await SIZE_LIMIT.limit(fileSizes);
112
+ let { remove, availableBytes, availableFiles } = await SIZE_LIMIT().limit(fileSizes);
108
113
  for (let file of remove) {
109
114
  try {
110
- await fs.promises.unlink(cacheArchives2 + file.path);
115
+ await fs.promises.unlink(getCacheDir() + file.path);
111
116
  } catch { }
112
117
  }
113
118
 
@@ -138,19 +143,25 @@ const getDiskMetricsBase = async () => {
138
143
  let sourceTempFileStat = await fs.promises.stat(sourceTempFile);
139
144
  availableBytes -= sourceTempFileStat.size;
140
145
  availableFiles--;
141
- while ((availableBytes < 0 || availableFiles < 0) && fileSizes.length > 0) {
142
- let leastRecentlyAccessed = fileSizes[0];
143
- for (let file of fileSizes) {
144
- if (file.time < leastRecentlyAccessed.time) {
145
- leastRecentlyAccessed = file;
146
+ sort(fileSizes, x => x.time);
147
+ for (let i = 0; i < fileSizes.length;) {
148
+ if (availableBytes >= 0 && availableFiles >= 0) break;
149
+ let file = fileSizes[i];
150
+ let deleted = false;
151
+ try {
152
+ await fs.promises.unlink(file.path);
153
+ deleted = true;
154
+ } catch (e: any) {
155
+ if (e?.code === "ENOENT") {
156
+ deleted = true;
157
+ } else {
158
+ console.warn(`Failed to evict cache file ${file.path}:`, (e as Error).stack ?? e);
146
159
  }
147
160
  }
148
- let size = 0;
149
- try {
150
- size = (await fs.promises.stat(leastRecentlyAccessed.path)).size;
151
- } catch { }
152
- if (await deleteCacheFile(leastRecentlyAccessed.path)) {
153
- removeFile(leastRecentlyAccessed.path);
161
+ if (deleted) {
162
+ removeFile(file.path);
163
+ } else {
164
+ i++;
154
165
  }
155
166
  }
156
167
  fileSizes.push({
@@ -200,15 +211,10 @@ const getDiskMetricsBase = async () => {
200
211
  }
201
212
  if (buffer) {
202
213
  updateAccessTime(path);
214
+ bumpAtimeIfStale(path);
203
215
  }
204
216
  return buffer;
205
217
  }
206
- async function getPathAndLock(archives: Archives, key: string): Promise<string> {
207
- let path = getArchiveCachePath(archives, key);
208
- await lockFile(path);
209
- updateAccessTime(path);
210
- return path;
211
- }
212
218
  async function delCacheFile(archives: Archives, key: string): Promise<void> {
213
219
  let path = getArchiveCachePath(archives, key);
214
220
  if (removeFile(path)) {
@@ -225,7 +231,6 @@ const getDiskMetricsBase = async () => {
225
231
  addCacheFile: runInSerial(addCacheFile),
226
232
  getCacheFile,
227
233
  delCacheFile,
228
- getPathAndLock,
229
234
  };
230
235
  };
231
236
  let curSeqNum = 1;
@@ -242,23 +247,6 @@ const ensureDiskMetricsUpdated = lazy(() => {
242
247
  });
243
248
  });
244
249
 
245
- export type LockFncs = {
246
- // Get a path to the file locally, locking it so it won't be garbage collected until lockRegion completes
247
- // - If the file isn't in the archives, returns undefined, and doesn't lock it
248
- getPathAndLock(fileName: string): Promise<string | undefined>;
249
- // Gets it, and only checks the cache, not the archives. Faster, but means it might be deleted in
250
- // the archives and we will still use it locally.
251
- getPathAndLockFast(fileName: string): Promise<string | undefined>;
252
- getPathAndLockCacheOnly(fileName: string): Promise<string | undefined>;
253
- // MOVES the sourceFileName to the archives (so sourceFileName will be deleted after this runs),
254
- // and locks the archives file until lockRegion completes
255
- moveFileAndLock(config: {
256
- archivesFilename: string;
257
- sourceFileName: string;
258
- onlyCache?: boolean;
259
- }): Promise<string>;
260
- };
261
-
262
250
  let cacheArchivesSymbol = Symbol("cacheArchives");
263
251
  /** IMPORTANT! The cache assumes the files contents immutable, and they will only be created
264
252
  * and deleted, never mutated.
@@ -266,14 +254,6 @@ let cacheArchivesSymbol = Symbol("cacheArchives");
266
254
  export function wrapArchivesWithCache(archives: Archives, rootConfig?: {
267
255
  immutable?: boolean;
268
256
  }): Archives & {
269
- // NOTE: lockRegion / path based functions are preferred for external accesses, as they ensure files
270
- // won't be garbage collected, and uses paths, which will be required for external processes.
271
- // - Locks only protect the local cache. The values can still be deleted explicitly.
272
- lockRegion<T>(
273
- code: (
274
- fncs: LockFncs
275
- ) => Promise<T>
276
- ): Promise<T>;
277
257
  debugGetPath(key: string): string;
278
258
  } {
279
259
  if (cacheArchivesSymbol in archives) {
@@ -303,7 +283,6 @@ export function wrapArchivesWithCache(archives: Archives, rootConfig?: {
303
283
  await metrics.addCacheFile(archives, config.path, tempPath);
304
284
  let cachePath = getArchiveCachePath(archives, config.path);
305
285
 
306
- await lockFile(cachePath);
307
286
  let pos = 0;
308
287
  let cacheHandle: fs.promises.FileHandle = await fs.promises.open(cachePath, "r");
309
288
  let data = Buffer.alloc(LARGE_FILE_CHUNK);
@@ -329,185 +308,8 @@ export function wrapArchivesWithCache(archives: Archives, rootConfig?: {
329
308
  });
330
309
  } finally {
331
310
  await cacheHandle.close();
332
- await unlockFile(cachePath);
333
311
  }
334
312
  }
335
- function createGetPathAndLock(locked: string[]) {
336
- return async function getPathAndLock(fileName: string) {
337
- const info = await archives.getInfo(fileName);
338
- if (!info) return undefined;
339
-
340
- let path = getArchiveCachePath(archives, fileName);
341
- await lockFile(path);
342
- locked.push(path);
343
-
344
- try {
345
- let cacheStat = await fs.promises.stat(path);
346
- // NOTE: We check the size, and not just the existence, in case the file
347
- // is partially populated? Even though this isn't a real thing, as we rename
348
- // when we copy it...
349
- if (cacheStat.size === info.size) {
350
- return path;
351
- }
352
- } catch {
353
- // It doesn't exist, so read it in
354
- }
355
-
356
- let startRead = Date.now();
357
-
358
- let readPos = 0;
359
- let getNextDataBase = async (): Promise<Buffer | undefined> => {
360
- if (readPos >= info.size) return undefined;
361
- let curPos = readPos;
362
- readPos += LARGE_FILE_CHUNK;
363
- let end = Math.min(readPos, info.size);
364
- let data = await archives.get(fileName, { range: { start: curPos, end } });
365
- if (!data?.length) return undefined;
366
- return data;
367
- };
368
-
369
- let nextWriteIndex = 0;
370
- let buffers: (Buffer | { error: Error } | null | undefined | (() => void))[] = [];
371
-
372
- async function getThread() {
373
- while (true) {
374
- let index = nextWriteIndex++;
375
- let next: typeof buffers[number];
376
- try {
377
- next = await getNextDataBase();
378
- } catch (e: any) {
379
- next = { error: e };
380
- }
381
- let prev = buffers[index];
382
- buffers[index] = next || null;
383
- if (typeof prev === "function") {
384
- prev();
385
- }
386
- if (!next || "error" in next) break;
387
- }
388
- }
389
- // Read in parallel, for faster read times, and so we can read from backblaze
390
- // while writing to disk.
391
- void list(8).map(getThread);
392
-
393
- let nextReadIndex = 0;
394
- let getNextData = async (): Promise<Buffer | undefined> => {
395
- let index = nextReadIndex++;
396
- if (buffers[index] === null) return undefined;
397
- if (!buffers[index]) {
398
- await new Promise<void>(resolve => {
399
- buffers[index] = resolve;
400
- });
401
- }
402
- let result = buffers[index] as any;
403
- // Clear it, so we don't store all buffers in memory
404
- buffers[index] = undefined;
405
- return result;
406
- };
407
-
408
- let size = 0;
409
- const tempPath = getTempFilePath();
410
- let handle: fs.promises.FileHandle | undefined;
411
- try {
412
- handle = await fs.promises.open(tempPath, "w");
413
- while (true) {
414
- let data = await getNextData();
415
- if (!data?.length) break;
416
- await handle.write(data, 0, data.length, size);
417
- size += data.length;
418
- }
419
- } finally {
420
- if (handle) {
421
- try {
422
- await handle.close();
423
- } catch { }
424
- }
425
- }
426
- let totalRead = Date.now() - startRead;
427
- console.log(`Read ${formatNumber(info.size)}B at ${formatNumber(info.size / totalRead)}B/s into cache (${fileName})`);
428
-
429
- let metrics = await getDiskMetrics();
430
- await metrics.addCacheFile(archives, fileName, tempPath);
431
-
432
- return path;
433
- };
434
- }
435
- function createGetPathAndLockFast(locked: string[]) {
436
- return async function getPathAndLockFast(fileName: string) {
437
- let path = getArchiveCachePath(archives, fileName);
438
- await lockFile(path);
439
- locked.push(path);
440
- try {
441
- // If it exists in the cache, just return the path. Otherwise, we might have to read it in
442
- await fs.promises.stat(path);
443
- return path;
444
- } catch {
445
- return createGetPathAndLock(locked)(fileName);
446
- }
447
- };
448
- }
449
- function createMovePathFromFileAndLock(locked: string[]) {
450
- return async function movePathFromFileAndLock(config: {
451
- archivesFilename: string;
452
- sourceFileName: string;
453
- onlyCache?: boolean;
454
- }): Promise<string> {
455
- let { archivesFilename, sourceFileName } = config;
456
- if (!config.onlyCache) {
457
- // NOTE: While we COULD use a rename to quickly move the file... we have to at least copy
458
- // it to the underlying archives, so... moving it piece by piece is fine...
459
-
460
- let handle: fs.promises.FileHandle = await fs.promises.open(sourceFileName, "r");
461
- let data = Buffer.alloc(LARGE_FILE_CHUNK);
462
- let pos = 0;
463
- async function getNextData(): Promise<Buffer | undefined> {
464
- try {
465
- let read = await handle.read(data, 0, LARGE_FILE_CHUNK, pos);
466
- if (read.bytesRead === 0) return undefined;
467
- if (read.bytesRead < LARGE_FILE_CHUNK) {
468
- data = data.slice(0, read.bytesRead);
469
- }
470
- pos += read.bytesRead;
471
- return data;
472
- } catch {
473
- return undefined;
474
- }
475
- }
476
- try {
477
- await setLargeFile({
478
- path: archivesFilename,
479
- getNextData,
480
- });
481
- await handle.close();
482
- handle = undefined as any;
483
- await fs.promises.unlink(sourceFileName);
484
- } finally {
485
- if (handle) {
486
- await handle.close();
487
- }
488
- }
489
- } else {
490
- await fs.promises.rename(sourceFileName, getArchiveCachePath(archives, archivesFilename));
491
- }
492
- let path = getArchiveCachePath(archives, archivesFilename);
493
- await lockFile(path);
494
- locked.push(path);
495
- return path;
496
- };
497
- }
498
- function createGetPathAndLockCacheOnly(locked: string[]) {
499
- return async function getPathAndLockCacheOnly(fileName: string) {
500
- let path = getArchiveCachePath(archives, fileName);
501
- await lockFile(path);
502
- locked.push(path);
503
- try {
504
- await fs.promises.stat(path);
505
- return path;
506
- } catch {
507
- return undefined;
508
- }
509
- };
510
- }
511
313
  function debugGetPath(key: string) {
512
314
  return getArchiveCachePath(archives, key);
513
315
  }
@@ -592,351 +394,5 @@ export function wrapArchivesWithCache(archives: Archives, rootConfig?: {
592
394
  await archives.move(config);
593
395
  },
594
396
  getBaseArchives: () => archives.getBaseArchives?.() ?? ({ archives: archives, parentPath: "" }),
595
-
596
- async lockRegion<T>(code: (fncs: LockFncs) => Promise<T>) {
597
- let locked: string[] = [];
598
- let fncs = {
599
- getPathAndLock: createGetPathAndLock(locked),
600
- getPathAndLockFast: createGetPathAndLockFast(locked),
601
- moveFileAndLock: createMovePathFromFileAndLock(locked),
602
- getPathAndLockCacheOnly: createGetPathAndLockCacheOnly(locked),
603
- };
604
- try {
605
- return await code(fncs);
606
- } finally {
607
- for (let path of locked) {
608
- await unlockFile(path);
609
- }
610
- }
611
- },
612
- });
613
- }
614
-
615
-
616
- function lockFilePath(path: string): string {
617
- return path + LOCK_SUFFIX;
618
- }
619
-
620
- function getUniqueId() {
621
- return process.pid + " " + process.ppid;
622
- }
623
-
624
- let lockCache = new Map<string, number>();
625
- async function lockFile(path: string): Promise<void> {
626
- // NOTE: If we don't stop ourself from locking it multiple times, a single process
627
- // could use up the fill lock file limit, which would break the system.
628
- let prevCount = lockCache.get(path);
629
- if (prevCount) {
630
- lockCache.set(path, prevCount + 1);
631
- return;
632
- }
633
- if (!prevCount) {
634
- const lockPath = lockFilePath(path);
635
- // NOTE: Locking is taking WAY too long, so... we're not going to wait. This should still
636
- // be mostly fine, due to how we maintain transactions files.
637
- lockRetryLoop(async function appendFile() {
638
- await fs.promises.appendFile(lockPath, "lock " + getUniqueId() + "\n");
639
- }).catch(e => console.error("Error appending file lock for", path, e));
640
- }
641
- prevCount = lockCache.get(path) || 0;
642
- lockCache.set(path, prevCount + 1);
643
- }
644
-
645
- async function unlockFile(path: string): Promise<void> {
646
- let prevCount = lockCache.get(path);
647
- if (!prevCount) {
648
- console.warn(`Unlocking a file that wasn't locked: ${path}`);
649
- } else {
650
- prevCount--;
651
- lockCache.set(path, prevCount);
652
- if (prevCount <= 0) {
653
- lockCache.delete(path);
654
- }
655
- }
656
- if (!prevCount) {
657
- await isLocked(path, { type: "unlock", lock: "lock " + getUniqueId() });
658
- }
659
- }
660
-
661
- async function lockRetryLoop<T>(code: () => Promise<T>): Promise<T> {
662
- while (true) {
663
- try {
664
- return await code();
665
- } catch (error: any) {
666
- if (error.code === "EBUSY") {
667
- console.log("Lock file busy, retrying in 1 second");
668
- await new Promise(r => setTimeout(r, 1000));
669
- continue;
670
- }
671
- throw error;
672
- }
673
- }
674
- }
675
-
676
- async function getHandle(path: string) {
677
- while (true) {
678
- try {
679
- return await fs.promises.open(path, "r+");
680
- } catch (error: any) {
681
- if (error.code === "ENOENT") {
682
- try {
683
- return await fs.promises.open(path, "wx+");
684
- } catch (error: any) {
685
- if (error.code === "EEXIST") {
686
- continue;
687
- }
688
- throw error;
689
- }
690
- }
691
- throw error;
692
- }
693
- }
694
- }
695
-
696
-
697
- /** NOTE: This inherently has a race condition, as anything you do based on isLocked
698
- * could be invalidated by the time you do it. But... this should be good enough
699
- * for our use case...
700
- */
701
- async function isLocked(path: string, operation?: {
702
- type: "delete";
703
- } | {
704
- type: "unlock";
705
- lock: string;
706
- }): Promise<number> {
707
- const lockPath = lockFilePath(path);
708
-
709
- async function filterToValidState(locks: string[]): Promise<string[]> {
710
- let existingLocks: string[] = [];
711
- for (let lock of locks) {
712
- if (lock.startsWith("lock ")) {
713
- let [_, pid, ppid] = lock.split(" ");
714
- const invalid = (
715
- !await isProcessAlive(+pid)
716
- || !await isProcessAlive(+ppid)
717
- || await getPPID(+pid) !== +ppid
718
- );
719
- await getPPID(+pid);
720
- if (!invalid) {
721
- existingLocks.push(lock);
722
- }
723
- } else {
724
- continue;
725
- }
726
- }
727
- return Array.from(existingLocks);
728
- }
729
-
730
- return await lockRetryLoop(async function isLocked() {
731
- let handle = await getHandle(lockPath);
732
- try {
733
- const { flock } = await import("fs-ext");
734
- await new Promise<void>((r, e) => flock(handle.fd, "ex", err => err ? e(err) : r()));
735
- let contents = (await handle.readFile()).toString();
736
- let locks = contents.toString().replaceAll("\0", "").trim().split("\n").filter(x => x);
737
- let validLocks = await filterToValidState(locks);
738
- if (operation?.type === "unlock") {
739
- let index = validLocks.indexOf(operation.lock);
740
- if (index >= 0) {
741
- validLocks.splice(index, 1);
742
- }
743
- }
744
- if (validLocks.length !== locks.length) {
745
- //console.log(`Lock count changed from ${locks.length} to ${validLocks.length}`);
746
- let newContents = Buffer.from(validLocks.map(x => x + "\n").join(""));
747
- if (newContents.length < contents.length) {
748
- newContents = Buffer.concat([newContents, Buffer.alloc(contents.length - newContents.length)]);
749
- }
750
- await handle.write(newContents, 0, newContents.length, 0);
751
- } else {
752
- //console.log(`Lock count unchanged: ${validLocks.length}`);
753
- }
754
- if (operation?.type === "delete") {
755
- if (validLocks.length === 0) {
756
- await fs.promises.unlink(path);
757
- }
758
- }
759
- return validLocks.length;
760
- } catch (error: any) {
761
- if (error.code === "ENOENT") {
762
- return 0; // Lock file doesn't exist
763
- }
764
- throw error; // Unexpected error, rethrow
765
- } finally {
766
- await handle.close();
767
- }
768
- });
769
- }
770
- async function execPromise(command: string, args: string[]): Promise<string> {
771
- return new Promise((resolve, reject) => {
772
- child_process.execFile(command, args, (error, stdout, stderr) => {
773
- if (error) {
774
- reject(error);
775
- return;
776
- }
777
- resolve(stdout);
778
- });
779
- });
780
- }
781
-
782
-
783
- async function isProcessAlive(pid: number): Promise<boolean> {
784
- try {
785
- process.kill(pid, 0);
786
- return true;
787
- } catch (error) {
788
- return false;
789
- }
790
- }
791
-
792
-
793
- async function atomicRead(path: string) {
794
- while (true) {
795
- let stat0 = fs.statSync(path);
796
- let contents = fs.readFileSync(path);
797
- let stat1 = fs.statSync(path);
798
- if (stat0.mtimeMs === stat1.mtimeMs) {
799
- return contents;
800
- }
801
- }
802
- };
803
-
804
- async function lockCacheFile(archives: Archives, key: string): Promise<void> {
805
- await lockFile(getArchiveCachePath(archives, key));
806
- }
807
- async function unlockCacheFile(archives: Archives, key: string): Promise<void> {
808
- await unlockFile(getArchiveCachePath(archives, key));
809
- }
810
- // Returns true if it was deleted
811
- async function deleteCacheFile(path: string): Promise<boolean> {
812
- let cannotDelete = await isLocked(path, { type: "delete" });
813
- return !cannotDelete;
814
- }
815
-
816
- // for i in {1..100}; do yarn typenode src/-b-archives/archiveCache.ts & done
817
-
818
- async function testLocks() {
819
- let testFiles = ["a", "b", "c"].map(x => cacheArchives2 + x);
820
- function getAFile() {
821
- let file = testFiles[Math.floor(Math.random() * testFiles.length)];
822
- console.log(file);
823
- return file;
824
- }
825
-
826
- function assert(condition: unknown, message: string) {
827
- if (!condition) {
828
- throw new Error(message);
829
- }
830
- }
831
-
832
- async function test1() {
833
- let file = getAFile();
834
- await lockFile(file);
835
- let locked = await isLocked(file);
836
- assert(locked, "File should be locked");
837
- await new Promise(r => setTimeout(r, 100));
838
- await unlockFile(file);
839
- console.log(await fs.promises.readFile(file + LOCK_SUFFIX, "utf8"));
840
- }
841
- async function test2() {
842
- let file = getAFile();
843
- let notDeleted = await isLocked(file, { type: "delete" });
844
- if (notDeleted) {
845
- console.log("File was not deleted, as it was locked");
846
- } else {
847
- console.log("File was deleted");
848
- }
849
- }
850
- async function test3() {
851
- let count = 10;
852
- let file = getAFile();
853
- console.log(`Lock count before: ${await isLocked(file)}`);
854
- for (let i = 0; i < count; i++) {
855
- await lockFile(file);
856
- }
857
- for (let i = 0; i < count; i++) {
858
- await unlockFile(file);
859
- }
860
- console.log(`Lock count after: ${await isLocked(file)}`);
861
- }
862
- async function test4() {
863
- let file = getAFile();
864
- await lockFile(file);
865
- let notDeleted = await isLocked(file, { type: "delete" });
866
- assert(notDeleted, "File should not be deleted if it is locked");
867
- await unlockFile(file);
868
- }
869
-
870
-
871
- async function runRandomTest() {
872
- let testFn = [test1, test2, test3, test4][Math.floor(Math.random() * 4)];
873
- console.log(`Running test: ${testFn.name}`);
874
- await testFn();
875
- }
876
- for (let i = 0; i < 1000; i++) {
877
- await runRandomTest();
878
- }
879
- }
880
- //testLocks().catch(console.error).finally(() => process.exit(0));
881
-
882
-
883
- async function testLargeFiles() {
884
- const largeFile = "E:/downloads/Weird.Science.1985.EXTENDED.1080p.BluRay.H264.AAC-RARBG/weirdscience.mp4";
885
- let test = wrapArchivesWithCache(getArchivesBackblaze("querysub.com-testbucket"));
886
-
887
- /*
888
- let cacheHandle: fs.promises.FileHandle = await fs.promises.open(largeFile, "r");
889
- let data = Buffer.alloc(LARGE_FILE_CHUNK);
890
- let pos = 0;
891
- async function getNextData(): Promise<Buffer | undefined> {
892
- try {
893
- let read = await cacheHandle.read(data, 0, LARGE_FILE_CHUNK, pos);
894
- if (read.bytesRead === 0) return undefined;
895
- if (read.bytesRead < LARGE_FILE_CHUNK) {
896
- data = data.slice(0, read.bytesRead);
897
- }
898
- pos += read.bytesRead;
899
- return data;
900
- } catch {
901
- return undefined;
902
- }
903
- }
904
-
905
- await test.setLargeFile({
906
- path: "test.mp4",
907
- getNextData,
908
- });
909
- */
910
-
911
-
912
- await test.lockRegion(async fncs => {
913
- let newPath = await fncs.getPathAndLock("copy.mp4");
914
- console.log({ newPath });
915
397
  });
916
-
917
-
918
- /*
919
- logErrors((async () => {
920
- while (true) {
921
- let first100Bytes = await test.get("copy.mp4", { range: { start: 0, end: 100 } });
922
- console.log({ first100Bytes });
923
- await delay(5000);
924
- }
925
- })());
926
-
927
- {
928
- let pos = 0;
929
- async function getNextData(): Promise<Buffer | undefined> {
930
- let data = await test.get("test.mp4", { range: { start: pos, end: pos + LARGE_FILE_CHUNK } });
931
- if (!data?.length) return undefined;
932
- pos += data.length;
933
- return data;
934
- }
935
- await test.setLargeFile({
936
- path: "copy.mp4",
937
- getNextData,
938
- });
939
- }
940
- */
941
398
  }
942
- //testLargeFiles().catch(console.error).finally(() => process.exit(0));