feat: add an API to time out query executions

Currently, if a predicate is hard to match on the Rust side, a sizable
query against a very large file can take forever, and ends up hanging.
This commit adds an API function `ts_query_cursor_set_timeout_micros` to
limit how long query execution is allowed to take, thereby negating the
chance of a hang to occur.
This commit is contained in:
Amaan Qureshi 2024-08-29 17:21:52 -04:00
parent a748488596
commit 3f424c0121
11 changed files with 132 additions and 8 deletions

View file

@ -792,7 +792,8 @@ void ts_query_matches_wasm(
uint32_t start_index,
uint32_t end_index,
uint32_t match_limit,
uint32_t max_start_depth
uint32_t max_start_depth,
uint32_t timeout_micros
) {
if (!scratch_query_cursor) {
scratch_query_cursor = ts_query_cursor_new();
@ -810,6 +811,7 @@ void ts_query_matches_wasm(
ts_query_cursor_set_byte_range(scratch_query_cursor, start_index, end_index);
ts_query_cursor_set_match_limit(scratch_query_cursor, match_limit);
ts_query_cursor_set_max_start_depth(scratch_query_cursor, max_start_depth);
ts_query_cursor_set_timeout_micros(scratch_query_cursor, timeout_micros);
ts_query_cursor_exec(scratch_query_cursor, self, node);
uint32_t index = 0;
@ -847,7 +849,8 @@ void ts_query_captures_wasm(
uint32_t start_index,
uint32_t end_index,
uint32_t match_limit,
uint32_t max_start_depth
uint32_t max_start_depth,
uint32_t timeout_micros
) {
if (!scratch_query_cursor) {
scratch_query_cursor = ts_query_cursor_new();
@ -862,6 +865,7 @@ void ts_query_captures_wasm(
ts_query_cursor_set_byte_range(scratch_query_cursor, start_index, end_index);
ts_query_cursor_set_match_limit(scratch_query_cursor, match_limit);
ts_query_cursor_set_max_start_depth(scratch_query_cursor, max_start_depth);
ts_query_cursor_set_timeout_micros(scratch_query_cursor, timeout_micros);
ts_query_cursor_exec(scratch_query_cursor, self, node);
unsigned index = 0;

View file

@ -1279,6 +1279,7 @@ class Query {
endIndex = 0,
matchLimit = 0xFFFFFFFF,
maxStartDepth = 0xFFFFFFFF,
timeoutMicros = 0,
} = {},
) {
if (typeof matchLimit !== 'number') {
@ -1298,6 +1299,7 @@ class Query {
endIndex,
matchLimit,
maxStartDepth,
timeoutMicros,
);
const rawCount = getValue(TRANSFER_BUFFER, 'i32');
@ -1342,6 +1344,7 @@ class Query {
endIndex = 0,
matchLimit = 0xFFFFFFFF,
maxStartDepth = 0xFFFFFFFF,
timeoutMicros = 0,
} = {},
) {
if (typeof matchLimit !== 'number') {
@ -1361,6 +1364,7 @@ class Query {
endIndex,
matchLimit,
maxStartDepth,
timeoutMicros,
);
const count = getValue(TRANSFER_BUFFER, 'i32');

View file

@ -451,6 +451,17 @@ describe('Query', () => {
]);
});
});
describe('Set a timeout', () =>
it('returns less than the expected matches', () => {
tree = parser.parse('function foo() while (true) { } }\n'.repeat(1000));
query = JavaScript.query('(function_declaration name: (identifier) @function)');
const matches = query.matches(tree.rootNode, { timeoutMicros: 1000 });
assert.isBelow(matches.length, 1000);
const matches2 = query.matches(tree.rootNode, { timeoutMicros: 0 });
assert.equal(matches2.length, 1000);
})
);
});
function formatMatches(matches) {

View file

@ -179,6 +179,7 @@ declare module 'web-tree-sitter' {
endIndex?: number;
matchLimit?: number;
maxStartDepth?: number;
timeoutMicros?: number;
};
export interface PredicateResult {