supabase-js v2 abortSignal cancels the HTTP request, but does PostgREST stop the running Postgres statement?
0 reputation · 30 Apr 2023, 07:38 UTC
0 reputation · 30 Apr 2023, 07:38 UTC
After migrating to supabase-js v2, I want to use the documented .abortSignal(signal) option on query builders (with AbortSignal.timeout(ms)) to bound long-running PostgREST and RPC calls. The client-side behavior is clear: the pending promise rejects with a fetch-level AbortError. What I cannot determine is what happens on the server side once the HTTP connection is torn down.
The documentation does not state whether PostgREST cancels the in-flight SQL statement when the client disconnects, or whether the backend keeps executing until completion. If the query keeps running, it still consumes a pooled connection and may hold locks, which matters for capacity planning on a hosted project. The fallback would be bounding work with ALTER ROLE ... SET statement_timeout for roles like authenticated, but I would rather not layer both mechanisms blindly if client aborts already propagate.
pg_stat_activity inspection after an aborted pg_sleep RPC a reliable way to verify this on my project, and does the answer differ for self-hosted PostgREST versions?statement_timeout the recommended ceiling regardless of client-side aborts?32775 reputation · 30 Apr 2023, 19:06 UTC
Short answer: no. In supabase-js v2, .abortSignal(signal) aborts the client-side fetch, so the pending promise rejects with an AbortError and the HTTP connection is torn down. It does not send a cancellation to PostgreSQL. The statement generally keeps running on the backend until it finishes, hits a server-side timeout, or is cancelled by an operator. Treat that as the expected shape of the behavior, but verify it on your own deployment before relying on it.
Confirmed: the abort is a client and HTTP-layer concern. The promise settles and the socket closes. That is standard AbortController/fetch behavior.
Likely, but version- and deployment-dependent: PostgREST does not, on detecting a dropped client connection, issue a PostgreSQL CANCEL for the running statement. The backend session stays in pg_stat_activity with state active, holding its pooled connection and any locks it acquired. This is the behavior most commonly reported for hosted Supabase projects, but it is not something I can confirm for every PostgREST release or hosting configuration, so treat it as a hypothesis to test.
create or replace function test_abort()
returns void language plpgsql as $$
begin
perform pg_sleep(30);
end;
$$;
supabase.rpc('test_abort').abortSignal(AbortSignal.timeout(1000)).select pid, state, wait_event_type,
now() - query_start as runtime, query
from pg_stat_activity
where query ilike '%test_abort%'
and pid <> pg_backend_pid();
If a row stays active for the full 30 seconds after the client error, the statement was not cancelled. If it vanishes within a second or two, something in your stack is propagating the cancellation and it is worth finding out which layer. Note that pg_stat_activity.query shows the top-level statement text, and with a pooler in front the pid you observe may be a pooled backend rather than a per-request session.
Because a client abort is not a reliable server-side resource control, add one. A per-role statement_timeout is the simplest lever and applies to every request made through that role:
alter role authenticated set statement_timeout = '10s';
alter role anon set statement_timeout = '5s';
For an endpoint that legitimately needs longer, scope the limit inside the function with set local statement_timeout rather than raising the role-wide value. Keep the client-side abortSignal as well: it releases the UI early, which is a real user-experience win, but it is a UX control rather than a backend kill switch.
Do your API requests go through a transaction-mode pooler (Supavisor or PgBouncer) or straight to Postgres? Under transaction pooling a long statement holds a server connection out of the pool for its whole duration, so a lower timeout matters more. On direct connections the blast radius is smaller. That detail is the one thing that would let me tighten the numbers.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.