Skip to content

Commit 22f6f2c

Browse files
committed
Improve management of statement timeouts.
Commit f8e5f15 added private state in postgres.c to track whether a statement timeout is running. This seems like bad design to me; timeout.c's private state should be the single source of truth about that. We already fixed one bug associated with failure to keep those states in sync (cf. be42015), and I've got little faith that we won't find more in future. So get rid of postgres.c's local variable by exposing a way to ask timeout.c whether a timeout is running. (Obviously, such an inquiry is subject to race conditions, but it seems fine for the purpose at hand.) To make get_timeout_active() as cheap as possible, add a flag in the per-timeout struct showing whether that timeout is active. This allows some small savings elsewhere in timeout.c, mainly elimination of unnecessary searches of the active_timeouts array. While at it, fix enable_statement_timeout to not call disable_timeout when statement_timeout is 0 and the timeout is not running. This avoids a useless deschedule-and-reschedule-timeouts cycle, which represents a significant savings (at least one kernel call) when there is any other active timeout. Right now, there usually isn't, but there are proposals around to change that. Discussion: https://postgr.es/m/16035-456e6e69ebfd4374@postgresql.org
1 parent 2b2bacd commit 22f6f2c

File tree

3 files changed

+39
-33
lines changed

3 files changed

+39
-33
lines changed

src/backend/tcop/postgres.c

+6-16
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,6 @@ static bool DoingCommandRead = false;
145145
static bool doing_extended_query_message = false;
146146
static bool ignore_till_sync = false;
147147

148-
/*
149-
* Flag to keep track of whether statement timeout timer is active.
150-
*/
151-
static bool stmt_timeout_active = false;
152-
153148
/*
154149
* If an unnamed prepared statement exists, it's stored here.
155150
* We keep it separate from the hashtable kept by commands/prepare.c
@@ -4029,7 +4024,6 @@ PostgresMain(int argc, char *argv[],
40294024
*/
40304025
disable_all_timeouts(false);
40314026
QueryCancelPending = false; /* second to avoid race condition */
4032-
stmt_timeout_active = false;
40334027

40344028
/* Not reading from the client anymore. */
40354029
DoingCommandRead = false;
@@ -4711,14 +4705,14 @@ enable_statement_timeout(void)
47114705

47124706
if (StatementTimeout > 0)
47134707
{
4714-
if (!stmt_timeout_active)
4715-
{
4708+
if (!get_timeout_active(STATEMENT_TIMEOUT))
47164709
enable_timeout_after(STATEMENT_TIMEOUT, StatementTimeout);
4717-
stmt_timeout_active = true;
4718-
}
47194710
}
47204711
else
4721-
disable_timeout(STATEMENT_TIMEOUT, false);
4712+
{
4713+
if (get_timeout_active(STATEMENT_TIMEOUT))
4714+
disable_timeout(STATEMENT_TIMEOUT, false);
4715+
}
47224716
}
47234717

47244718
/*
@@ -4727,10 +4721,6 @@ enable_statement_timeout(void)
47274721
static void
47284722
disable_statement_timeout(void)
47294723
{
4730-
if (stmt_timeout_active)
4731-
{
4724+
if (get_timeout_active(STATEMENT_TIMEOUT))
47324725
disable_timeout(STATEMENT_TIMEOUT, false);
4733-
4734-
stmt_timeout_active = false;
4735-
}
47364726
}

src/backend/utils/misc/timeout.c

+32-17
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ typedef struct timeout_params
2727
{
2828
TimeoutId index; /* identifier of timeout reason */
2929

30-
/* volatile because it may be changed from the signal handler */
30+
/* volatile because these may be changed from the signal handler */
31+
volatile bool active; /* true if timeout is in active_timeouts[] */
3132
volatile bool indicator; /* true if timeout has occurred */
3233

3334
/* callback function for timeout, or NULL if timeout not registered */
@@ -105,6 +106,9 @@ insert_timeout(TimeoutId id, int index)
105106
elog(FATAL, "timeout index %d out of range 0..%d", index,
106107
num_active_timeouts);
107108

109+
Assert(!all_timeouts[id].active);
110+
all_timeouts[id].active = true;
111+
108112
for (i = num_active_timeouts - 1; i >= index; i--)
109113
active_timeouts[i + 1] = active_timeouts[i];
110114

@@ -125,6 +129,9 @@ remove_timeout_index(int index)
125129
elog(FATAL, "timeout index %d out of range 0..%d", index,
126130
num_active_timeouts - 1);
127131

132+
Assert(active_timeouts[index]->active);
133+
active_timeouts[index]->active = false;
134+
128135
for (i = index + 1; i < num_active_timeouts; i++)
129136
active_timeouts[i - 1] = active_timeouts[i];
130137

@@ -147,9 +154,8 @@ enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time)
147154
* If this timeout was already active, momentarily disable it. We
148155
* interpret the call as a directive to reschedule the timeout.
149156
*/
150-
i = find_active_timeout(id);
151-
if (i >= 0)
152-
remove_timeout_index(i);
157+
if (all_timeouts[id].active)
158+
remove_timeout_index(find_active_timeout(id));
153159

154160
/*
155161
* Find out the index where to insert the new timeout. We sort by
@@ -349,6 +355,7 @@ InitializeTimeouts(void)
349355
for (i = 0; i < MAX_TIMEOUTS; i++)
350356
{
351357
all_timeouts[i].index = i;
358+
all_timeouts[i].active = false;
352359
all_timeouts[i].indicator = false;
353360
all_timeouts[i].timeout_handler = NULL;
354361
all_timeouts[i].start_time = 0;
@@ -524,8 +531,6 @@ enable_timeouts(const EnableTimeoutParams *timeouts, int count)
524531
void
525532
disable_timeout(TimeoutId id, bool keep_indicator)
526533
{
527-
int i;
528-
529534
/* Assert request is sane */
530535
Assert(all_timeouts_initialized);
531536
Assert(all_timeouts[id].timeout_handler != NULL);
@@ -534,9 +539,8 @@ disable_timeout(TimeoutId id, bool keep_indicator)
534539
disable_alarm();
535540

536541
/* Find the timeout and remove it from the active list. */
537-
i = find_active_timeout(id);
538-
if (i >= 0)
539-
remove_timeout_index(i);
542+
if (all_timeouts[id].active)
543+
remove_timeout_index(find_active_timeout(id));
540544

541545
/* Mark it inactive, whether it was active or not. */
542546
if (!keep_indicator)
@@ -571,13 +575,11 @@ disable_timeouts(const DisableTimeoutParams *timeouts, int count)
571575
for (i = 0; i < count; i++)
572576
{
573577
TimeoutId id = timeouts[i].id;
574-
int idx;
575578

576579
Assert(all_timeouts[id].timeout_handler != NULL);
577580

578-
idx = find_active_timeout(id);
579-
if (idx >= 0)
580-
remove_timeout_index(idx);
581+
if (all_timeouts[id].active)
582+
remove_timeout_index(find_active_timeout(id));
581583

582584
if (!timeouts[i].keep_indicator)
583585
all_timeouts[id].indicator = false;
@@ -595,6 +597,8 @@ disable_timeouts(const DisableTimeoutParams *timeouts, int count)
595597
void
596598
disable_all_timeouts(bool keep_indicators)
597599
{
600+
int i;
601+
598602
disable_alarm();
599603

600604
/*
@@ -613,15 +617,26 @@ disable_all_timeouts(bool keep_indicators)
613617

614618
num_active_timeouts = 0;
615619

616-
if (!keep_indicators)
620+
for (i = 0; i < MAX_TIMEOUTS; i++)
617621
{
618-
int i;
619-
620-
for (i = 0; i < MAX_TIMEOUTS; i++)
622+
all_timeouts[i].active = false;
623+
if (!keep_indicators)
621624
all_timeouts[i].indicator = false;
622625
}
623626
}
624627

628+
/*
629+
* Return true if the timeout is active (enabled and not yet fired)
630+
*
631+
* This is, of course, subject to race conditions, as the timeout could fire
632+
* immediately after we look.
633+
*/
634+
bool
635+
get_timeout_active(TimeoutId id)
636+
{
637+
return all_timeouts[id].active;
638+
}
639+
625640
/*
626641
* Return the timeout's I've-been-fired indicator
627642
*

src/include/utils/timeout.h

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ extern void disable_timeouts(const DisableTimeoutParams *timeouts, int count);
8080
extern void disable_all_timeouts(bool keep_indicators);
8181

8282
/* accessors */
83+
extern bool get_timeout_active(TimeoutId id);
8384
extern bool get_timeout_indicator(TimeoutId id, bool reset_indicator);
8485
extern TimestampTz get_timeout_start_time(TimeoutId id);
8586
extern TimestampTz get_timeout_finish_time(TimeoutId id);

0 commit comments

Comments
 (0)