Re: On login trigger: take three

From: Andres Freund <andres(at)anarazel(dot)de>
To: Greg Nancarrow <gregn4422(at)gmail(dot)com>
Cc: Daniel Gustafsson <daniel(at)yesql(dot)se>, 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: 2022-03-12 02:46:52
Message-ID: 20220312024652.lvgehszwke4hhove@alap3.anarazel.de
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

On 2022-02-15 21:07:15 +1100, Greg Nancarrow wrote:
> Subject: [PATCH v25] Add a new "login" event and login event trigger support.
>
> The login event occurs when a user logs in to the system.

I think this needs a HUGE warning in the docs about most event triggers
needing to check pg_is_in_recovery() or breaking hot standby. And certainly
the example given needs to include an pg_is_in_recovery() check.

> + <para>
> + The <literal>login</literal> event occurs when a user logs in to the
> + system.
> + Any bugs in a trigger procedure for this event may prevent successful
> + login to the system. Such bugs may be fixed after first restarting the
> + system in single-user mode (as event triggers are disabled in this mode).
> + See the <xref linkend="app-postgres"/> reference page for details about
> + using single-user mode.
> + </para>

I'm strongly against adding any new dependencies on single user mode.

A saner approach might be a superuser-only GUC that can be set as part of the
connection data (e.g. PGOPTIONS='-c ignore_login_event_trigger=true').

> @@ -293,6 +297,27 @@ insert_event_trigger_tuple(const char *trigname, const char *eventname, Oid evtO
> CatalogTupleInsert(tgrel, tuple);
> heap_freetuple(tuple);
>
> + if (strcmp(eventname, "login") == 0)
> + {
> + Form_pg_database db;
> + Relation pg_db = table_open(DatabaseRelationId, RowExclusiveLock);
> +
> + /* Set dathasloginevt flag in pg_database */
> + 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);
> + }
> + else
> + CacheInvalidateRelcacheByTuple(tuple);
> + table_close(pg_db, RowExclusiveLock);
> + heap_freetuple(tuple);
> + }
> +
> /* Depend on owner. */
> recordDependencyOnOwner(EventTriggerRelationId, trigoid, evtOwner);

Maybe I am confused, but isn't that CacheInvalidateRelcacheByTuple call
*entirely* bogus? CacheInvalidateRelcacheByTuple() expects a pg_class tuple,
but you're passing in a pg_database tuple? And what is relcache invalidation
even supposed to achieve here?

I think this should mention that ->dathasloginevt is unset on login when
appropriate.

> +/*
> + * Fire login event triggers.
> + */
> +void
> +EventTriggerOnLogin(void)
> +{
> + List *runlist;
> + EventTriggerData trigdata;
> +
> + /*
> + * See EventTriggerDDLCommandStart for a discussion about why event
> + * triggers are disabled in single user mode.
> + */
> + if (!IsUnderPostmaster || !OidIsValid(MyDatabaseId))
> + return;
> +
> + StartTransactionCommand();
> +
> + if (DatabaseHasLoginEventTriggers())
> + {
> + runlist = EventTriggerCommonSetup(NULL,
> + EVT_Login, "login",
> + &trigdata);
> +
> + if (runlist != NIL)
> + {
> + /*
> + * Make sure anything the main command did will be visible to the
> + * event triggers.
> + */
> + CommandCounterIncrement();

"Main command"?

It's not clear to my why a CommandCounterIncrement() could be needed here -
which previous writes do you need to make visible?

> + /*
> + * Runlist is empty: clear dathasloginevt flag
> + */
> + Relation pg_db = table_open(DatabaseRelationId, RowExclusiveLock);
> + HeapTuple tuple = SearchSysCacheCopy1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
> + Form_pg_database db;
> +
> + if (!HeapTupleIsValid(tuple))
> + elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
> +
> + db = (Form_pg_database) GETSTRUCT(tuple);
> + if (db->dathasloginevt)
> + {
> + /*
> + * There can be a race condition: a login event trigger may
> + * have been added after the pg_event_trigger table was
> + * scanned, and we don't want to erroneously clear the
> + * dathasloginevt flag in this case. To be sure that this
> + * hasn't happened, repeat the scan under the pg_database
> + * table lock.
> + */
> + AcceptInvalidationMessages();
> + runlist = EventTriggerCommonSetup(NULL,
> + EVT_Login, "login",
> + &trigdata);

This doesn't work. RowExclusiveLock doesn't conflict with another
RowExclusiveLock.

What is the AcceptInvalidationMessages() intending to do here?

> + if (runlist == NULL) /* list is still empty, so clear the
> + * flag */
> + {
> + db->dathasloginevt = false;
> + CatalogTupleUpdate(pg_db, &tuple->t_self, tuple);
> + }
> + else
> + CacheInvalidateRelcacheByTuple(tuple);

Copy of the bogus relcache stuff.

Greetings,

Andres Freund

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Amit Kapila 2022-03-12 02:58:35 Re: Issue with pg_stat_subscription_stats
Previous Message Mark Dilger 2022-03-12 02:08:36 Re: role self-revocation