Repository navigation
Conversation
QueryResultIter::next recursed once for every query result outside the range, so a long statement overflowed the stack, like ParseIterator did.
The language server's tokio threads and the rayon threads of check used the 2 MiB default. libpg_query needs stack proportional to the length of an expression, and an overflow aborts the daemon for every editor. The threads now get 8 MiB, the main thread's default on Linux.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two stack fixes, one commit each: a recursion that grows with the input, and the thread stack size that turns such recursions into an abort of the daemon.
No recursion in
QueryResultIterQueryResultIter::next(pgls_treesitter/src/queries/mod.rs) called itself once for every query result outside the requested range, the same pattern as theParseIteratorfixed in #892. The completion and hover context filters relations, aliases and columns by the range of the statement, so a long statement could overflow the stack. It is afindnow.Test:
skips_many_results_outside_the_rangefilters 50,000 relations down to the last one on a thread with a 2 MiB stack. It aborts with a stack overflow onmain.An explicit stack size for the worker threads
The daemon's tokio runtime (which runs the language server's handlers and its
spawn_blockingthreads) and the rayon pool ofcheckused the 2 MiB default. A stack overflow aborts the process, so for the daemon it kills the server for every connected editor. That is what turned R1 from #892 into a crash.Not every deep recursion can be removed: libpg_query serializes the parse tree recursively, with stack proportional to the depth of the tree. A left-deep expression such as
select 1 + abs(1) + abs(1) + ...is deep while staying valid SQL. Measured on this machine with hover, inlay hints, diagnostics, code actions and formatting on that statement, in a release build:Postgres 15 with its default
max_stack_depthof 2 MB accepts 4,000 terms and rejects 5,000 ("stack depth limit exceeded"). WithWORKER_STACK_SIZE_BYTES = 8 MiB, the default stack of the main thread on Linux, the server handles four times what Postgres accepts by default. The newpgls_cli::runtime()builds every tokio runtime of the CLI and the daemon with it, and the rayon pool ofcheckuses it too. The memory is virtual and only committed as it is used, so idle threads don't cost 8 MiB each.Test:
workers_parse_long_expressionsparses a 2,000-term expression on a worker ofruntime(). In a debug build, where libpg_query's frames are larger, that already overflows tokio's default 2 MiB.Not covered
pgls_workspace'sasync_helperonly runs database futures and keeps the default.