@dench.com/cli 2.8.6-staging.178 → 2.8.7-staging.189

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.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Serialize pull-queue drains without dropping notifications that arrive
3
+ * while a drain is already in progress.
4
+ */
5
+ export function createPullQueueDrain(args: {
6
+ processRequests: () => Promise<void>;
7
+ onError: (error: unknown) => void;
8
+ }): () => Promise<void> {
9
+ let inFlight: Promise<void> | null = null;
10
+ let rerunRequested = false;
11
+
12
+ return function drain(): Promise<void> {
13
+ if (inFlight) {
14
+ rerunRequested = true;
15
+ return inFlight;
16
+ }
17
+
18
+ inFlight = (async () => {
19
+ try {
20
+ do {
21
+ rerunRequested = false;
22
+ try {
23
+ await args.processRequests();
24
+ } catch (error) {
25
+ args.onError(error);
26
+ }
27
+ } while (rerunRequested);
28
+ } finally {
29
+ inFlight = null;
30
+ }
31
+ })();
32
+
33
+ return inFlight;
34
+ };
35
+ }
package/fs-daemon.ts CHANGED
@@ -56,6 +56,7 @@ import {
56
56
  mkdir,
57
57
  readdir,
58
58
  readFile,
59
+ rename,
59
60
  stat,
60
61
  unlink,
61
62
  writeFile,
@@ -76,6 +77,7 @@ import {
76
77
  type MountInfo,
77
78
  readMountInfo,
78
79
  } from "./fs-daemon-mount";
80
+ import { createPullQueueDrain } from "./fs-daemon-pull-queue";
79
81
 
80
82
  type FileTreeSyncRow = {
81
83
  path: string;
@@ -731,7 +733,34 @@ async function runDaemon(daemonArgs: Args): Promise<void> {
731
733
  status.recordBreakerEvent(breakerState);
732
734
  void status.flush();
733
735
  });
736
+
737
+ // Register the pull signal before publishing the daemon status. `fs pull`
738
+ // treats that status as readiness; publishing first left a startup window
739
+ // where SIGUSR2 was lost (or terminated the daemon) and the request timed
740
+ // out. A queued rerun also prevents signals received mid-drain from being
741
+ // coalesced away.
742
+ const drainPullRequests = createPullQueueDrain({
743
+ processRequests: () =>
744
+ processPullRequests({
745
+ client,
746
+ tracker,
747
+ workspace: daemonArgs.workspace,
748
+ }),
749
+ onError: (error) =>
750
+ console.error(
751
+ `[dench-fs-daemon] pull queue failed: ${
752
+ error instanceof Error ? error.message : String(error)
753
+ }`,
754
+ ),
755
+ });
756
+ process.on("SIGUSR2", () => {
757
+ console.log(`[dench-fs-daemon] SIGUSR2 received — processing pull queue`);
758
+ void drainPullRequests();
759
+ });
734
760
  await status.flush();
761
+ // Recover requests left behind if a previous daemon exited after the
762
+ // request file was written but before it could process the signal.
763
+ void drainPullRequests();
735
764
 
736
765
  let chokidar: typeof import("chokidar");
737
766
  try {
@@ -940,21 +969,6 @@ async function runDaemon(daemonArgs: Args): Promise<void> {
940
969
  .catch(() => undefined);
941
970
  });
942
971
 
943
- process.on("SIGUSR2", () => {
944
- console.log(`[dench-fs-daemon] SIGUSR2 received — processing pull queue`);
945
- void processPullRequests({
946
- client,
947
- tracker,
948
- workspace: daemonArgs.workspace,
949
- }).catch((error) =>
950
- console.error(
951
- `[dench-fs-daemon] pull queue failed: ${
952
- error instanceof Error ? error.message : String(error)
953
- }`,
954
- ),
955
- );
956
- });
957
-
958
972
  process.on("SIGTERM", () => {
959
973
  reconciler.stop();
960
974
  unsubscribe();
@@ -1505,11 +1519,15 @@ async function runPull(argv: string[]): Promise<void> {
1505
1519
  await mkdir(PULL_REQUEST_DIR, { recursive: true });
1506
1520
  const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
1507
1521
  const request: PullRequest = { id, paths, requestedAt: Date.now() };
1508
- await writeFile(
1509
- `${PULL_REQUEST_DIR}/request-${id}.json`,
1510
- JSON.stringify(request),
1511
- "utf-8",
1512
- );
1522
+ const requestPath = `${PULL_REQUEST_DIR}/request-${id}.json`;
1523
+ const temporaryRequestPath = `${requestPath}.${process.pid}.tmp`;
1524
+ try {
1525
+ await writeFile(temporaryRequestPath, JSON.stringify(request), "utf-8");
1526
+ await rename(temporaryRequestPath, requestPath);
1527
+ } catch (error) {
1528
+ await unlink(temporaryRequestPath).catch(() => undefined);
1529
+ throw error;
1530
+ }
1513
1531
  process.kill(status.pid, "SIGUSR2");
1514
1532
  if (!wait) {
1515
1533
  const response = { ok: true, id, queued: paths };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dench.com/cli",
3
- "version": "2.8.6-staging.178",
3
+ "version": "2.8.7-staging.189",
4
4
  "description": "Dench agent workspace CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,6 +17,7 @@
17
17
  "fs-daemon-binary.ts",
18
18
  "fs-daemon-flush.ts",
19
19
  "fs-daemon-mount.ts",
20
+ "fs-daemon-pull-queue.ts",
20
21
  "home-sync",
21
22
  "home-sync.ts",
22
23
  "home-sync-lib.ts",