pinokiod 8.0.72 → 8.0.73

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.
@@ -57,6 +57,7 @@ class AutomaticScans {
57
57
  this.lifecycleWork = new Set()
58
58
  this.stopSettleMs = STOP_SETTLE_MS
59
59
  this.drainQueued = false
60
+ this.drainRequested = false
60
61
  this.waitingFor = null
61
62
  this.listeners = new Set()
62
63
  this.appTransitions = new Map()
@@ -266,9 +267,13 @@ class AutomaticScans {
266
267
  }
267
268
 
268
269
  async candidateThreshold() {
269
- const scan = this.vault.registry
270
- ? await this.vault.registry.scanFor()
270
+ const cached = this.vault.lastScanCache &&
271
+ typeof this.vault.lastScanCache.get === "function"
272
+ ? this.vault.lastScanCache.get("")
271
273
  : null
274
+ const scan = cached || (this.vault.registry
275
+ ? await this.vault.registry.scanFor()
276
+ : null)
272
277
  const threshold = scan ? Number(scan.candidate_min_bytes) : NaN
273
278
  return Number.isFinite(threshold) && threshold >= 0
274
279
  ? threshold
@@ -805,24 +810,38 @@ class AutomaticScans {
805
810
  }
806
811
 
807
812
  schedule() {
808
- if (this.disposed || this.drainQueued) return
813
+ if (this.disposed) return
814
+ if (this.drainQueued) {
815
+ this.drainRequested = true
816
+ return
817
+ }
809
818
  this.drainQueued = true
810
819
  queueMicrotask(() => {
811
- this.drainQueued = false
812
- const pending = this.drain()
820
+ const pending = (async () => {
821
+ do {
822
+ this.drainRequested = false
823
+ await this.drain()
824
+ } while (!this.disposed && this.drainRequested)
825
+ })()
813
826
  this.lifecycleWork.add(pending)
814
827
  pending.then(
815
- () => this.lifecycleWork.delete(pending),
816
- () => this.lifecycleWork.delete(pending)
828
+ () => {
829
+ this.lifecycleWork.delete(pending)
830
+ this.drainQueued = false
831
+ if (this.drainRequested) this.schedule()
832
+ },
833
+ (error) => {
834
+ this.lifecycleWork.delete(pending)
835
+ this.drainQueued = false
836
+ this.log("error", {
837
+ stage: "queue",
838
+ message: error && error.message ? error.message : String(error)
839
+ })
840
+ console.warn("Automatic Disk Saver check failed:",
841
+ error && error.message ? error.message : error)
842
+ if (this.drainRequested) this.schedule()
843
+ }
817
844
  )
818
- pending.catch((error) => {
819
- this.log("error", {
820
- stage: "queue",
821
- message: error && error.message ? error.message : String(error)
822
- })
823
- console.warn("Automatic Disk Saver check failed:",
824
- error && error.message ? error.message : error)
825
- })
826
845
  })
827
846
  }
828
847
 
@@ -972,7 +991,6 @@ class AutomaticScans {
972
991
  const paths = [...new Set(active.paths || [])].filter((filePath) =>
973
992
  typeof filePath === "string" && inside(root, filePath))
974
993
  const verifiedHashes = new Map()
975
- await this.vault.registry.beginAutomaticPrecheck(app)
976
994
  this.log("check-started", {
977
995
  app,
978
996
  root,
@@ -980,6 +998,7 @@ class AutomaticScans {
980
998
  changed_paths: paths.length,
981
999
  policy: "metadata-prefilter-sha256"
982
1000
  })
1001
+ await this.vault.registry.beginAutomaticPrecheck(app)
983
1002
  try {
984
1003
  this.checkpoint(active)
985
1004
  const metadataOptions = {
@@ -1164,7 +1183,19 @@ class AutomaticScans {
1164
1183
  item.state === "checking")
1165
1184
  if (!entry) return
1166
1185
  await this.refreshGlobalScanReady()
1167
- if (this.disposed || !this.globalScanReady) return
1186
+ if (this.disposed) return
1187
+ if (!this.globalScanReady) {
1188
+ if (this.entries.get(entry.app) === entry) {
1189
+ this.restorePrevious(entry.app)
1190
+ this.discardChangedPaths(entry.app)
1191
+ this.log("check-skipped", {
1192
+ app: entry.app,
1193
+ reason: "global-scan-required"
1194
+ })
1195
+ }
1196
+ this.schedule()
1197
+ return
1198
+ }
1168
1199
  if (this.manualDepth > 0 || this.currentBusyPromise() ||
1169
1200
  this.vault.fileActionProgress) {
1170
1201
  this.waitForBusyWork()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "8.0.72",
3
+ "version": "8.0.73",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -405,6 +405,71 @@ describe("automatic app checks", () => {
405
405
  assert.equal(checksStarted, 0)
406
406
  })
407
407
 
408
+ test("scheduler coalesces wakeups while dispatch is in progress", async () => {
409
+ let releaseReadiness
410
+ let readinessCalls = 0
411
+ let dispatches = 0
412
+ const automatic = makeAutomatic({
413
+ enabled: true,
414
+ registry: {},
415
+ runExclusive: () => {
416
+ dispatches += 1
417
+ return new Promise(() => {})
418
+ }
419
+ })
420
+ automatic.entries.set("demo", {
421
+ app: "demo",
422
+ state: "checking",
423
+ paths: []
424
+ })
425
+ automatic.globalScanReady = true
426
+ automatic.refreshGlobalScanReady = async () => {
427
+ readinessCalls += 1
428
+ if (readinessCalls === 1) {
429
+ await new Promise((resolve) => { releaseReadiness = resolve })
430
+ }
431
+ automatic.globalScanReady = true
432
+ }
433
+ automatic.appRootIsAvailable = async () => true
434
+ automatic.appIsRunning = () => false
435
+
436
+ automatic.schedule()
437
+ await waitFor(() => typeof releaseReadiness === "function",
438
+ "scheduler readiness pause")
439
+ automatic.schedule()
440
+ releaseReadiness()
441
+ await waitFor(() => dispatches > 0, "automatic dispatch")
442
+ await new Promise((resolve) => setImmediate(resolve))
443
+
444
+ assert.equal(dispatches, 1)
445
+ assert.equal(readinessCalls, 1)
446
+ })
447
+
448
+ test("queued checks are cleared if global readiness disappears", async () => {
449
+ const automatic = makeAutomatic({
450
+ enabled: true,
451
+ registry: {}
452
+ })
453
+ automatic.entries.set("demo", {
454
+ app: "demo",
455
+ state: "checking",
456
+ paths: ["/pinokio/api/demo/model.bin"]
457
+ })
458
+ automatic.changedPaths.set("demo", new Set([
459
+ "/pinokio/api/demo/model.bin"
460
+ ]))
461
+ automatic.refreshGlobalScanReady = async () => {
462
+ automatic.globalScanReady = false
463
+ }
464
+ automatic.log = () => {}
465
+
466
+ automatic.schedule()
467
+ await waitFor(() => !automatic.drainQueued, "automatic queue cleanup")
468
+
469
+ assert.equal(automatic.entries.has("demo"), false)
470
+ assert.equal(automatic.changedPaths.has("demo"), false)
471
+ })
472
+
408
473
  test("Linux starts no watcher and ignores automatic lifecycle work", async () => {
409
474
  const home = await makeHome()
410
475
  const appRoot = path.join(home, "api", "linux-app")