Report the current queries of all backends involved in a deadlock
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 21 Mar 2008 21:08:31 +0000 (21:08 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 21 Mar 2008 21:08:31 +0000 (21:08 +0000)
(if they'd be visible to the current user in pg_stat_activity).

This might look like it's subject to race conditions, but it's actually
pretty safe because at the time DeadLockReport() is constructing the
report, we haven't yet aborted our transaction and so we can expect that
everyone else involved in the deadlock is still blocked on some lock.
(There are corner cases where that might not be true, such as a statement
timeout triggering in another backend before we finish reporting; but at
worst we'd report a misleading activity string, so it seems acceptable
considering the usefulness of reporting the queries.)

Original patch by Itagaki Takahiro, heavily modified by me.

src/backend/postmaster/pgstat.c
src/backend/storage/lmgr/deadlock.c
src/include/pgstat.h

index 002bf89014b3d75d16ef1ffef8396b7abba511c8..b37a6ddda1bb3658f34844076333df3381e864c2 100644 (file)
@@ -2036,6 +2036,80 @@ pgstat_read_current_status(void)
 }
 
 
+/* ----------
+ * pgstat_get_backend_current_activity() -
+ *
+ *     Return a string representing the current activity of the backend with
+ *     the specified PID.  This looks directly at the BackendStatusArray,
+ *     and so will provide current information regardless of the age of our
+ *     transaction's snapshot of the status array.
+ *
+ *     It is the caller's responsibility to invoke this only for backends whose
+ *     state is expected to remain stable while the result is in use.  The
+ *     only current use is in deadlock reporting, where we can expect that
+ *     the target backend is blocked on a lock.  (There are corner cases
+ *     where the target's wait could get aborted while we are looking at it,
+ *     but the very worst consequence is to return a pointer to a string
+ *     that's been changed, so we won't worry too much.)
+ *
+ *     Note: return strings for special cases match pg_stat_get_backend_activity.
+ * ----------
+ */
+const char *
+pgstat_get_backend_current_activity(int pid)
+{
+       PgBackendStatus *beentry;
+       int                     i;
+
+       beentry = BackendStatusArray;
+       for (i = 1; i <= MaxBackends; i++)
+       {
+               /*
+                * Although we expect the target backend's entry to be stable, that
+                * doesn't imply that anyone else's is.  To avoid identifying the
+                * wrong backend, while we check for a match to the desired PID we
+                * must follow the protocol of retrying if st_changecount changes
+                * while we examine the entry, or if it's odd.  (This might be
+                * unnecessary, since fetching or storing an int is almost certainly
+                * atomic, but let's play it safe.)  We use a volatile pointer here
+                * to ensure the compiler doesn't try to get cute.
+                */
+               volatile PgBackendStatus *vbeentry = beentry;
+               bool    found;
+
+               for (;;)
+               {
+                       int                     save_changecount = vbeentry->st_changecount;
+
+                       found = (vbeentry->st_procpid == pid);
+
+                       if (save_changecount == vbeentry->st_changecount &&
+                               (save_changecount & 1) == 0)
+                               break;
+
+                       /* Make sure we can break out of loop if stuck... */
+                       CHECK_FOR_INTERRUPTS();
+               }
+
+               if (found)
+               {
+                       /* Now it is safe to use the non-volatile pointer */
+                       if (!superuser() && beentry->st_userid != GetUserId())
+                               return "<insufficient privilege>";
+                       else if (*(beentry->st_activity) == '\0')
+                               return "<command string not enabled>";
+                       else
+                               return beentry->st_activity;
+               }
+
+               beentry++;
+       }
+
+       /* If we get here, caller is in error ... */
+       return "<backend information not available>";
+}
+
+
 /* ------------------------------------------------------------
  * Local support functions follow
  * ------------------------------------------------------------
index 57d574fec6efdde02eac80356e11d406f2e3113f..79fdc8df31d61589b9acf89a4efe5868c3c60167 100644 (file)
@@ -26,6 +26,7 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
+#include "pgstat.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
 #include "utils/memutils.h"
@@ -878,12 +879,14 @@ PrintLockQueue(LOCK *lock, const char *info)
 void
 DeadLockReport(void)
 {
-       StringInfoData buf;
-       StringInfoData buf2;
+       StringInfoData detailbuf;
+       StringInfoData contextbuf;
+       StringInfoData locktagbuf;
        int                     i;
 
-       initStringInfo(&buf);
-       initStringInfo(&buf2);
+       initStringInfo(&detailbuf);
+       initStringInfo(&contextbuf);
+       initStringInfo(&locktagbuf);
 
        for (i = 0; i < nDeadlockDetails; i++)
        {
@@ -896,26 +899,36 @@ DeadLockReport(void)
                else
                        nextpid = deadlockDetails[0].pid;
 
-               if (i > 0)
-                       appendStringInfoChar(&buf, '\n');
+               /* reset locktagbuf to hold next object description */
+               resetStringInfo(&locktagbuf);
 
-               /* reset buf2 to hold next object description */
-               resetStringInfo(&buf2);
+               DescribeLockTag(&locktagbuf, &info->locktag);
 
-               DescribeLockTag(&buf2, &info->locktag);
+               if (i > 0)
+                       appendStringInfoChar(&detailbuf, '\n');
 
-               appendStringInfo(&buf,
+               appendStringInfo(&detailbuf,
                                  _("Process %d waits for %s on %s; blocked by process %d."),
                                                 info->pid,
                                                 GetLockmodeName(info->locktag.locktag_lockmethodid,
                                                                                 info->lockmode),
-                                                buf2.data,
+                                                locktagbuf.data,
                                                 nextpid);
+
+               if (i > 0)
+                       appendStringInfoChar(&contextbuf, '\n');
+
+               appendStringInfo(&contextbuf,
+                                                _("Process %d: %s"),
+                                                info->pid,
+                                                pgstat_get_backend_current_activity(info->pid));
        }
+
        ereport(ERROR,
                        (errcode(ERRCODE_T_R_DEADLOCK_DETECTED),
                         errmsg("deadlock detected"),
-                        errdetail("%s", buf.data)));
+                        errdetail("%s", detailbuf.data),
+                        errcontext("%s", contextbuf.data)));
 }
 
 /*
index 8131520f169094f0c7573dc88e62b91637a812df..35c2c6eb86ac603b8970aa61af04d15076a78ca3 100644 (file)
@@ -507,6 +507,7 @@ extern void pgstat_bestart(void);
 extern void pgstat_report_activity(const char *what);
 extern void pgstat_report_xact_timestamp(TimestampTz tstamp);
 extern void pgstat_report_waiting(bool waiting);
+extern const char *pgstat_get_backend_current_activity(int pid);
 
 extern void pgstat_initstats(Relation rel);