diff -Nacr -x CVS bah2\src\backend\postmaster\pgstat.c bah\src\backend\postmaster\pgstat.c *** bah2\src\backend\postmaster\pgstat.c Sat Jun 25 21:57:22 2005 --- bah\src\backend\postmaster\pgstat.c Sat Jun 25 21:56:06 2005 *************** *** 131,136 **** --- 131,137 ---- static TransactionId pgStatDBHashXact = InvalidTransactionId; static HTAB *pgStatDBHash = NULL; + static HTAB *pgStatUserHash = NULL; static HTAB *pgStatBeDead = NULL; static PgStat_StatBeEntry *pgStatBeTable = NULL; static int pgStatNumBackends = 0; *************** *** 163,173 **** --- 164,177 ---- static void pgstat_beshutdown_hook(int code, Datum arg); static PgStat_StatDBEntry *pgstat_get_db_entry(int databaseid); + static PgStat_StatUserEntry *pgstat_get_user_entry(int userid); static int pgstat_add_backend(PgStat_MsgHdr *msg); static void pgstat_sub_backend(int procpid); static void pgstat_drop_database(Oid databaseid); + static void pgstat_drop_user(Oid userid); static void pgstat_write_statsfile(void); static void pgstat_read_statsfile(HTAB **dbhash, Oid onlydb, + HTAB **userhash, PgStat_StatBeEntry **betab, int *numbackends); static void backend_read_statsfile(void); *************** *** 181,186 **** --- 185,191 ---- static void pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len); static void pgstat_recv_tabpurge(PgStat_MsgTabpurge *msg, int len); static void pgstat_recv_dropdb(PgStat_MsgDropdb *msg, int len); + static void pgstat_recv_dropuser(PgStat_MsgDropuser *msg, int len); static void pgstat_recv_resetcounter(PgStat_MsgResetcounter *msg, int len); *************** *** 772,782 **** Relation dbrel; HeapScanDesc dbscan; HeapTuple dbtup; ! Oid *dbidlist; ! int dbidalloc; ! int dbidused; HASH_SEQ_STATUS hstat; PgStat_StatDBEntry *dbentry; PgStat_StatTabEntry *tabentry; HeapTuple reltup; int nobjects = 0; --- 777,791 ---- Relation dbrel; HeapScanDesc dbscan; HeapTuple dbtup; ! Relation userrel; ! HeapScanDesc userscan; ! HeapTuple usertup; ! Oid *oidlist; ! int oidalloc; ! int oidused; HASH_SEQ_STATUS hstat; PgStat_StatDBEntry *dbentry; + PgStat_StatUserEntry *userentry; PgStat_StatTabEntry *tabentry; HeapTuple reltup; int nobjects = 0; *************** *** 866,886 **** /* * Read pg_database and remember the Oid's of all existing databases */ ! dbidalloc = 256; ! dbidused = 0; ! dbidlist = (Oid *) palloc(sizeof(Oid) * dbidalloc); dbrel = heap_open(DatabaseRelationId, AccessShareLock); dbscan = heap_beginscan(dbrel, SnapshotNow, 0, NULL); while ((dbtup = heap_getnext(dbscan, ForwardScanDirection)) != NULL) { ! if (dbidused >= dbidalloc) { ! dbidalloc *= 2; ! dbidlist = (Oid *) repalloc((char *) dbidlist, ! sizeof(Oid) * dbidalloc); } ! dbidlist[dbidused++] = HeapTupleGetOid(dbtup); } heap_endscan(dbscan); heap_close(dbrel, AccessShareLock); --- 875,895 ---- /* * Read pg_database and remember the Oid's of all existing databases */ ! oidalloc = 256; ! oidused = 0; ! oidlist = (Oid *) palloc(sizeof(Oid) * oidalloc); dbrel = heap_open(DatabaseRelationId, AccessShareLock); dbscan = heap_beginscan(dbrel, SnapshotNow, 0, NULL); while ((dbtup = heap_getnext(dbscan, ForwardScanDirection)) != NULL) { ! if (oidused >= oidalloc) { ! oidalloc *= 2; ! oidlist = (Oid *) repalloc((char *) oidlist, ! sizeof(Oid) * oidalloc); } ! oidlist[oidused++] = HeapTupleGetOid(dbtup); } heap_endscan(dbscan); heap_close(dbrel, AccessShareLock); *************** *** 894,902 **** { Oid dbid = dbentry->databaseid; ! for (i = 0; i < dbidused; i++) { ! if (dbidlist[i] == dbid) { dbid = InvalidOid; break; --- 903,911 ---- { Oid dbid = dbentry->databaseid; ! for (i = 0; i < oidused; i++) { ! if (oidlist[i] == dbid) { dbid = InvalidOid; break; *************** *** 910,919 **** } } /* ! * Free the dbid list. */ ! pfree(dbidlist); /* * Tell the caller how many removeable objects we found --- 919,977 ---- } } + /* ! * Clear the Oid list. */ ! memset(oidlist, 0, sizeof(Oid) * oidalloc); ! ! /* ! * Read pg_shadow and remember the Oid's of all existing users ! */ ! userrel = heap_open(ShadowRelationId, AccessShareLock); ! userscan = heap_beginscan(userrel, SnapshotNow, 0, NULL); ! while ((usertup = heap_getnext(userscan, ForwardScanDirection)) != NULL) ! { ! if (oidused >= oidalloc) ! { ! oidalloc *= 2; ! oidlist = (Oid *) repalloc((char *) oidlist, ! sizeof(Oid) * oidalloc); ! } ! oidlist[oidused++] = HeapTupleGetOid(usertup); ! } ! heap_endscan(userscan); ! heap_close(userrel, AccessShareLock); ! ! /* ! * Search the user hash table for dead users and tell the ! * collector to drop them as well. ! */ ! hash_seq_init(&hstat, pgStatUserHash); ! while ((userentry = (PgStat_StatUserEntry *) hash_seq_search(&hstat)) != NULL) ! { ! Oid userid = userentry->userid; ! ! for (i = 0; i < oidused; i++) ! { ! if (oidlist[i] == userid) ! { ! userid = InvalidOid; ! break; ! } ! } ! ! if (userid != InvalidOid) ! { ! nobjects++; ! pgstat_drop_user(userid); ! } ! } ! ! /* ! * Free the Oid list. ! */ ! pfree(oidlist); /* * Tell the caller how many removeable objects we found *************** *** 946,951 **** --- 1004,1032 ---- /* ---------- + * pgstat_drop_user() - + * + * Tell the collector that we just dropped a user. + * This is the only message that shouldn't get lost in space. Otherwise + * the collector will keep the statistics for the dead users until his + * stats file got removed while the postmaster is down. + * ---------- + */ + static void + pgstat_drop_user(Oid userid) + { + PgStat_MsgDropuser msg; + + if (pgStatSock < 0) + return; + + pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DROPUSER); + msg.m_userid = userid; + pgstat_send(&msg, sizeof(msg)); + } + + + /* ---------- * pgstat_reset_counters() - * * Tell the statistics collector to reset counters for our database. *************** *** 1196,1201 **** --- 1277,1308 ---- HASH_FIND, NULL); } + /* ---------- + * pgstat_fetch_stat_userentry() - + * + * Support function for the SQL-callable pgstat* functions. Returns + * the collected statistics for one user or NULL. NULL doesn't mean + * that the user doesn't exist, it is just not yet known by the + * collector, so the caller is better off to report ZERO instead. + * ---------- + */ + PgStat_StatUserEntry * + pgstat_fetch_stat_userentry(Oid userid) + { + /* + * If not done for this transaction, read the statistics collector + * stats file into some hash tables. + */ + backend_read_statsfile(); + + /* + * Lookup the requested database; return NULL if not found + */ + return (PgStat_StatUserEntry *) hash_search(pgStatUserHash, + (void *) &userid, + HASH_FIND, NULL); + } + /* ---------- * pgstat_fetch_stat_tabentry() - *************** *** 1490,1496 **** * to zero. */ pgStatRunningInCollector = TRUE; ! pgstat_read_statsfile(&pgStatDBHash, InvalidOid, NULL, NULL); /* * Create the dead backend hashtable --- 1597,1603 ---- * to zero. */ pgStatRunningInCollector = TRUE; ! pgstat_read_statsfile(&pgStatDBHash, InvalidOid, &pgStatUserHash, NULL, NULL); /* * Create the dead backend hashtable *************** *** 1670,1675 **** --- 1777,1786 ---- pgstat_recv_dropdb((PgStat_MsgDropdb *) &msg, nread); break; + case PGSTAT_MTYPE_DROPUSER: + pgstat_recv_dropuser((PgStat_MsgDropuser *) &msg, nread); + break; + case PGSTAT_MTYPE_RESETCOUNTER: pgstat_recv_resetcounter((PgStat_MsgResetcounter *) &msg, nread); *************** *** 2087,2092 **** --- 2198,2228 ---- return result; } + /* + * Lookup the hash table entry for the specified user. If no hash + * table entry exists, initialize it. + */ + static PgStat_StatUserEntry * + pgstat_get_user_entry(int userid) + { + PgStat_StatUserEntry *result; + bool found; + + /* Lookup or create the hash table entry for this user */ + result = (PgStat_StatUserEntry *) hash_search(pgStatUserHash, + &userid, + HASH_ENTER, &found); + + /* If not found, initialize the new one. */ + if (!found) + { + result->n_backends = 0; + result->destroy = 0; + } + + return result; + } + /* ---------- * pgstat_sub_backend() - * *************** *** 2156,2161 **** --- 2292,2298 ---- HASH_SEQ_STATUS hstat; HASH_SEQ_STATUS tstat; PgStat_StatDBEntry *dbentry; + PgStat_StatUserEntry *userentry; PgStat_StatTabEntry *tabentry; PgStat_StatBeDead *deadbe; FILE *fpout; *************** *** 2254,2259 **** --- 2391,2412 ---- } /* + * Walk through the user table. + */ + ereport(DEBUG3, (errmsg_internal("before write 'U'"))); + hash_seq_init(&hstat, pgStatUserHash); + ereport(DEBUG3, (errmsg_internal("write 'U' - before while"))); + while ((userentry = (PgStat_StatUserEntry *) hash_seq_search(&hstat)) != NULL) + { + ereport(DEBUG3, (errmsg_internal("write 'U' - in while 1"))); + fputc('U', fpout); + ereport(DEBUG3, (errmsg_internal("write 'U' - in while 2"))); + fwrite(userentry, sizeof(PgStat_StatUserEntry), 1, fpout); + ereport(DEBUG3, (errmsg_internal("write 'U' - in while 3"))); + } + ereport(DEBUG3, (errmsg_internal("after write 'U'"))); + + /* * Write out the known running backends to the stats file. */ i = MaxBackends; *************** *** 2327,2336 **** --- 2480,2492 ---- */ static void pgstat_read_statsfile(HTAB **dbhash, Oid onlydb, + HTAB **userhash, PgStat_StatBeEntry **betab, int *numbackends) { PgStat_StatDBEntry *dbentry; PgStat_StatDBEntry dbbuf; + PgStat_StatUserEntry *userentry; + PgStat_StatUserEntry userbuf; PgStat_StatTabEntry *tabentry; PgStat_StatTabEntry tabbuf; HASHCTL hash_ctl; *************** *** 2371,2376 **** --- 2527,2545 ---- HASH_ELEM | HASH_FUNCTION | mcxt_flags); /* + * Create users hashtable + */ + ereport(DEBUG3, (errmsg_internal("before Create users hashtable"))); + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(Oid); + hash_ctl.entrysize = sizeof(PgStat_StatUserEntry); + hash_ctl.hash = oid_hash; + hash_ctl.hcxt = use_mcxt; + *userhash = hash_create("Users hash", PGSTAT_DB_HASH_SIZE, &hash_ctl, + HASH_ELEM | HASH_FUNCTION | mcxt_flags); + ereport(DEBUG3, (errmsg_internal("after Create users hashtable"))); + + /* * Initialize the number of known backends to zero, just in case we do * a silent error return below. */ *************** *** 2501,2506 **** --- 2670,2707 ---- break; /* + * 'U' A PgStat_StatUserEntry struct describing an user follows. + */ + case 'U': + ereport(DEBUG3, (errmsg_internal("in read 'U' start"))); + if (fread(&userbuf, 1, sizeof(userbuf), fpin) != sizeof(userbuf)) + { + ereport(pgStatRunningInCollector ? LOG : WARNING, + (errmsg("corrupted pgstat.stat file"))); + goto done; + } + + /* + * Add to the user hash + */ + userentry = (PgStat_StatUserEntry *) hash_search(*userhash, + (void *) &userbuf.userid, + HASH_ENTER, + &found); + if (found) + { + ereport(pgStatRunningInCollector ? LOG : WARNING, + (errmsg("corrupted pgstat.stat file"))); + goto done; + } + + memcpy(userentry, &userbuf, sizeof(PgStat_StatUserEntry)); + userentry->destroy = 0; + userentry->n_backends = 0; + ereport(DEBUG3, (errmsg_internal("in read 'U' end"))); + break; + + /* * 'M' The maximum number of backends to expect follows. */ case 'M': *************** *** 2557,2562 **** --- 2758,2772 ---- if (dbentry) dbentry->n_backends++; + /* + * Count backends per user here. + */ + userentry = (PgStat_StatUserEntry *) hash_search(*userhash, + (void *) &((*betab)[havebackends].userid), + HASH_FIND, NULL); + if (userentry) + userentry->n_backends++; + havebackends++; if (numbackends != 0) *numbackends = havebackends; *************** *** 2598,2603 **** --- 2808,2814 ---- { Assert(!pgStatRunningInCollector); pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId, + &pgStatUserHash, &pgStatBeTable, &pgStatNumBackends); pgStatDBHashXact = topXid; } *************** *** 2615,2620 **** --- 2826,2832 ---- { PgStat_StatBeEntry *beentry; PgStat_StatDBEntry *dbentry; + PgStat_StatUserEntry *userentry; bool found; /* *************** *** 2670,2675 **** --- 2882,2913 ---- * Count number of connects to the database */ dbentry->n_backends++; + + + /* + * Lookup or create the user entry for this backends user. + */ + userentry = (PgStat_StatUserEntry *) hash_search(pgStatUserHash, + (void *) &(msg->m_userid), + HASH_ENTER, &found); + if (userentry == NULL) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory in statistics collector --- abort"))); + + /* + * If not found, initialize the new one. + */ + if (!found) + { + userentry->n_backends = 0; + userentry->destroy = 0; + } + + /* + * Count number of connects of the user + */ + userentry->n_backends++; } *************** *** 2865,2870 **** --- 3103,3137 ---- * Mark the database for destruction. */ dbentry->destroy = PGSTAT_DESTROY_COUNT; + } + + + /* ---------- + * pgstat_recv_dropuser() - + * + * Arrange for dead user removal + * ---------- + */ + static void + pgstat_recv_dropuser(PgStat_MsgDropuser *msg, int len) + { + PgStat_StatUserEntry *userentry; + + /* + * Make sure the backend is counted for. + */ + if (pgstat_add_backend(&msg->m_hdr) < 0) + return; + + /* + * Lookup the user in the hashtable. + */ + userentry = pgstat_get_user_entry(msg->m_userid); + + /* + * Mark the user for destruction. + */ + userentry->destroy = PGSTAT_DESTROY_COUNT; } diff -Nacr -x CVS bah2\src\backend\utils\init\globals.c bah\src\backend\utils\init\globals.c *** bah2\src\backend\utils\init\globals.c Sat Jan 01 00:01:40 2005 --- bah\src\backend\utils\init\globals.c Sat Jun 25 21:56:06 2005 *************** *** 92,97 **** --- 92,99 ---- /* Primary determinants of sizes of shared-memory structures: */ int NBuffers = 1000; int MaxBackends = 100; + int MaxDBBackends = 0; + int MaxUserBackends = 0; int VacuumCostPageHit = 1; /* GUC parameters for vacuum */ int VacuumCostPageMiss = 10; diff -Nacr -x CVS bah2\src\backend\utils\init\postinit.c bah\src\backend\utils\init\postinit.c *** bah2\src\backend\utils\init\postinit.c Fri Jun 24 19:42:44 2005 --- bah\src\backend\utils\init\postinit.c Sat Jun 25 21:56:06 2005 *************** *** 43,48 **** --- 43,49 ---- #include "utils/portal.h" #include "utils/relcache.h" #include "utils/syscache.h" + #include "pgstat.h" static bool FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace); *************** *** 50,55 **** --- 51,57 ---- static void InitCommunication(void); static void ShutdownPostgres(int code, Datum arg); static bool ThereIsAtLeastOneUser(void); + static void CheckMaxConnections(const char *dbname, const char *username); /*** InitPostgres support ***/ *************** *** 436,441 **** --- 438,450 ---- if (!bootstrap) ReverifyMyDatabase(dbname); + + /* Now we have database specifig & user specifig configs loaded, + * we can check for max_db_connections and max_user_connections + */ + CheckMaxConnections(dbname, username); + + /* * Final phase of relation cache startup: write a new cache file if * necessary. This is done after ReverifyMyDatabase to avoid writing *************** *** 548,551 **** --- 557,599 ---- heap_close(pg_shadow_rel, AccessExclusiveLock); return result; + } + + + /* + * Check if we are not over max_db_conenctions and max_user_connections limits + */ + static void + CheckMaxConnections(const char *dbname, const char *username) + { + PgStat_StatDBEntry *dbentry; + PgStat_StatUserEntry *userentry; + + if (MaxDBBackends > 0) + { + if ((dbentry = pgstat_fetch_stat_dbentry(MyDatabaseId)) != NULL) + { + if (dbentry->n_backends > MaxDBBackends) + { + ereport(FATAL, + (errcode(ERRCODE_TOO_MANY_CONNECTIONS), + errmsg("sorry, too many clients already for database \"%s\"", + dbname))); + } + } + } + + if (MaxUserBackends > 0) + { + if ((userentry = pgstat_fetch_stat_userentry(GetUserId())) != NULL) + { + if (userentry->n_backends > MaxUserBackends) + { + ereport(FATAL, + (errcode(ERRCODE_TOO_MANY_CONNECTIONS), + errmsg("sorry, too many clients already for user \"%s\"", + username))); + } + } + } } diff -Nacr -x CVS bah2\src\backend\utils\misc\guc.c bah\src\backend\utils\misc\guc.c *** bah2\src\backend\utils\misc\guc.c Thu Jun 23 03:57:06 2005 --- bah\src\backend\utils\misc\guc.c Sat Jun 25 21:56:06 2005 *************** *** 981,986 **** --- 981,1004 ---- }, { + {"max_db_connections", PGC_SUSET, CONN_AUTH_SETTINGS, + gettext_noop("Sets the maximum number of concurrent connections per database."), + NULL + }, + &MaxDBBackends, + 0, 0, INT_MAX / BLCKSZ, NULL, NULL + }, + + { + {"max_user_connections", PGC_SUSET, CONN_AUTH_SETTINGS, + gettext_noop("Sets the maximum number of concurrent connections per user."), + NULL + }, + &MaxUserBackends, + 0, 0, INT_MAX / BLCKSZ, NULL, NULL + }, + + { {"shared_buffers", PGC_POSTMASTER, RESOURCES_MEM, gettext_noop("Sets the number of shared memory buffers used by the server."), NULL diff -Nacr -x CVS bah2\src\bin\psql\tab-complete.c bah\src\bin\psql\tab-complete.c *** bah2\src\bin\psql\tab-complete.c Thu Jun 23 03:57:08 2005 --- bah\src\bin\psql\tab-complete.c Sat Jun 25 21:56:06 2005 *************** *** 576,581 **** --- 576,583 ---- "log_statement_stats", "maintenance_work_mem", "max_connections", + "max_db_connections", + "max_user_connections", "max_files_per_process", "max_fsm_pages", "max_fsm_relations", diff -Nacr -x CVS bah2\src\include\miscadmin.h bah\src\include\miscadmin.h *** bah2\src\include\miscadmin.h Sat Feb 26 20:43:34 2005 --- bah\src\include\miscadmin.h Sat Jun 25 21:56:06 2005 *************** *** 130,135 **** --- 130,137 ---- extern DLLIMPORT int NBuffers; extern int MaxBackends; + extern int MaxDBBackends; + extern int MaxUserBackends; extern DLLIMPORT int MyProcPid; extern struct Port *MyProcPort; diff -Nacr -x CVS bah2\src\include\pgstat.h bah\src\include\pgstat.h *** bah2\src\include\pgstat.h Wed May 11 03:41:42 2005 --- bah\src\include\pgstat.h Sat Jun 25 21:56:06 2005 *************** *** 28,33 **** --- 28,34 ---- #define PGSTAT_MTYPE_TABPURGE 5 #define PGSTAT_MTYPE_DROPDB 6 #define PGSTAT_MTYPE_RESETCOUNTER 7 + #define PGSTAT_MTYPE_DROPUSER 8 /* ---------- * The data type used for counters. *************** *** 175,180 **** --- 176,193 ---- /* ---------- + * PgStat_MsgDropuser Sent by the backend to tell the collector + * about dropped user + * ---------- + */ + typedef struct PgStat_MsgDropuser + { + PgStat_MsgHdr m_hdr; + Oid m_userid; + } PgStat_MsgDropuser; + + + /* ---------- * PgStat_MsgResetcounter Sent by the backend to tell the collector * to reset counters * ---------- *************** *** 224,229 **** --- 237,253 ---- int destroy; } PgStat_StatDBEntry; + /* ---------- + * PgStat_StatUserEntry The collectors data per user + * ---------- + */ + typedef struct PgStat_StatUserEntry + { + Oid userid; + int n_backends; + int destroy; + } PgStat_StatUserEntry; + /* ---------- * PgStat_StatBeEntry The collectors data per backend *************** *** 424,429 **** --- 448,454 ---- */ extern PgStat_StatDBEntry *pgstat_fetch_stat_dbentry(Oid dbid); extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry(Oid relid); + extern PgStat_StatUserEntry *pgstat_fetch_stat_userentry(Oid userid); extern PgStat_StatBeEntry *pgstat_fetch_stat_beentry(int beid); extern int pgstat_fetch_stat_numbackends(void);