Which fff frontend?
Node SDK (@ff-labs/fff-node)
has logs
No response
Description
Summary
FileFinder.grep() does not enforce timeBudgetMs when a plain-text search produces no matches.
In the reproduction below, a 5 ms search scans all 10,000 candidate files, returns nextCursor: null, and takes substantially longer than the requested budget.
Because the Node API is synchronous and exposes no cancellation mechanism, callers cannot independently interrupt the native operation. This makes timeBudgetMs the only available execution bound.
Environment
Reproduction
mkdir fff-budget-repro
cd fff-budget-repro
npm init -y
npm install @ff-labs/fff-node@0.10.5
Save the following as repro.mjs:
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import { FileFinder } from "@ff-labs/fff-node";
function unwrap(result) {
if (!result.ok) {
throw new Error(`FFF operation failed: ${result.error}`);
}
return result.value;
}
const root = await fs.mkdtemp(
path.join(os.tmpdir(), "fff-no-match-budget-"),
);
let finder;
try {
const contents = `${"x".repeat(4 * 1024)}\n`;
for (let batch = 0; batch < 100; batch += 1) {
await Promise.all(
Array.from({ length: 100 }, (_, index) =>
fs.writeFile(
path.join(root, `${batch}-${index}.txt`),
contents,
),
),
);
}
finder = unwrap(
FileFinder.create({
basePath: root,
disableMmapCache: true,
disableContentIndexing: false,
aiMode: false,
enableFsRootScanning: false,
enableHomeDirScanning: false,
}),
);
const ready = unwrap(await finder.waitForIndexReady(30_000));
if (!ready) {
throw new Error("Index did not become ready");
}
const startedAt = performance.now();
const result = unwrap(
finder.grep("needle-not-present", {
mode: "plain",
maxFileSize: 1024 * 1024,
pageSize: 500,
timeBudgetMs: 5,
}),
);
const elapsedMs = performance.now() - startedAt;
console.log({
elapsedMs,
totalFiles: result.totalFiles,
filteredFileCount: result.filteredFileCount,
totalFilesSearched: result.totalFilesSearched,
nextCursor: result.nextCursor,
cancellationAvailable: "cancel" in finder,
});
} finally {
finder?.destroy();
await fs.rm(root, { recursive: true, force: true });
}
Run:
Actual behavior
Example result from Linux x86-64:
{
elapsedMs: 59.25,
totalFiles: 10000,
filteredFileCount: 10000,
totalFilesSearched: 10000,
nextCursor: null,
cancellationAvailable: false
}
The exact elapsed time is hardware-dependent, but the important behavior is that all candidate files are searched and no continuation cursor is returned after the time budget expires.
Expected behavior
When timeBudgetMs expires:
- Grep should stop traversing candidate files even if it has found zero matches.
totalFilesSearched should be less than filteredFileCount.
nextCursor should allow the caller to resume from the next unsearched file, or the result should otherwise explicitly indicate that the search timed out.
- The synchronous native call should return within a small, documented amount of overshoot beyond the requested budget.
Suspected root cause
The plain/regex grep budget check is conditional on existing matches:
if !need_abort
&& let Some(budget) = time_budget
&& all_matches.len() > 1
&& search_start.elapsed() > budget
{
need_abort = true;
}
For a zero-match search, all_matches.len() remains zero, so the deadline condition is never applied.
Removing the match-count condition, while preserving cursor accounting for the partially consumed chunk, appears necessary.
Relationship to #746 / #750
This is related to, but not resolved by:
PR #750 added timeBudgetMs to the pi-fff wrapper and deduplicated auxiliary index creation. It did not address the native zero-match budget bypass demonstrated here.
Which fff frontend?
Node SDK (@ff-labs/fff-node)
has logs
No response
Description
Summary
FileFinder.grep()does not enforcetimeBudgetMswhen a plain-text search produces no matches.In the reproduction below, a 5 ms search scans all 10,000 candidate files, returns
nextCursor: null, and takes substantially longer than the requested budget.Because the Node API is synchronous and exposes no cancellation mechanism, callers cannot independently interrupt the native operation. This makes
timeBudgetMsthe only available execution bound.Environment
@ff-labs/fff-node0.9.4https://github.com/dmtrKovalenko/fff/blob/v0.10.5/crates/fff-core/src/grep/grep.rs
Reproduction
mkdir fff-budget-repro cd fff-budget-repro npm init -y npm install @ff-labs/fff-node@0.10.5Save the following as
repro.mjs:Run:
Actual behavior
Example result from Linux x86-64:
The exact elapsed time is hardware-dependent, but the important behavior is that all candidate files are searched and no continuation cursor is returned after the time budget expires.
Expected behavior
When
timeBudgetMsexpires:totalFilesSearchedshould be less thanfilteredFileCount.nextCursorshould allow the caller to resume from the next unsearched file, or the result should otherwise explicitly indicate that the search timed out.Suspected root cause
The plain/regex grep budget check is conditional on existing matches:
For a zero-match search,
all_matches.len()remains zero, so the deadline condition is never applied.Removing the match-count condition, while preserving cursor accounting for the partially consumed chunk, appears necessary.
Relationship to #746 / #750
This is related to, but not resolved by:
PR #750 added
timeBudgetMsto thepi-fffwrapper and deduplicated auxiliary index creation. It did not address the native zero-match budget bypass demonstrated here.