mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Check the session's backend_start for cancelling or terminating sessions (#48929)
## Context Related to database connections - specifically for cancelling queries or terminating sessions PIDs can be re-used, so a more accurate check is to use both PID and `backend_start` to uniquely identify the session to cancel or terminate <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved query cancellation and session termination reliability by verifying the active database session before taking action. * Prevented actions from affecting a different session that reused the same process ID. * Added clearer guidance to refresh when a session has changed or is no longer available. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
cdfb5b310f
commit
75b90c5de1
7 files changed
+56
-9
No files matched your search
@@ -158,6 +158,7 @@ export const ActivityRow = ({
|
||||
try {
|
||||
await cancelQuery({
|
||||
pid: activity.pid,
|
||||
backendStart: activity.backend_start,
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
})
|
||||
@@ -183,6 +184,7 @@ export const ActivityRow = ({
|
||||
try {
|
||||
await terminateSession({
|
||||
pid: activity.pid,
|
||||
backendStart: activity.backend_start,
|
||||
projectRef: project?.ref,
|
||||
connectionString: project?.connectionString,
|
||||
})
|
||||
|
||||
+1
@@ -31,6 +31,7 @@ const activity = (overrides: ActivityOverrides = {}): DatabaseActivity => ({
|
||||
application_name: 'test',
|
||||
blocked_by: [],
|
||||
query: 'select 1',
|
||||
backend_start: NOW,
|
||||
query_start: null,
|
||||
transaction_start: null,
|
||||
state_change: null,
|
||||
|
||||
@@ -67,6 +67,7 @@ export type DatabaseActivity = {
|
||||
application_name: string
|
||||
blocked_by: number[]
|
||||
query: string | null
|
||||
backend_start: string
|
||||
query_start: string | null
|
||||
transaction_start: string | null
|
||||
state_change: string | null
|
||||
|
||||
@@ -4,22 +4,34 @@ import { toast } from 'sonner'
|
||||
|
||||
import { sqlKeys } from './keys'
|
||||
import { executeSql } from '@/data/sql/execute-sql-mutation'
|
||||
import type { ResponseError, UseCustomMutationOptions } from '@/types'
|
||||
import { ResponseError, type UseCustomMutationOptions } from '@/types'
|
||||
|
||||
type QueryCancelVariables = {
|
||||
pid: number
|
||||
/** Pass the pid's last-known backend_start to guard against a reused pid matching an unrelated session */
|
||||
backendStart?: string
|
||||
projectRef?: string
|
||||
connectionString?: string | null
|
||||
}
|
||||
|
||||
async function cancelQuery({ pid, projectRef, connectionString }: QueryCancelVariables) {
|
||||
const sql = getCancelQuerySQL({ pid })
|
||||
async function cancelQuery({
|
||||
pid,
|
||||
backendStart,
|
||||
projectRef,
|
||||
connectionString,
|
||||
}: QueryCancelVariables) {
|
||||
const sql = getCancelQuerySQL({ pid, backendStart })
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['cancel-query'],
|
||||
})
|
||||
if (backendStart !== undefined && result.length === 0) {
|
||||
throw new ResponseError(
|
||||
`Session (PID: ${pid}) has already changed since this list was loaded. Refresh and try again.`
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -4,26 +4,34 @@ import { toast } from 'sonner'
|
||||
|
||||
import { sqlKeys } from './keys'
|
||||
import { executeSql } from '@/data/sql/execute-sql-mutation'
|
||||
import type { ResponseError, UseCustomMutationOptions } from '@/types'
|
||||
import { ResponseError, type UseCustomMutationOptions } from '@/types'
|
||||
|
||||
type SessionTerminateVariables = {
|
||||
pid: number
|
||||
/** Pass the pid's last-known backend_start to guard against a reused pid matching an unrelated session */
|
||||
backendStart?: string
|
||||
projectRef?: string
|
||||
connectionString?: string | null
|
||||
}
|
||||
|
||||
export async function terminateSession({
|
||||
pid,
|
||||
backendStart,
|
||||
projectRef,
|
||||
connectionString,
|
||||
}: SessionTerminateVariables) {
|
||||
const sql = getTerminateSessionSQL({ pid })
|
||||
const sql = getTerminateSessionSQL({ pid, backendStart })
|
||||
const { result } = await executeSql({
|
||||
projectRef,
|
||||
connectionString,
|
||||
sql,
|
||||
queryKey: ['terminate-session'],
|
||||
})
|
||||
if (backendStart !== undefined && result.length === 0) {
|
||||
throw new ResponseError(
|
||||
`Session (PID: ${pid}) has already changed since this list was loaded. Refresh and try again.`
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ select
|
||||
a.query,
|
||||
a.wait_event_type,
|
||||
a.wait_event,
|
||||
a.backend_start,
|
||||
a.xact_start as transaction_start,
|
||||
a.query_start,
|
||||
a.state_change,
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
import { literal, safeSql, type SafeSqlFragment } from '../../../pg-format'
|
||||
|
||||
export const getCancelQuerySQL = ({ pid }: { pid: number }): SafeSqlFragment => {
|
||||
return safeSql`select pg_cancel_backend(${literal(pid)})`
|
||||
export const getCancelQuerySQL = ({
|
||||
pid,
|
||||
backendStart,
|
||||
}: {
|
||||
pid: number
|
||||
backendStart?: string
|
||||
}): SafeSqlFragment => {
|
||||
return safeSql`
|
||||
select pg_cancel_backend(pid) as cancelled
|
||||
from pg_stat_activity
|
||||
where pid = ${literal(pid)}
|
||||
${backendStart ? safeSql`and backend_start = ${literal(backendStart)}::timestamptz` : safeSql``}
|
||||
`
|
||||
}
|
||||
|
||||
export const getTerminateSessionSQL = ({ pid }: { pid: number }): SafeSqlFragment => {
|
||||
return safeSql`select pg_terminate_backend(${literal(pid)})`
|
||||
export const getTerminateSessionSQL = ({
|
||||
pid,
|
||||
backendStart,
|
||||
}: {
|
||||
pid: number
|
||||
backendStart?: string
|
||||
}): SafeSqlFragment => {
|
||||
return safeSql`
|
||||
select pg_terminate_backend(pid) as terminated
|
||||
from pg_stat_activity
|
||||
where pid = ${literal(pid)}
|
||||
${backendStart ? safeSql`and backend_start = ${literal(backendStart)}::timestamptz` : safeSql``}
|
||||
`
|
||||
}
|
||||
Reference in new issue
Block a user