Hi,

I've written a simple extension that limits number of connection by
IP/db/user, and I do receive this exception:

psql: FATAL:  cannot read pg_class without having selected a database

I've found this happens because the extension defines a client auth hook
that reads pg_stat_activity. The really interesting thing is that this
happens only when I start several backends 'at the same time' right
after the cluster is started. From that time, everything works just fine.

So it seems like a race condition or something like that.

I've prepared a simple testcase to demonstrate this issue - see the
files attached. I've put there several 'sleep' to demonstrate the timing
error.

All you need to do is this:

1) compile the extension (make install)
2) add the extension to shared_preload_libraries
3) restart the cluster
4) start two backends at the same time (within a second or so)


Tomas
#include <stdio.h>
#include <unistd.h>

#include <sys/time.h>
#include <sys/types.h>
#include <sys/ipc.h>

#include "postgres.h"
#include "miscadmin.h"
#include "storage/ipc.h"

#include "libpq/auth.h"
#include "pgstat.h"

#include "executor/executor.h"
#include "commands/dbcommands.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif

/* allocates space for the rules */
static void pg_limits_shmem_startup(void);

/* check the rules (using pg_stat_activity) */
static void rules_check(Port *port, int status);

/* Saved hook values in case of unload */
static shmem_startup_hook_type prev_shmem_startup_hook = NULL;

/* Original Hook */
static ClientAuthentication_hook_type prev_client_auth_hook = NULL;

static LWLockId lock;

void		_PG_init(void);
void		_PG_fini(void);

/*
 * Module load callback
 */
void
_PG_init(void)
{
	
	/* can be preloaded only from postgresql.conf */
	if (! process_shared_preload_libraries_in_progress)
		elog(ERROR, "connection_limits_shared has to be loaded using "
					"shared_preload_libraries");
	
	/*
	 * Request additional shared resources.  (These are no-ops if we're not in
	 * the postmaster process.)  We'll allocate or attach to the shared
	 * resources in pg_limits_shmem_startup().
	 */
	RequestAddinLWLocks(1);

	/* Install hooks. */
	prev_shmem_startup_hook = shmem_startup_hook;
	shmem_startup_hook = pg_limits_shmem_startup;
	
	/* Install Hooks */
	prev_client_auth_hook = ClientAuthentication_hook;
	ClientAuthentication_hook = rules_check;

}

/*
 * Module unload callback
 */
void
_PG_fini(void)
{
	/* Uninstall hooks. */
	shmem_startup_hook = prev_shmem_startup_hook;
}

/* This is probably the most important part - allocates the shared 
 * segment, initializes it etc. */
static
void pg_limits_shmem_startup() {
	
	if (prev_shmem_startup_hook)
		prev_shmem_startup_hook();
	
	/*
	 * Create or attach to the shared memory state, including hash table
	 */
	LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
	
	/* First time through ... */
	lock = LWLockAssign();

	LWLockRelease(AddinShmemInitLock);
	
}

static
void rules_check(Port *port, int status)
{

	int b, nbackends;
	PgBackendStatus *beentry;

	/*
	 * Any other plugins which use ClientAuthentication_hook.
	 */
	if (prev_client_auth_hook)
		prev_client_auth_hook(port, status);

	/*
	 * Inject a short delay if authentication failed.
	 */
	if (status == STATUS_OK)
	{

		/* lock the segment (serializes the backend creation) */
		LWLockAcquire(lock, LW_EXCLUSIVE);
	
		sleep(1);
		
		/* how many backends are already there ? */
		nbackends = pgstat_fetch_stat_numbackends();
		
		/* loop through the backends */
		for (b = 1; b <= nbackends; b++) {
			
			char * usr, * db;
			
			beentry = pgstat_fetch_stat_beentry(b);

			/* pgstatfuncs.c : 630 */
			if (beentry != NULL) {
				
				db = get_database_name(beentry->st_databaseid);
				usr = GetUserNameFromId(beentry->st_userid);
				
			} /* (beentry != NULL) */
			
		} /* for (b = 1; b <= nbackends; b++) */

	}
	
	sleep(4);
	
	LWLockRelease(lock);

}
# issue
comment = '...'
default_version = '1.0.0'
relocatable = true

module_pathname = '$libdir/issue'
MODULE_big = issue
OBJS = issue.o

EXTENSION = issue
MODULES = issue

CFLAGS=`pg_config --includedir-server`

PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

all: issue.so

issue.so: issue.o

issue.o : issue.c
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to