Re: On login trigger: take three - Mailing list pgsql-hackers

From Andres Freund
Subject Re: On login trigger: take three
Date
Msg-id 20220312024652.lvgehszwke4hhove@alap3.anarazel.de
Whole thread Raw
In response to Re: On login trigger: take three  (Greg Nancarrow <gregn4422@gmail.com>)
Responses Re: On login trigger: take three
List 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



pgsql-hackers by date:

Previous
From: Mark Dilger
Date:
Subject: Re: role self-revocation
Next
From: Amit Kapila
Date:
Subject: Re: Issue with pg_stat_subscription_stats