Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions apps/server/src/workspace/WorkspaceEntries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -813,10 +813,14 @@ it.layer(layerTest, { excludeTestServices: true })("WorkspaceEntries", (it) => {
if (created.ok) {
const finder = created.value;
finders += 1;
const scanFiles = finder.scanFiles.bind(finder);
// The synchronous rescan waits on fff's picker lock on the event loop.
finder.scanFiles = () => {
throw new Error("refresh must not rescan on the JS thread");
};
const scanFilesAsync = finder.scanFilesAsync.bind(finder);
finder.scanFilesAsync = () => {
rescans += 1;
return scanFiles();
return scanFilesAsync();
};
const waitForIndexReady = finder.waitForIndexReady.bind(finder);
finder.waitForIndexReady = (timeoutMs) =>
Expand Down
7 changes: 4 additions & 3 deletions apps/server/src/workspace/WorkspaceSearchIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,12 +399,13 @@ export const make = Effect.fn("WorkspaceSearchIndex.make")(function* (
const refresh: WorkspaceSearchIndex["Service"]["refresh"] = Effect.fn(
"WorkspaceSearchIndex.refresh",
)(function* () {
const result = yield* Effect.try({
try: () => finder.scanFiles(),
// scanFiles waits on fff's picker lock, which its watcher can hold for seconds.
const result = yield* Effect.tryPromise({
try: () => finder.scanFilesAsync(),
catch: (cause) =>
new WorkspaceSearchIndexRefreshFailed({
cwd,
reason: "FileFinder.scanFiles threw unexpectedly.",
reason: "FileFinder.scanFilesAsync rejected unexpectedly.",
cause,
}),
});
Expand Down
207 changes: 183 additions & 24 deletions patches/@ff-labs__fff-node@0.11.0.patch
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
- "require": "./dist/index.cjs",
+ "require": "./dist/index.js",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
diff --git a/dist/fff-api.d.ts b/dist/fff-api.d.ts
index 0f90eedca20655ccd30b8f28e433b2094341fbdf..c2ef86927d6e3ab0e8584330fa0ac3fdacc06cf5 100644
--- a/dist/fff-api.d.ts
+++ b/dist/fff-api.d.ts
@@ -199,7 +199,7 @@ export type Location = {
Expand Down Expand Up @@ -40,7 +29,34 @@ diff --git a/dist/fff-api.d.ts b/dist/fff-api.d.ts
scores: Score[];
/** Total number of items (files + dirs) that matched */
totalMatched: number;
diff --git a/dist/finder.d.ts b/dist/finder.d.ts
index 452102d720cc19b0560cfc9e2f1a320687047c92..3d3211c70a7de4b97792edb32cc9466c8ba89547 100644
--- a/dist/finder.d.ts
+++ b/dist/finder.d.ts
@@ -242,6 +242,10 @@ export declare class FileFinder implements FileFinderApi {
* background watcher might have missed.
*/
scanFiles(): Result<void>;
+ /**
+ * Trigger a rescan without blocking the JS thread. Patched in T3 Code.
+ */
+ scanFilesAsync(): Promise<Result<void>>;
/**
* Check if a scan is currently in progress.
*/
@@ -254,6 +258,10 @@ export declare class FileFinder implements FileFinderApi {
* Get the current scan progress.
*/
getScanProgress(): Result<ScanProgress>;
+ /**
+ * Read scan progress without blocking the JS thread. Patched in T3 Code.
+ */
+ getScanProgressAsync(): Promise<Result<ScanProgress>>;
/**
* Wait for the initial file scan to complete.
* Non-blocking: polls `isScanning` and yields to the event loop between checks.
diff --git a/dist/index.js b/dist/index.js
index 7c1d722eabc8d8eb98998eb48c5fe9d70b749d10..138e590bdcb1bc4fd33f7d195b8d588ac7f86cc9 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -1,7 +1,7 @@
Expand Down Expand Up @@ -86,7 +102,45 @@ diff --git a/dist/index.js b/dist/index.js
if (isNullPointer(ptr)) return null;
try {
const [str] = restorePointer({
@@ -426,9 +437,9 @@ function ffiDestroy(handle) {
@@ -303,6 +314,26 @@ function freeResult(resultPtr) {
function readResultEnvelope(funcName, paramsType, paramsValue) {
loadLibrary();
const { rawPtr, struct: structData } = callRaw(funcName, paramsType, paramsValue);
+ return envelopeFromRaw(rawPtr, structData);
+}
+async function readResultEnvelopeOnNativeThread(funcName, handle) {
+ loadLibrary();
+ const rawPtr = await load({
+ library: LIBRARY_KEY,
+ funcName,
+ retType: DataType.External,
+ paramsType: [DataType.External],
+ paramsValue: [handle],
+ freeResultMemory: false,
+ runInNewThread: true
+ });
+ const [structData] = restorePointer({
+ retType: [FFF_RESULT_STRUCT],
+ paramsValue: wrapPointer([rawPtr])
+ });
+ return envelopeFromRaw(rawPtr, structData);
+}
+function envelopeFromRaw(rawPtr, structData) {
if (structData.success === 0) {
const errorStr = readCString(structData.error);
freeResult(rawPtr);
@@ -311,7 +342,9 @@ function readResultEnvelope(funcName, paramsType, paramsValue) {
return { rawPtr, struct: structData };
}
function callVoidResult(funcName, paramsType, paramsValue) {
- const res = readResultEnvelope(funcName, paramsType, paramsValue);
+ return voidResultFromEnvelope(readResultEnvelope(funcName, paramsType, paramsValue));
+}
+function voidResultFromEnvelope(res) {
if ("ok" in res) return res;
freeResult(res.rawPtr);
return { ok: true, value: void 0 };
@@ -426,9 +459,9 @@ function ffiDestroy(handle) {
});
}
var FFF_FILE_ITEM_STRUCT = {
Expand All @@ -99,7 +153,7 @@ diff --git a/dist/index.js b/dist/index.js
size: DataType.U64,
modified: DataType.U64,
access_frecency_score: DataType.I64,
@@ -445,8 +456,9 @@ var FFF_SCORE_STRUCT = {
@@ -445,8 +478,9 @@ var FFF_SCORE_STRUCT = {
distance_penalty: DataType.I32,
current_file_penalty: DataType.I32,
combo_match_boost: DataType.I32,
Expand All @@ -110,7 +164,7 @@ diff --git a/dist/index.js b/dist/index.js
};
var FFF_SEARCH_RESULT_STRUCT = {
items: DataType.External,
@@ -462,8 +474,8 @@ var FFF_SEARCH_RESULT_STRUCT = {
@@ -462,8 +496,8 @@ var FFF_SEARCH_RESULT_STRUCT = {
location_end_col: DataType.I32
};
var FFF_DIR_ITEM_STRUCT = {
Expand All @@ -121,7 +175,7 @@ diff --git a/dist/index.js b/dist/index.js
max_access_frecency: DataType.I32
};
var FFF_DIR_SEARCH_RESULT_STRUCT = {
@@ -475,9 +487,9 @@ var FFF_DIR_SEARCH_RESULT_STRUCT = {
@@ -475,9 +509,9 @@ var FFF_DIR_SEARCH_RESULT_STRUCT = {
};
var FFF_MIXED_ITEM_STRUCT = {
item_type: DataType.U8,
Expand All @@ -134,7 +188,7 @@ diff --git a/dist/index.js b/dist/index.js
size: DataType.U64,
modified: DataType.U64,
access_frecency_score: DataType.I64,
@@ -500,10 +512,10 @@ var FFF_MIXED_SEARCH_RESULT_STRUCT = {
@@ -500,10 +534,10 @@ var FFF_MIXED_SEARCH_RESULT_STRUCT = {
location_end_col: DataType.I32
};
var FFF_GREP_MATCH_STRUCT = {
Expand All @@ -149,7 +203,7 @@ diff --git a/dist/index.js b/dist/index.js
match_ranges: DataType.External,
context_before: DataType.External,
context_after: DataType.External,
@@ -598,18 +610,13 @@ function readMixedItemFromRaw(raw) {
@@ -598,18 +632,13 @@ function readMixedItemFromRaw(raw) {
}
function callAccessor(funcName, resultPtr, index, structDef) {
loadLibrary();
Expand All @@ -170,15 +224,15 @@ diff --git a/dist/index.js b/dist/index.js
}
function ptrOffset(base, bytes) {
return load({
@@ -766,6 +773,7 @@ function parseSearchResult(rawPtr) {
@@ -766,6 +795,7 @@ function parseSearchResult(rawPtr) {
}
const items = [];
const scores = [];
+ const includeScores = count <= 200;
for (let i = 0; i < count; i++) {
const rawItem = callAccessor(
"fff_search_result_get_item",
@@ -774,13 +782,15 @@ function parseSearchResult(rawPtr) {
@@ -774,13 +804,15 @@ function parseSearchResult(rawPtr) {
FFF_FILE_ITEM_STRUCT
);
items.push(readFileItemFromRaw(rawItem));
Expand All @@ -201,15 +255,15 @@ diff --git a/dist/index.js b/dist/index.js
}
load({
library: LIBRARY_KEY,
@@ -824,6 +834,7 @@ function parseDirSearchResult(rawPtr) {
@@ -824,6 +856,7 @@ function parseDirSearchResult(rawPtr) {
const count = sr.count;
const items = [];
const scores = [];
+ const includeScores = count <= 200;
for (let i = 0; i < count; i++) {
const rawItem = callAccessor(
"fff_dir_search_result_get_item",
@@ -832,13 +843,15 @@ function parseDirSearchResult(rawPtr) {
@@ -832,13 +865,15 @@ function parseDirSearchResult(rawPtr) {
FFF_DIR_ITEM_STRUCT
);
items.push(readDirItemFromRaw(rawItem));
Expand All @@ -232,15 +286,15 @@ diff --git a/dist/index.js b/dist/index.js
}
load({
library: LIBRARY_KEY,
@@ -897,6 +910,7 @@ function parseMixedSearchResult(rawPtr) {
@@ -897,6 +932,7 @@ function parseMixedSearchResult(rawPtr) {
}
const items = [];
const scores = [];
+ const includeScores = count <= 200;
for (let i = 0; i < count; i++) {
const rawItem = callAccessor(
"fff_mixed_search_result_get_item",
@@ -905,13 +919,15 @@ function parseMixedSearchResult(rawPtr) {
@@ -905,13 +941,15 @@ function parseMixedSearchResult(rawPtr) {
FFF_MIXED_ITEM_STRUCT
);
items.push(readMixedItemFromRaw(rawItem));
Expand All @@ -263,3 +317,108 @@ diff --git a/dist/index.js b/dist/index.js
}
load({
library: LIBRARY_KEY,
@@ -1188,7 +1226,11 @@ var FFF_SCAN_PROGRESS_STRUCT = {
};
function ffiGetScanProgress(handle) {
loadLibrary();
- const res = readResultEnvelope("fff_get_scan_progress", [DataType.External], [handle]);
+ return scanProgressFromEnvelope(
+ readResultEnvelope("fff_get_scan_progress", [DataType.External], [handle])
+ );
+}
+function scanProgressFromEnvelope(res) {
if ("ok" in res) return res;
const handlePtr = res.struct.handle;
freeResult(res.rawPtr);
@@ -1429,6 +1471,8 @@ var FileFinder = class _FileFinder {
handle;
/** Native ids of this instance's active watch subscriptions. */
watchers = /* @__PURE__ */ new Set();
+ /** Patched in T3 Code: in-flight native-thread calls by function name. */
+ nativeThreadCalls = /* @__PURE__ */ new Map();
constructor(handle) {
this.handle = handle;
}
@@ -1485,12 +1529,37 @@ var FileFinder = class _FileFinder {
destroy() {
if (this.handle !== null) {
const handle = this.handle;
- ffiDestroy(handle);
+ const watchers = this.watchers;
this.handle = null;
- ffiWatchCleanupAfterDestroy(handle, this.watchers);
- this.watchers.clear();
+ this.watchers = /* @__PURE__ */ new Set();
+ const release = () => {
+ ffiDestroy(handle);
+ ffiWatchCleanupAfterDestroy(handle, watchers);
+ };
+ // Patched in T3 Code: a native-thread call may still be using the handle.
+ if (this.nativeThreadCalls.size > 0) {
+ Promise.allSettled(this.nativeThreadCalls.values()).then(release);
+ } else {
+ release();
+ }
}
}
+ /**
+ * Patched in T3 Code: runs a handle-only call on a libuv pool thread so waiting on fff's
+ * picker lock never blocks the JS thread. Concurrent callers share one call, so one finder
+ * holds at most one pool thread per function.
+ */
+ callOnNativeThread(funcName, fromEnvelope) {
+ const guard = this.ensureAlive();
+ if (!guard.ok) return Promise.resolve(guard);
+ const pending = this.nativeThreadCalls.get(funcName);
+ if (pending) return pending;
+ const call = readResultEnvelopeOnNativeThread(funcName, guard.value).then(fromEnvelope, (cause) => err(String(cause))).finally(() => {
+ this.nativeThreadCalls.delete(funcName);
+ });
+ this.nativeThreadCalls.set(funcName, call);
+ return call;
+ }
/**
* Check if this instance has been destroyed.
*/
@@ -1757,6 +1826,18 @@ var FileFinder = class _FileFinder {
if (!guard.ok) return guard;
return ffiScanFiles(guard.value);
}
+ /**
+ * Trigger a rescan without blocking the JS thread. Patched in T3 Code.
+ */
+ scanFilesAsync() {
+ return this.callOnNativeThread("fff_scan_files", voidResultFromEnvelope);
+ }
+ /**
+ * Read scan progress without blocking the JS thread. Patched in T3 Code.
+ */
+ getScanProgressAsync() {
+ return this.callOnNativeThread("fff_get_scan_progress", scanProgressFromEnvelope);
+ }
/**
* Check if a scan is currently in progress.
*/
@@ -1838,7 +1919,8 @@ var FileFinder = class _FileFinder {
if (!guard.ok) return guard;
const deadline = Date.now() + timeoutMs;
while (true) {
- const progress = this.getScanProgress();
+ // Patched in T3 Code: the progress read takes the picker lock.
+ const progress = await this.getScanProgressAsync();
if (!progress.ok) return progress;
if (!progress.value.isScanning && progress.value.isWarmupComplete) {
return { ok: true, value: true };
diff --git a/package.json b/package.json
index e000a78b3f79826200979ebb3fe297b599bcea67..84325461c2d963cb42cf72267079d36a419c1398 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
- "require": "./dist/index.cjs",
+ "require": "./dist/index.js",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading