From 32ebe63cb31457a6f0e5bf06fa99b91393d6d7c0 Mon Sep 17 00:00:00 2001 From: Julien Rouhaud Date: Sat, 3 Sep 2022 03:13:47 +0800 Subject: [PATCH 2/3] FIXUP 0002-session-variables --- src/backend/commands/session_variable.c | 137 +++++++++++++----------- 1 file changed, 73 insertions(+), 64 deletions(-) diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c index d5febfef75..711ebce76e 100644 --- a/src/backend/commands/session_variable.c +++ b/src/backend/commands/session_variable.c @@ -45,25 +45,26 @@ #include "utils/typcache.h" /* - * Values of session variables are stored in local memory, in - * sessionvars hash table in binary format and the life cycle of - * session variable can be longer than transaction. Then we - * need to solve following issues: + * Values of session variables are stored in the backend local memory, + * in sessionvars hash table in binary format, in a dedicated memory + * context SVariableMemoryContext. A session variable value can stay + * valid for longer than a transaction. To make sure that the + * underlying memory is freed, we need to solve following issues: * - * - We need to free local memory when variable is dropped (in current - * transaction in current session or by other sessions (other - * users). There is a request to protect content against - * possibly aborted the DROP VARIABLE command. So the purging should - * not be executed immediately. It should be postponed until the end - * of the transaction. + * - We need to free local memory when the variable is droppedn, + * whether in the current transaction in the current session or by + * other sessions (other users). To protect the content against + * possibly rollbacked DROP VARIABLE commands, the memory shouldn't + * be freed immediately but be postponed until the end of the + * transaction. * - * - The session variable can be dropped explicitly (by DROP VARIABLE) - * command or implicitly (by using ON COMMIT DROP clause). The - * purging memory at transaction end can be requested implicitly - * too (by using the ON TRANSACTION END clause). + * - The session variable can be dropped explicitly (by DROP VARIABLE + * command) or implicitly (using ON COMMIT DROP clause), and the + * value can be implicitly removed (using the ON TRANSACTION END + * clause). In all those cases the memory should be freed at the + * transaction end. * - * The values of session variables are stored in hash table - * sessionvars. We maintain four queues (implemented as list + * To achieve that, we maintain 4 queues (implemented as list * of variable oid, list of RecheckVariableRequests * (requests are created in reaction on sinval) and lists * of actions). List of actions are used whe we need to calculate @@ -140,20 +141,15 @@ static List *xact_reset_varids = NIL; /* * When the session variable is dropped we need to free local memory. The * session variable can be dropped by current session, but it can be - * dropped by other's sessions too, so we have to watc sinval message. + * dropped by other's sessions too, so we have to watch sinval message. * But because we don't want to free local memory immediately, we need to * hold list of possibly dropped session variables and at the end of * transaction, we check session variables from this list against system * catalog. This check can be postponed into next transaction if - * current transactions is in aborted state. When the check is postponed - * to the next transaction, then have to be executed just once time. - * Saved lxid in synced_lxid is safeguard against repeated useles - * recheck inside one transaction. + * current transactions is in aborted state. */ static List *xact_recheck_varids = NIL; -static LocalTransactionId synced_lxid = InvalidLocalTransactionId; - typedef struct SVariableData { Oid varid; /* pg_variable OID of this sequence (hash key) */ @@ -176,7 +172,8 @@ typedef struct SVariableData * acceptable for debug purposes (and it is necessary, because * sometimes we cannot to access to catalog. */ - char *name; /* session variable name (at time of initialization) */ + char *name; /* session variable name (at time of + initialization) */ char *nsname; /* session variable schema name */ bool isnull; @@ -193,6 +190,13 @@ typedef struct SVariableData void *domain_check_extra; LocalTransactionId domain_check_extra_lxid; + /* + * Top level local transaction id of the last transaction that dropped the + * variable if any. We need this information to avoid freeing memory for + * variable dropped by the local backend that may be rollbacked. + */ + LocalTransactionId drop_lxid; + TupleDesc tupdesc; /* for row type we save actual tupdesc * * at save time, for conversion to uptime * * tupdesc in read time */ @@ -217,8 +221,6 @@ static HTAB *sessionvars_types = NULL; /* hash table for type fingerprints of se static MemoryContext SVariableMemoryContext = NULL; -static bool first_time = true; - static void init_session_variable(SVariable svar, Variable *var); static void register_session_variable_xact_action(Oid varid, SVariableXActAction action); @@ -360,7 +362,7 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue) /* * although it there is low probability, we have to iterate - * over all actively used session variables, because hashvalue + * over all locally set session variables, because hashvalue * is not unique identifier. */ } @@ -397,9 +399,7 @@ is_session_variable_valid(SVariable svar) /* * The possible invalidated variables (in memory) are checked * against system catalog. This routine is called before - * any read or any write from/to session variable, but it - * should be called only once per transaction. The synced_lxid - * is used as protection against repeated call. + * any read or any write from/to session variable. */ static void sync_sessionvars_all() @@ -407,14 +407,8 @@ sync_sessionvars_all() SVariable svar; ListCell *l; - if (synced_lxid == MyProc->lxid) - return; - if (!xact_recheck_varids) - { - synced_lxid = MyProc->lxid; return; - } /* * sessionvars is null after DISCARD VARIABLES. @@ -425,14 +419,13 @@ sync_sessionvars_all() { list_free(xact_recheck_varids); xact_recheck_varids = NIL; - synced_lxid = MyProc->lxid; return; } elog(DEBUG1, "effective call of sync_sessionvars_all()"); /* - * This routine is called before any reading. So the + * This routine is called before any reading, so the * session should be in transaction state. This is required * for access to system catalog. */ @@ -446,18 +439,27 @@ sync_sessionvars_all() svar = (SVariable) hash_search(sessionvars, &varid, HASH_FIND, &found); + /* + * Remove invalid variables, but don't touch variables that were + * dropped by the current top level local transaction, as there's no + * guarantee that the transaction will be committed. Such variables + * will be removed in the next transaction if needed. + */ if (found) { + /* + * If this is a variable dropped by the current transaction, ignore + * it and keep the oid to recheck in the next transaction. + */ + if (svar->drop_lxid == MyProc->lxid) + continue; + if (!is_session_variable_valid(svar)) remove_session_variable(svar); } - } - - list_free(xact_recheck_varids); - xact_recheck_varids = NIL; - /* Don't repeat this check in this transaction more time */ - synced_lxid = MyProc->lxid; + xact_recheck_varids = foreach_delete_current(xact_recheck_varids, l); + } } /* @@ -468,20 +470,21 @@ create_sessionvars_hashtables(void) { HASHCTL vars_ctl; + Assert(!sessionvars); + /* set callbacks */ - if (first_time) + if (!SVariableMemoryContext) { /* Read sinval messages */ CacheRegisterSyscacheCallback(VARIABLEOID, pg_variable_cache_callback, (Datum) 0); - /* needs its own long lived memory context */ + + /* We need our own long lived memory context */ SVariableMemoryContext = AllocSetContextCreate(TopMemoryContext, "session variables", ALLOCSET_START_SMALL_SIZES); - - first_time = false; } Assert(SVariableMemoryContext); @@ -946,7 +949,6 @@ SetSessionVariable(Oid varid, Datum value, bool isNull) SVariable svar; bool found; - if (!sessionvars) create_sessionvars_hashtables(); @@ -1238,24 +1240,31 @@ RemoveSessionVariable(Oid varid) svar = (SVariable) hash_search(sessionvars, &varid, HASH_FIND, &found); - /* - * For variables that are not purged by default we need to - * register SVAR_ON_COMMIT_RESET action. This action can - * be reverted by ABORT of DROP VARIABLE command. - */ - if (found && !svar->eox_reset) + if (found) { + /* Save the current top level local transaction id to make sure we + * don't automatically remove the local variable storage in + * sync_sessionvars_all, as the DROP VARIABLE will send an + * invalidation message. + */ + Assert(LocalTransactionIdIsValid(MyProc->lxid)); + svar->drop_lxid = MyProc->lxid; + /* - * And we want to enforce variable clearning when this transaction or - * subtransaction will be committed (we don't need to wait for - * sinval message). The cleaning action for one session variable - * can be repeated in the action list without causing any problem, - * so we don't need to ensure uniqueness. We need a different action - * from RESET, because RESET is executed on any transaction end, - * but we want to execute cleaning only when the current transaction - * will be committed. + * For variables that are not purged by default we need to register + * an SVAR_ON_COMMIT_RESET action to free the local memory for this + * variable when this transaction or subtransaction is committed + * (we don't need to wait for sinval message). The cleaning action + * for one session variable can be repeated in the action list + * without causing any problem, so we don't need to ensure + * uniqueness. We need a different action from RESET, because + * RESET is executed on any transaction end, but we want to execute + * cleaning only when the current transaction will be committed. + * This action can be reverted by ABORT of DROP VARIABLE command. */ - register_session_variable_xact_action(varid, SVAR_ON_COMMIT_RESET); + if (!svar->eox_reset) + register_session_variable_xact_action(varid, + SVAR_ON_COMMIT_RESET); } } } -- 2.37.0