@dench.com/cli 2.8.5 → 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.
- package/fs-daemon-pull-queue.ts +35 -0
- package/fs-daemon.ts +38 -20
- package/lib/enrichment-gateway.ts +12 -6
- package/package.json +2 -1
|
@@ -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
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
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 };
|
|
@@ -127,6 +127,14 @@ export type SearchJobsBody = {
|
|
|
127
127
|
pageToken?: string;
|
|
128
128
|
};
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* FullEnrich hard cases run 40–120s end to end; a 90s budget produced
|
|
132
|
+
* spurious "No data returned" failures for jobs that later completed (and
|
|
133
|
+
* charged). 150s covers the observed tail while the 2s interval keeps
|
|
134
|
+
* fast results snappy.
|
|
135
|
+
*/
|
|
136
|
+
const DEFAULT_POLL_TIMEOUT_MS = 150000;
|
|
137
|
+
|
|
130
138
|
export type EnrichmentGatewayOptions = {
|
|
131
139
|
apiKey?: string;
|
|
132
140
|
baseUrl?: string;
|
|
@@ -134,7 +142,7 @@ export type EnrichmentGatewayOptions = {
|
|
|
134
142
|
fetchImpl?: FetchLike;
|
|
135
143
|
/** Override the poll interval (ms) for testing. Default: 2000. */
|
|
136
144
|
pollIntervalMs?: number;
|
|
137
|
-
/** Override the poll timeout (ms) for testing. Default:
|
|
145
|
+
/** Override the poll timeout (ms) for testing. Default: 150000. */
|
|
138
146
|
pollTimeoutMs?: number;
|
|
139
147
|
/**
|
|
140
148
|
* Optional abort signal — propagated to fetch and short-circuits the polling
|
|
@@ -448,9 +456,7 @@ async function pollForPersonResult(
|
|
|
448
456
|
options: EnrichmentGatewayOptions,
|
|
449
457
|
): Promise<Record<string, unknown>> {
|
|
450
458
|
const pollIntervalMs = options.pollIntervalMs ?? 2000;
|
|
451
|
-
|
|
452
|
-
// 30s was too short and caused spurious "No data returned" FatalErrors.
|
|
453
|
-
const pollTimeoutMs = options.pollTimeoutMs ?? 90000;
|
|
459
|
+
const pollTimeoutMs = options.pollTimeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;
|
|
454
460
|
const deadline = Date.now() + pollTimeoutMs;
|
|
455
461
|
|
|
456
462
|
while (Date.now() < deadline) {
|
|
@@ -640,7 +646,7 @@ async function pollForBulkPersonResults(
|
|
|
640
646
|
options: EnrichmentGatewayOptions,
|
|
641
647
|
): Promise<void> {
|
|
642
648
|
const pollIntervalMs = options.pollIntervalMs ?? 2000;
|
|
643
|
-
const pollTimeoutMs = options.pollTimeoutMs ??
|
|
649
|
+
const pollTimeoutMs = options.pollTimeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;
|
|
644
650
|
const deadline = Date.now() + pollTimeoutMs;
|
|
645
651
|
|
|
646
652
|
// Slots that this chunk is responsible for — anything still missing after
|
|
@@ -882,7 +888,7 @@ async function pollForReverseEmailResults(
|
|
|
882
888
|
options: EnrichmentGatewayOptions,
|
|
883
889
|
): Promise<void> {
|
|
884
890
|
const pollIntervalMs = options.pollIntervalMs ?? 2000;
|
|
885
|
-
const pollTimeoutMs = options.pollTimeoutMs ??
|
|
891
|
+
const pollTimeoutMs = options.pollTimeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;
|
|
886
892
|
const deadline = Date.now() + pollTimeoutMs;
|
|
887
893
|
|
|
888
894
|
const ownedIndexes = emails
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dench.com/cli",
|
|
3
|
-
"version": "2.8.
|
|
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",
|