From: | Andres Freund <andres(at)anarazel(dot)de> |
---|---|
To: | Alexander Korotkov <aekorotkov(at)gmail(dot)com> |
Cc: | Robert Haas <robertmhaas(at)gmail(dot)com>, Daniel Gustafsson <daniel(at)yesql(dot)se>, Mikhail Gribkov <youzhick(at)gmail(dot)com>, Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>, Greg Nancarrow <gregn4422(at)gmail(dot)com>, Ivan Panchenko <wao(at)mail(dot)ru>, Teodor Sigaev <teodor(at)sigaev(dot)ru>, Ibrar Ahmed <ibrar(dot)ahmad(at)gmail(dot)com>, vignesh C <vignesh21(at)gmail(dot)com>, Pavel Stehule <pavel(dot)stehule(at)gmail(dot)com>, PostgreSQL Hackers <pgsql-hackers(at)postgresql(dot)org>, Amit Kapila <amit(dot)kapila16(at)gmail(dot)com>, Masahiko Sawada <sawada(dot)mshk(at)gmail(dot)com> |
Subject: | Re: On login trigger: take three |
Date: | 2023-10-10 16:37:30 |
Message-ID: | 20231010163730.qwadzt5f3biprook@awork3.anarazel.de |
Views: | Raw Message | Whole Thread | Download mbox | Resend email |
Thread: | |
Lists: | pgsql-hackers |
Hi,
On 2023-10-10 08:18:46 +0300, Alexander Korotkov wrote:
> @@ -968,7 +969,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
>
> if (!get_db_info(dbtemplate, ShareLock,
> &src_dboid, &src_owner, &src_encoding,
> - &src_istemplate, &src_allowconn,
> + &src_istemplate, &src_allowconn, &src_hasloginevt,
> &src_frozenxid, &src_minmxid, &src_deftablespace,
> &src_collate, &src_ctype, &src_iculocale, &src_icurules, &src_locprovider,
> &src_collversion))
This isn't your fault, but this imo has become unreadable. Think we ought to
move the information about a database to a struct.
> @@ -296,6 +306,13 @@ insert_event_trigger_tuple(const char *trigname, const char *eventname, Oid evtO
> CatalogTupleInsert(tgrel, tuple);
> heap_freetuple(tuple);
>
> + /*
> + * Login event triggers have an additional flag in pg_database to allow
> + * faster lookups in hot codepaths. Set the flag unless already True.
> + */
> + if (strcmp(eventname, "login") == 0)
> + SetDatatabaseHasLoginEventTriggers();
It's not really faster lookups, it's no lookups, right?
> /* Depend on owner. */
> recordDependencyOnOwner(EventTriggerRelationId, trigoid, evtOwner);
>
> @@ -357,6 +374,39 @@ filter_list_to_array(List *filterlist)
> return PointerGetDatum(construct_array_builtin(data, l, TEXTOID));
> }
>
> +/*
> + * Set pg_database.dathasloginevt flag for current database indicating that
> + * current database has on login triggers.
> + */
> +void
> +SetDatatabaseHasLoginEventTriggers(void)
> +{
> + /* Set dathasloginevt flag in pg_database */
> + Form_pg_database db;
> + Relation pg_db = table_open(DatabaseRelationId, RowExclusiveLock);
> + HeapTuple tuple;
> +
> + /*
> + * Use shared lock to prevent a conflit with EventTriggerOnLogin() trying
> + * to reset pg_database.dathasloginevt flag. Note that we use
> + * AccessShareLock allowing setters concurently.
> + */
> + LockSharedObject(DatabaseRelationId, MyDatabaseId, 0, AccessShareLock);
That seems like a very odd approach - how does this avoid concurrency issues
with one backend setting and another unsetting the flag? And outside of that,
won't this just lead to concurrently updated tuples?
> + tuple = SearchSysCacheCopy1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
> + if (!HeapTupleIsValid(tuple))
> + elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
> + db = (Form_pg_database) GETSTRUCT(tuple);
> + if (!db->dathasloginevt)
> + {
> + db->dathasloginevt = true;
> + CatalogTupleUpdate(pg_db, &tuple->t_self, tuple);
> + CommandCounterIncrement();
> + }
> + table_close(pg_db, RowExclusiveLock);
> + heap_freetuple(tuple);
> +}
> +
> /*
> * ALTER EVENT TRIGGER foo ENABLE|DISABLE|ENABLE ALWAYS|REPLICA
> */
> @@ -391,6 +441,10 @@ AlterEventTrigger(AlterEventTrigStmt *stmt)
>
> CatalogTupleUpdate(tgrel, &tup->t_self, tup);
>
> + if (namestrcmp(&evtForm->evtevent, "login") == 0 &&
> + tgenabled != TRIGGER_DISABLED)
> + SetDatatabaseHasLoginEventTriggers();
> +
> InvokeObjectPostAlterHook(EventTriggerRelationId,
> trigoid, 0);
>
> @@ -557,7 +611,7 @@ filter_event_trigger(CommandTag tag, EventTriggerCacheItem *item)
> static List *
> EventTriggerCommonSetup(Node *parsetree,
> EventTriggerEvent event, const char *eventstr,
> - EventTriggerData *trigdata)
> + EventTriggerData *trigdata, bool unfiltered)
> {
> CommandTag tag;
> List *cachelist;
> @@ -582,10 +636,15 @@ EventTriggerCommonSetup(Node *parsetree,
> {
> CommandTag dbgtag;
>
> - dbgtag = CreateCommandTag(parsetree);
> + if (event == EVT_Login)
> + dbgtag = CMDTAG_LOGIN;
> + else
> + dbgtag = CreateCommandTag(parsetree);
> +
> if (event == EVT_DDLCommandStart ||
> event == EVT_DDLCommandEnd ||
> - event == EVT_SQLDrop)
> + event == EVT_SQLDrop ||
> + event == EVT_Login)
> {
> if (!command_tag_event_trigger_ok(dbgtag))
> elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag));
> @@ -604,7 +663,10 @@ EventTriggerCommonSetup(Node *parsetree,
> return NIL;
>
> /* Get the command tag. */
> - tag = CreateCommandTag(parsetree);
> + if (event == EVT_Login)
> + tag = CMDTAG_LOGIN;
> + else
> + tag = CreateCommandTag(parsetree);
Seems this bit should instead be in a function, given that you have it in
multiple places.
>
> +/*
> + * Fire login event triggers if any are present. The dathasloginevt
> + * pg_database flag is left when an event trigger is dropped, to avoid
> + * complicating the codepath in the case of multiple event triggers. This
> + * function will instead unset the flag if no trigger is defined.
> + */
> +void
> +EventTriggerOnLogin(void)
> +{
> + List *runlist;
> + EventTriggerData trigdata;
> +
> + /*
> + * See EventTriggerDDLCommandStart for a discussion about why event
> + * triggers are disabled in single user mode or via a GUC. We also need a
> + * database connection (some background workers doesn't have it).
> + */
> + if (!IsUnderPostmaster || !event_triggers ||
> + !OidIsValid(MyDatabaseId) || !MyDatabaseHasLoginEventTriggers)
> + return;
> +
> + StartTransactionCommand();
> + runlist = EventTriggerCommonSetup(NULL,
> + EVT_Login, "login",
> + &trigdata, false);
> +
> + if (runlist != NIL)
> + {
> + /*
> + * Event trigger execution may require an active snapshot.
> + */
> + PushActiveSnapshot(GetTransactionSnapshot());
> +
> + /* Run the triggers. */
> + EventTriggerInvoke(runlist, &trigdata);
> +
> + /* Cleanup. */
> + list_free(runlist);
> +
> + PopActiveSnapshot();
> + }
> + /*
> + * There is no active login event trigger, but our pg_database.dathasloginevt was set.
> + * Try to unset this flag. We use the lock to prevent concurrent
> + * SetDatatabaseHasLoginEventTriggers(), but we don't want to hang the
> + * connection waiting on the lock. Thus, we are just trying to acquire
> + * the lock conditionally.
> + */
> + else if (ConditionalLockSharedObject(DatabaseRelationId, MyDatabaseId,
> + 0, AccessExclusiveLock))
Eek. Why are we doing it this way? I think this is a seriously bad
idea. Maybe it's obvious to you, but it seems much more reasonable to make the
pg_database column an integer and count the number of login event
triggers. When 0, then we don't need to look for login event triggers.
Greetings,
Andres Freund
From | Date | Subject | |
---|---|---|---|
Next Message | jinser | 2023-10-10 17:11:15 | Fix typo in psql zh_CN.po |
Previous Message | Alvaro Herrera | 2023-10-10 15:15:36 | Re: Fwd: Advice about preloaded libraries |