Skip to content

Commit 14ee853

Browse files
committed
Fix hint stack corruption on ERROR when setting GUCs from Set hints
The planner hook uses a PG_TRY/PG_CATCH block to handle the hint stack available for a given query. push_hint() is in charge of updating the stack with a new set in the planner hook, and pop_hint() would reset the stack to its previous state. It happens that the code was not cleaning up the stack between push_hint() and the PG_TRY/PG_CATCH block, and that it was possible to trigger ERRORs, corrupting the hint stack hold in a backend. Based on report #141, setting pg_hint_plan.parse_messages TO ERROR with an incorrect Set hint would be one case where the stack would get corrupted, because the GUCs defined in such hints are loaded after the hint stack update and before PG_TRY(). Any errors happening before PG_TRY() would be enough to corrupt the stack, like a palloc() failing on OOM, though this is very unlikely. This commit takes care of this bug by removing completely the window where the stack could get corrupted, by moving PG_TRY a little bit earlier. A regression test is added, provided by reporter. The patch is from me. Per report #141, from Egor Chindyaskin. Backpatch-through: 11
1 parent db5bd83 commit 14ee853

File tree

3 files changed

+57
-32
lines changed

3 files changed

+57
-32
lines changed

expected/pg_hint_plan.out

+16
Original file line numberDiff line numberDiff line change
@@ -9119,6 +9119,22 @@ error hint:
91199119
----+-----
91209120
(0 rows)
91219121

9122+
set pg_hint_plan.parse_messages to 'ERROR';
9123+
-- Force an error before running the planner hook, when forcing the Set hints.
9124+
/*+ Set(work_mem "foo") */ SELECT 1;
9125+
ERROR: invalid value for parameter "work_mem": "foo"
9126+
/*+ SeqScan(t1) */ SELECT * FROM t1 LIMIT 0;
9127+
LOG: pg_hint_plan:
9128+
used hint:
9129+
SeqScan(t1)
9130+
not used hint:
9131+
duplication hint:
9132+
error hint:
9133+
9134+
id | val
9135+
----+-----
9136+
(0 rows)
9137+
91229138
set pg_hint_plan.message_level to 'DEBUG1';
91239139
set pg_hint_plan.parse_messages to 'NOTICE';
91249140
/*+ SeqScan( */ SELECT 1;

pg_hint_plan.c

+37-32
Original file line numberDiff line numberDiff line change
@@ -3117,43 +3117,15 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
31173117

31183118
/*
31193119
* Push new hint struct to the hint stack to disable previous hint context.
3120+
* There should be no ERROR-level failures until we begin the
3121+
* PG_TRY/PG_CATCH block below to ensure a consistent stack handling all
3122+
* the time.
31203123
*/
31213124
push_hint(hstate);
31223125

31233126
/* Set scan enforcement here. */
31243127
save_nestlevel = NewGUCNestLevel();
31253128

3126-
/* Apply Set hints, then save it as the initial state */
3127-
setup_guc_enforcement(current_hint_state->set_hints,
3128-
current_hint_state->num_hints[HINT_TYPE_SET],
3129-
current_hint_state->context);
3130-
3131-
current_hint_state->init_scan_mask = get_current_scan_mask();
3132-
current_hint_state->init_join_mask = get_current_join_mask();
3133-
current_hint_state->init_min_para_tablescan_size =
3134-
min_parallel_table_scan_size;
3135-
current_hint_state->init_min_para_indexscan_size =
3136-
min_parallel_index_scan_size;
3137-
current_hint_state->init_paratup_cost = parallel_tuple_cost;
3138-
current_hint_state->init_parasetup_cost = parallel_setup_cost;
3139-
3140-
/*
3141-
* max_parallel_workers_per_gather should be non-zero here if Workers hint
3142-
* is specified.
3143-
*/
3144-
if (max_hint_nworkers > 0 && max_parallel_workers_per_gather < 1)
3145-
set_config_int32_option("max_parallel_workers_per_gather",
3146-
1, current_hint_state->context);
3147-
current_hint_state->init_nworkers = max_parallel_workers_per_gather;
3148-
3149-
if (debug_level > 1)
3150-
{
3151-
ereport(pg_hint_plan_debug_message_level,
3152-
(errhidestmt(msgqno != qno),
3153-
errmsg("pg_hint_plan%s: planner", qnostr)));
3154-
msgqno = qno;
3155-
}
3156-
31573129
/*
31583130
* The planner call below may replace current_hint_str. Store and restore
31593131
* it so that the subsequent planning in the upper level doesn't get
@@ -3165,10 +3137,43 @@ pg_hint_plan_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
31653137

31663138
/*
31673139
* Use PG_TRY mechanism to recover GUC parameters and current_hint_state to
3168-
* the state when this planner started when error occurred in planner.
3140+
* the state when this planner started when error occurred in planner. We
3141+
* do this here to minimize the window where the hints currently pushed on
3142+
* the stack could not be popped out of it.
31693143
*/
31703144
PG_TRY();
31713145
{
3146+
/* Apply Set hints, then save it as the initial state */
3147+
setup_guc_enforcement(current_hint_state->set_hints,
3148+
current_hint_state->num_hints[HINT_TYPE_SET],
3149+
current_hint_state->context);
3150+
3151+
current_hint_state->init_scan_mask = get_current_scan_mask();
3152+
current_hint_state->init_join_mask = get_current_join_mask();
3153+
current_hint_state->init_min_para_tablescan_size =
3154+
min_parallel_table_scan_size;
3155+
current_hint_state->init_min_para_indexscan_size =
3156+
min_parallel_index_scan_size;
3157+
current_hint_state->init_paratup_cost = parallel_tuple_cost;
3158+
current_hint_state->init_parasetup_cost = parallel_setup_cost;
3159+
3160+
/*
3161+
* max_parallel_workers_per_gather should be non-zero here if Workers
3162+
* hint is specified.
3163+
*/
3164+
if (max_hint_nworkers > 0 && max_parallel_workers_per_gather < 1)
3165+
set_config_int32_option("max_parallel_workers_per_gather",
3166+
1, current_hint_state->context);
3167+
current_hint_state->init_nworkers = max_parallel_workers_per_gather;
3168+
3169+
if (debug_level > 1)
3170+
{
3171+
ereport(pg_hint_plan_debug_message_level,
3172+
(errhidestmt(msgqno != qno),
3173+
errmsg("pg_hint_plan%s: planner", qnostr)));
3174+
msgqno = qno;
3175+
}
3176+
31723177
if (prev_planner)
31733178
result = (*prev_planner) (parse, cursorOptions, boundParams);
31743179
else

sql/pg_hint_plan.sql

+4
Original file line numberDiff line numberDiff line change
@@ -1143,6 +1143,10 @@ set client_min_messages to 'DEBUG1';
11431143
set pg_hint_plan.debug_level to 'verbose';
11441144
/*+ SeqScan( */ SELECT 1;
11451145
/*+ SeqScan(t1) */ SELECT * FROM t1 LIMIT 0;
1146+
set pg_hint_plan.parse_messages to 'ERROR';
1147+
-- Force an error before running the planner hook, when forcing the Set hints.
1148+
/*+ Set(work_mem "foo") */ SELECT 1;
1149+
/*+ SeqScan(t1) */ SELECT * FROM t1 LIMIT 0;
11461150
set pg_hint_plan.message_level to 'DEBUG1';
11471151
set pg_hint_plan.parse_messages to 'NOTICE';
11481152
/*+ SeqScan( */ SELECT 1;

0 commit comments

Comments
 (0)