diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index e759789c1f..6e801174e8 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -1,5 +1,5 @@ @@ -593,22 +593,25 @@ FROM (SELECT pg_stat_get_backend_idset() AS backendid) AS S; - When the pg_locks view is accessed, an - exclusive lock on an internal lock manager data structure must be - acquired to ensure that the data produced by the view is - consistent. The lock held on this structure conflicts with normal - database operations, and can therefore have an effect on overall - database performance. Nevertheless, the performance impact of - accessing this view should be minimal in most situations. + When the pg_locks view is accessed, the + internal lock manager data structures are momentarily locked, + and a copy is made for the view to display. This ensures that + the view produces a consistent set of results, while not blocking + normal lock manager operations longer than necessary. Nonetheless + there could be some impact on database performance if this view is + examined often. - The pg_locks view contains one row per - lock. This means that if there are multiple locks on a single - relation (which may or may not conflict with one another), a - single relation may show up many times. Furthermore, only - table-level locks are displayed (not row-level ones). + The pg_locks view contains one row per lockable + object and requested lock mode. Thus, the same lockable object + may appear many times, if multiple transactions are holding or + waiting for locks on it. A lockable object is either a relation + or a transaction ID. (Note that this view includes only table-level + locks, not row-level ones. If a transaction is waiting for a + row-level lock, it will appear in the view as waiting for the + transaction ID of the current holder of that row lock.) @@ -627,31 +630,50 @@ FROM (SELECT pg_stat_get_backend_idset() AS backendid) AS S; relation oid - The OID of the locked relation. When querying - pg_locks, this column can be joined with the - pg_class system catalog to get more - information on the locked relation. + The OID of the locked relation, or NULL if the lockable + object is a transaction ID. This column can be joined + with the pg_class system catalog to get more + information on the locked relation. Note however that this will + only work for relations in the current database (those for which + the database column is either the + current database's OID or zero). + database oid The OID of the database in which the locked relation - exists. If the lock is on a globally-shared object, this value - will be 0. When querying pg_locks, this - column can be joined with the pg_database - system catalog to get more information on the locked object's - database. + exists, or NULL if the lockable object is a transaction ID. + If the lock is on a globally-shared table, this field will be + zero. This + column can be joined with the pg_database + system catalog to get more information on the locked object's + database. + - backendpid + transaction + xid + The ID of a transaction, or NULL if the lockable object + is a relation. Every transaction holds ExclusiveLock on its + transaction ID for its entire duration. If one transaction finds + it necessary to wait specifically for another transaction, it + does so by attempting to acquire ShareLock on the other transaction + ID. That will succeed only when the other transaction terminates + and releases its locks. + + + + + pid int4 The process ID of the PostgreSQL backend that has acquired or is attempting to acquire the lock. If you have enabled the statistics collector, this column can be joined - with the pg_stat_activity view to access + with the pg_stat_activity view to get more information on the backend holding or waiting to hold the lock. @@ -659,7 +681,8 @@ FROM (SELECT pg_stat_get_backend_idset() AS backendid) AS S; mode text - The mode of the lock. For more information on the + The mode of the requested or held lock on the lockable + object. For more information on the different lock modes available in PostgreSQL, refer to the User's Guide. @@ -667,13 +690,14 @@ FROM (SELECT pg_stat_get_backend_idset() AS backendid) AS S; isgranted - text - A boolean column indicating whether or not this - particular lock has been granted. If the lock has not been - granted, the backend atempting to acquire it will sleep until - the lock is released (or a deadlock situation is detected). A - single backend can be waiting to acquire at most one lock at - any given time. + bool + True if this lock has been granted (is held by this + backend). False indicates that this backend is currently + waiting to acquire this lock, which implies that some other + backend is holding a conflicting lock mode on the same lockable + object. This backend will sleep until the other lock is released + (or a deadlock situation is detected). A single backend can be + waiting to acquire at most one lock at a time. diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 154c49b3e9..db4cf76f2f 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/storage/lmgr/lock.c,v 1.112 2002/08/17 13:04:14 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/storage/lmgr/lock.c,v 1.113 2002/08/31 17:14:27 tgl Exp $ * * NOTES * Outside modules can create a lock table and acquire/release @@ -1361,59 +1361,68 @@ LockShmemSize(int maxBackends) /* * GetLockStatusData - Return a summary of the lock manager's internal - * status, for use in a user-level statistical reporting function. + * status, for use in a user-level reporting function. * - * This function should be passed a pointer to a LockData struct. It fills - * the structure with the appropriate information and returns. The goal - * is to hold the LockMgrLock for as short a time as possible; thus, the - * function simply makes a copy of the necessary data and releases the - * lock, allowing the caller to contemplate and format the data for - * as long as it pleases. + * The return data consists of an array of PROCLOCK objects, with the + * associated PGPROC and LOCK objects for each. Note that multiple + * copies of the same PGPROC and/or LOCK objects are likely to appear. + * It is the caller's responsibility to match up duplicates if wanted. + * + * The design goal is to hold the LockMgrLock for as short a time as possible; + * thus, this function simply makes a copy of the necessary data and releases + * the lock, allowing the caller to contemplate and format the data for as + * long as it pleases. */ -void -GetLockStatusData(LockData *data) +LockData * +GetLockStatusData(void) { + LockData *data; HTAB *holderTable; PROCLOCK *holder; HASH_SEQ_STATUS seqstat; - int i = 0; + int i; - data->currIdx = 0; + data = (LockData *) palloc(sizeof(LockData)); LWLockAcquire(LockMgrLock, LW_EXCLUSIVE); holderTable = LockMethodTable[DEFAULT_LOCKMETHOD]->holderHash; - data->nelements = holderTable->hctl->nentries; + data->nelements = i = holderTable->hctl->nentries; - data->procs = (PGPROC *) palloc(sizeof(PGPROC) * data->nelements); - data->locks = (LOCK *) palloc(sizeof(LOCK) * data->nelements); - data->holders = (PROCLOCK *) palloc(sizeof(PROCLOCK) * data->nelements); + if (i == 0) + i = 1; /* avoid palloc(0) if empty table */ + + data->holderaddrs = (SHMEM_OFFSET *) palloc(sizeof(SHMEM_OFFSET) * i); + data->holders = (PROCLOCK *) palloc(sizeof(PROCLOCK) * i); + data->procs = (PGPROC *) palloc(sizeof(PGPROC) * i); + data->locks = (LOCK *) palloc(sizeof(LOCK) * i); hash_seq_init(&seqstat, holderTable); + i = 0; while ( (holder = hash_seq_search(&seqstat)) ) { - PGPROC *proc; - LOCK *lock; - - /* Only do a shallow copy */ - proc = (PGPROC *) MAKE_PTR(holder->tag.proc); - lock = (LOCK *) MAKE_PTR(holder->tag.lock); + PGPROC *proc = (PGPROC *) MAKE_PTR(holder->tag.proc); + LOCK *lock = (LOCK *) MAKE_PTR(holder->tag.lock); + data->holderaddrs[i] = MAKE_OFFSET(holder); + memcpy(&(data->holders[i]), holder, sizeof(PROCLOCK)); memcpy(&(data->procs[i]), proc, sizeof(PGPROC)); memcpy(&(data->locks[i]), lock, sizeof(LOCK)); - memcpy(&(data->holders[i]), holder, sizeof(PROCLOCK)); i++; } + LWLockRelease(LockMgrLock); + Assert(i == data->nelements); - LWLockRelease(LockMgrLock); + return data; } -char * +/* Provide the textual name of any lock mode */ +const char * GetLockmodeName(LOCKMODE mode) { Assert(mode <= MAX_LOCKMODES); diff --git a/src/backend/utils/adt/lockfuncs.c b/src/backend/utils/adt/lockfuncs.c index 199efbacd2..b1ccceebcb 100644 --- a/src/backend/utils/adt/lockfuncs.c +++ b/src/backend/utils/adt/lockfuncs.c @@ -1,36 +1,46 @@ -/* +/*------------------------------------------------------------------------- + * * lockfuncs.c * Set-returning functions to view the state of locks within the DB. * * Copyright (c) 2002, PostgreSQL Global Development Group * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/lockfuncs.c,v 1.4 2002/08/29 17:14:33 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/lockfuncs.c,v 1.5 2002/08/31 17:14:28 tgl Exp $ + * + *------------------------------------------------------------------------- */ #include "postgres.h" #include "funcapi.h" +#include "access/heapam.h" #include "catalog/pg_type.h" -#include "storage/lmgr.h" #include "storage/lock.h" -#include "storage/lwlock.h" #include "storage/proc.h" #include "utils/builtins.h" -static int next_lock(int locks[]); - +/* Working status for pg_lock_status */ +typedef struct +{ + LockData *lockData; /* state data from lmgr */ + int currIdx; /* current PROCLOCK index */ +} PG_Lock_Status; +/* + * pg_lock_status - produce a view with one row per held or awaited lock mode + */ Datum pg_lock_status(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; - LockData *lockData; - MemoryContext oldcontext; + PG_Lock_Status *mystatus; + LockData *lockData; if (SRF_IS_FIRSTCALL()) { TupleDesc tupdesc; + MemoryContext oldcontext; /* create a function context for cross-call persistence */ funcctx = SRF_FIRSTCALL_INIT(); @@ -38,124 +48,132 @@ pg_lock_status(PG_FUNCTION_ARGS) /* switch to memory context appropriate for multiple function calls */ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - tupdesc = CreateTemplateTupleDesc(5, WITHOUTOID); + /* build tupdesc for result tuples */ + /* this had better match pg_locks view in initdb.sh */ + tupdesc = CreateTemplateTupleDesc(6, WITHOUTOID); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "relation", OIDOID, -1, 0, false); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "database", OIDOID, -1, 0, false); - TupleDescInitEntry(tupdesc, (AttrNumber) 3, "backendpid", + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "transaction", + XIDOID, -1, 0, false); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "pid", INT4OID, -1, 0, false); - TupleDescInitEntry(tupdesc, (AttrNumber) 4, "mode", + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "mode", TEXTOID, -1, 0, false); - TupleDescInitEntry(tupdesc, (AttrNumber) 5, "isgranted", + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "granted", BOOLOID, -1, 0, false); funcctx->slot = TupleDescGetSlot(tupdesc); - funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc); /* - * Preload all the locking information that we will eventually format - * and send out as a result set. This is palloc'ed, but since the - * MemoryContext is reset when the SRF finishes, we don't need to - * free it ourselves. + * Collect all the locking information that we will format + * and send out as a result set. */ - funcctx->user_fctx = (LockData *) palloc(sizeof(LockData)); + mystatus = (PG_Lock_Status *) palloc(sizeof(PG_Lock_Status)); + funcctx->user_fctx = (void *) mystatus; - GetLockStatusData(funcctx->user_fctx); + mystatus->lockData = GetLockStatusData(); + mystatus->currIdx = 0; MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); - lockData = (LockData *) funcctx->user_fctx; + mystatus = (PG_Lock_Status *) funcctx->user_fctx; + lockData = mystatus->lockData; - while (lockData->currIdx < lockData->nelements) + while (mystatus->currIdx < lockData->nelements) { PROCLOCK *holder; LOCK *lock; PGPROC *proc; + bool granted; + LOCKMODE mode; + Datum values[6]; + char nulls[6]; HeapTuple tuple; Datum result; - char **values; - LOCKMODE mode; - int num_attrs; - int i; - int currIdx = lockData->currIdx; - holder = &(lockData->holders[currIdx]); - lock = &(lockData->locks[currIdx]); - proc = &(lockData->procs[currIdx]); - num_attrs = funcctx->attinmeta->tupdesc->natts; - - values = (char **) palloc(sizeof(*values) * num_attrs); - - for (i = 0; i < num_attrs; i++) - values[i] = (char *) palloc(32); - - /* The OID of the locked relation */ - snprintf(values[0], 32, "%u", lock->tag.relId); - /* The database the relation is in */ - snprintf(values[1], 32, "%u", lock->tag.dbId); - /* The PID of the backend holding or waiting for the lock */ - snprintf(values[2], 32, "%d", proc->pid); + holder = &(lockData->holders[mystatus->currIdx]); + lock = &(lockData->locks[mystatus->currIdx]); + proc = &(lockData->procs[mystatus->currIdx]); /* - * We need to report both the locks held (i.e. successfully acquired) - * by this holder, as well as the locks upon which it is still - * waiting, if any. Since a single PROCLOCK struct may contain - * multiple locks, we may need to loop several times before we - * advance the array index and continue on. + * Look to see if there are any held lock modes in this PROCLOCK. + * If so, report, and destructively modify lockData so we don't + * report again. */ - if (holder->nHolding > 0) + granted = false; + for (mode = 0; mode < MAX_LOCKMODES; mode++) { - /* Already held locks */ - mode = next_lock(holder->holding); - holder->holding[mode]--; - holder->nHolding--; - - strcpy(values[4], "t"); + if (holder->holding[mode] > 0) + { + granted = true; + holder->holding[mode] = 0; + break; + } } - else if (proc->waitLock != NULL) - { - /* Lock that is still being waited on */ - mode = proc->waitLockMode; - proc->waitLock = NULL; - proc->waitLockMode = NoLock; - strcpy(values[4], "f"); + /* + * If no (more) held modes to report, see if PROC is waiting for + * a lock on this lock. + */ + if (!granted) + { + if (proc->waitLock == (LOCK *) MAKE_PTR(holder->tag.lock)) + { + /* Yes, so report it with proper mode */ + mode = proc->waitLockMode; + /* + * We are now done with this PROCLOCK, so advance pointer + * to continue with next one on next call. + */ + mystatus->currIdx++; + } + else + { + /* + * Okay, we've displayed all the locks associated with this + * PROCLOCK, proceed to the next one. + */ + mystatus->currIdx++; + continue; + } + } + + /* + * Form tuple with appropriate data. + */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, ' ', sizeof(nulls)); + + if (lock->tag.relId == XactLockTableId && lock->tag.dbId == 0) + { + /* Lock is for transaction ID */ + nulls[0] = 'n'; + nulls[1] = 'n'; + values[2] = TransactionIdGetDatum(lock->tag.objId.xid); } else { - /* - * Okay, we've displayed all the lock's belonging to this PROCLOCK, - * procede to the next one. - */ - lockData->currIdx++; - continue; + /* Lock is for a relation */ + values[0] = ObjectIdGetDatum(lock->tag.relId); + values[1] = ObjectIdGetDatum(lock->tag.dbId); + nulls[2] = 'n'; + } - strncpy(values[3], GetLockmodeName(mode), 32); + values[3] = Int32GetDatum(proc->pid); + values[4] = DirectFunctionCall1(textin, + CStringGetDatum(GetLockmodeName(mode))); + values[5] = BoolGetDatum(granted); - tuple = BuildTupleFromCStrings(funcctx->attinmeta, values); + tuple = heap_formtuple(funcctx->slot->ttc_tupleDescriptor, + values, nulls); result = TupleGetDatum(funcctx->slot, tuple); SRF_RETURN_NEXT(funcctx, result); } SRF_RETURN_DONE(funcctx); } - -static LOCKMODE -next_lock(int locks[]) -{ - LOCKMODE i; - - for (i = 0; i < MAX_LOCKMODES; i++) - { - if (locks[i] != 0) - return i; - } - - /* No locks found: this should not occur */ - Assert(false); - return -1; -} diff --git a/src/bin/initdb/initdb.sh b/src/bin/initdb/initdb.sh index 33264050c8..7441105e00 100644 --- a/src/bin/initdb/initdb.sh +++ b/src/bin/initdb/initdb.sh @@ -27,7 +27,7 @@ # Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group # Portions Copyright (c) 1994, Regents of the University of California # -# $Header: /cvsroot/pgsql/src/bin/initdb/Attic/initdb.sh,v 1.169 2002/08/27 04:00:28 momjian Exp $ +# $Header: /cvsroot/pgsql/src/bin/initdb/Attic/initdb.sh,v 1.170 2002/08/31 17:14:28 tgl Exp $ # #------------------------------------------------------------------------- @@ -978,15 +978,12 @@ CREATE VIEW pg_stat_database AS \ FROM pg_database D; CREATE VIEW pg_locks AS \ - SELECT \ - L.relation, L.database, L.backendpid, L.mode, L.isgranted \ - FROM pg_lock_status() AS L(relation oid, database oid, \ - backendpid int4, mode text, isgranted boolean); + SELECT * \ + FROM pg_lock_status() AS L(relation oid, database oid, \ + transaction xid, pid int4, mode text, granted boolean); CREATE VIEW pg_settings AS \ - SELECT \ - A.name, \ - A.setting \ + SELECT * \ FROM pg_show_all_settings() AS A(name text, setting text); CREATE RULE pg_settings_u AS \ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 15f81094c8..f561dee66b 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: catversion.h,v 1.155 2002/08/30 19:23:20 tgl Exp $ + * $Id: catversion.h,v 1.156 2002/08/31 17:14:28 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200208301 +#define CATALOG_VERSION_NO 200208311 #endif diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h index bedf6d7faf..4c09bdda7c 100644 --- a/src/include/storage/lock.h +++ b/src/include/storage/lock.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: lock.h,v 1.65 2002/08/17 13:04:18 momjian Exp $ + * $Id: lock.h,v 1.66 2002/08/31 17:14:28 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -204,19 +204,20 @@ typedef struct PROCLOCK (((LOCK *) MAKE_PTR((holder).tag.lock))->tag.lockmethod) /* - * This struct is used to encapsulate information passed from lmgr - * internals to the lock listing statistical functions (lockfuncs.c). - * It's just a convenient bundle of other lock.h structures. All - * the information at a given index (holders[i], procs[i], locks[i]) - * is related. + * This struct holds information passed from lmgr internals to the lock + * listing user-level functions (lockfuncs.c). For each PROCLOCK in the + * system, the SHMEM_OFFSET, PROCLOCK itself, and associated PGPROC and + * LOCK objects are stored. (Note there will often be multiple copies + * of the same PGPROC or LOCK.) We do not store the SHMEM_OFFSET of the + * PGPROC or LOCK separately, since they're in the PROCLOCK's tag fields. */ typedef struct { - int nelements; /* The length of holders, procs, & locks */ - int currIdx; /* Current element being examined */ + int nelements; /* The length of each of the arrays */ + SHMEM_OFFSET *holderaddrs; + PROCLOCK *holders; PGPROC *procs; LOCK *locks; - PROCLOCK *holders; } LockData; /* @@ -242,8 +243,8 @@ extern void RemoveFromWaitQueue(PGPROC *proc); extern int LockShmemSize(int maxBackends); extern bool DeadLockCheck(PGPROC *proc); extern void InitDeadLockChecking(void); -extern void GetLockStatusData(LockData *data); -extern char *GetLockmodeName(LOCKMODE mode); +extern LockData *GetLockStatusData(void); +extern const char *GetLockmodeName(LOCKMODE mode); #ifdef LOCK_DEBUG extern void DumpLocks(void); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 77420cd2b2..0cfd3f103d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1268,7 +1268,7 @@ SELECT viewname, definition FROM pg_views ORDER BY viewname; --------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- iexit | SELECT ih.name, ih.thepath, interpt_pp(ih.thepath, r.thepath) AS exit FROM ihighway ih, ramp r WHERE (ih.thepath ## r.thepath); pg_indexes | SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, pg_get_indexdef(i.oid) AS indexdef FROM (((pg_index x JOIN pg_class c ON ((c.oid = x.indrelid))) JOIN pg_class i ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE ((c.relkind = 'r'::"char") AND (i.relkind = 'i'::"char")); - pg_locks | SELECT l.relation, l."database", l.backendpid, l."mode", l.isgranted FROM pg_lock_status() l(relation oid, "database" oid, backendpid integer, "mode" text, isgranted boolean); + pg_locks | SELECT l.relation, l."database", l."transaction", l.pid, l."mode", l.granted FROM pg_lock_status() l(relation oid, "database" oid, "transaction" xid, pid integer, "mode" text, granted boolean); pg_rules | SELECT n.nspname AS schemaname, c.relname AS tablename, r.rulename, pg_get_ruledef(r.oid) AS definition FROM ((pg_rewrite r JOIN pg_class c ON ((c.oid = r.ev_class))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (r.rulename <> '_RETURN'::name); pg_settings | SELECT a.name, a.setting FROM pg_show_all_settings() a(name text, setting text); pg_stat_activity | SELECT d.oid AS datid, d.datname, pg_stat_get_backend_pid(s.backendid) AS procpid, pg_stat_get_backend_userid(s.backendid) AS usesysid, u.usename, pg_stat_get_backend_activity(s.backendid) AS current_query FROM pg_database d, (SELECT pg_stat_get_backend_idset() AS backendid) s, pg_shadow u WHERE ((pg_stat_get_backend_dbid(s.backendid) = d.oid) AND (pg_stat_get_backend_userid(s.backendid) = u.usesysid));