Install an atexit(2) callback that ensures that proc_exit's cleanup processing
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 5 May 2009 20:06:07 +0000 (20:06 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 5 May 2009 20:06:07 +0000 (20:06 +0000)
will still be performed if something in a backend process calls exit()
directly, instead of going through proc_exit() as we prefer.  This is a second
response to the issue that we might load third-party code that doesn't know it
should not call exit().  Such a call will now cause a reasonably graceful
backend shutdown, if possible.  (Of course, if the reason for the exit() call
is out-of-memory or some such, we might not be able to recover, but at least
we will try.)

src/backend/storage/ipc/ipc.c

index 3cb320e57518043451d4d09b7fc008d7c8eeede0..5969e6e425b151070d159bec8dcb91f4644d069b 100644 (file)
@@ -13,7 +13,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/storage/ipc/ipc.c,v 1.102 2009/01/01 17:23:47 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/storage/ipc/ipc.c,v 1.103 2009/05/05 20:06:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
  */
 bool       proc_exit_inprogress = false;
 
+/*
+ * This flag tracks whether we've called atexit(2) in the current process
+ * (or in the parent postmaster).
+ */
+static bool atexit_callback_setup = false;
+
+/* local functions */
+static void proc_exit_prepare(int code);
+
 
 /* ----------------------------------------------------------------
  *                     exit() handling stuff
@@ -69,51 +78,21 @@ static int  on_proc_exit_index,
  *
  *     this function calls all the callbacks registered
  *     for it (to free resources) and then calls exit.
+ *
  *     This should be the only function to call exit().
  *     -cim 2/6/90
+ *
+ *     Unfortunately, we can't really guarantee that add-on code
+ *     obeys the rule of not calling exit() directly.  So, while
+ *     this is the preferred way out of the system, we also register
+ *     an atexit callback that will make sure cleanup happens.
  * ----------------------------------------------------------------
  */
 void
 proc_exit(int code)
 {
-   /*
-    * Once we set this flag, we are committed to exit.  Any ereport() will
-    * NOT send control back to the main loop, but right back here.
-    */
-   proc_exit_inprogress = true;
-
-   /*
-    * Forget any pending cancel or die requests; we're doing our best to
-    * close up shop already.  Note that the signal handlers will not set
-    * these flags again, now that proc_exit_inprogress is set.
-    */
-   InterruptPending = false;
-   ProcDiePending = false;
-   QueryCancelPending = false;
-   /* And let's just make *sure* we're not interrupted ... */
-   ImmediateInterruptOK = false;
-   InterruptHoldoffCount = 1;
-   CritSectionCount = 0;
-
-   elog(DEBUG3, "proc_exit(%d)", code);
-
-   /* do our shared memory exits first */
-   shmem_exit(code);
-
-   /*
-    * call all the callbacks registered before calling exit().
-    *
-    * Note that since we decrement on_proc_exit_index each time, if a
-    * callback calls ereport(ERROR) or ereport(FATAL) then it won't be
-    * invoked again when control comes back here (nor will the
-    * previously-completed callbacks).  So, an infinite loop should not be
-    * possible.
-    */
-   while (--on_proc_exit_index >= 0)
-       (*on_proc_exit_list[on_proc_exit_index].function) (code,
-                                 on_proc_exit_list[on_proc_exit_index].arg);
-
-   elog(DEBUG3, "exit(%d)", code);
+   /* Clean up everything that must be cleaned up */
+   proc_exit_prepare(code);
 
 #ifdef PROFILE_PID_DIR
    {
@@ -134,7 +113,10 @@ proc_exit(int code)
         *
         * Note that we do this here instead of in an on_proc_exit() callback
         * because we want to ensure that this code executes last - we don't
-        * want to interfere with any other on_proc_exit() callback.
+        * want to interfere with any other on_proc_exit() callback.  For
+        * the same reason, we do not include it in proc_exit_prepare ...
+        * so if you are exiting in the "wrong way" you won't drop your profile
+        * in a nice place.
         */
        char        gprofDirName[32];
 
@@ -149,9 +131,59 @@ proc_exit(int code)
    }
 #endif
 
+   elog(DEBUG3, "exit(%d)", code);
+
    exit(code);
 }
 
+/*
+ * Code shared between proc_exit and the atexit handler.  Note that in
+ * normal exit through proc_exit, this will actually be called twice ...
+ * but the second call will have nothing to do.
+ */
+static void
+proc_exit_prepare(int code)
+{
+   /*
+    * Once we set this flag, we are committed to exit.  Any ereport() will
+    * NOT send control back to the main loop, but right back here.
+    */
+   proc_exit_inprogress = true;
+
+   /*
+    * Forget any pending cancel or die requests; we're doing our best to
+    * close up shop already.  Note that the signal handlers will not set
+    * these flags again, now that proc_exit_inprogress is set.
+    */
+   InterruptPending = false;
+   ProcDiePending = false;
+   QueryCancelPending = false;
+   /* And let's just make *sure* we're not interrupted ... */
+   ImmediateInterruptOK = false;
+   InterruptHoldoffCount = 1;
+   CritSectionCount = 0;
+
+   /* do our shared memory exits first */
+   shmem_exit(code);
+
+   elog(DEBUG3, "proc_exit(%d)", code);
+
+   /*
+    * call all the registered callbacks.
+    *
+    * Note that since we decrement on_proc_exit_index each time, if a
+    * callback calls ereport(ERROR) or ereport(FATAL) then it won't be
+    * invoked again when control comes back here (nor will the
+    * previously-completed callbacks).  So, an infinite loop should not be
+    * possible.
+    */
+   while (--on_proc_exit_index >= 0)
+       (*on_proc_exit_list[on_proc_exit_index].function) (code,
+                                 on_proc_exit_list[on_proc_exit_index].arg);
+
+   on_proc_exit_index = 0;
+}
+
 /* ------------------
  * Run all of the on_shmem_exit routines --- but don't actually exit.
  * This is used by the postmaster to re-initialize shared memory and
@@ -176,6 +208,37 @@ shmem_exit(int code)
    on_shmem_exit_index = 0;
 }
 
+/* ----------------------------------------------------------------
+ *     atexit_callback
+ *
+ *     Backstop to ensure that direct calls of exit() don't mess us up.
+ *
+ * Somebody who was being really uncooperative could call _exit(),
+ * but for that case we have a "dead man switch" that will make the
+ * postmaster treat it as a crash --- see pmsignal.c.
+ * ----------------------------------------------------------------
+ */
+#ifdef HAVE_ATEXIT
+
+static void
+atexit_callback(void)
+{
+   /* Clean up everything that must be cleaned up */
+   /* ... too bad we don't know the real exit code ... */
+   proc_exit_prepare(-1);
+}
+
+#else  /* assume we have on_exit instead */
+
+static void
+atexit_callback(int exitstatus, void *arg)
+{
+   /* Clean up everything that must be cleaned up */
+   proc_exit_prepare(exitstatus);
+}
+
+#endif /* HAVE_ATEXIT */
+
 /* ----------------------------------------------------------------
  *     on_proc_exit
  *
@@ -195,6 +258,16 @@ on_proc_exit(pg_on_exit_callback function, Datum arg)
    on_proc_exit_list[on_proc_exit_index].arg = arg;
 
    ++on_proc_exit_index;
+
+   if (!atexit_callback_setup)
+   {
+#ifdef HAVE_ATEXIT
+       atexit(atexit_callback);
+#else
+       on_exit(atexit_callback, NULL);
+#endif
+       atexit_callback_setup = true;
+   }
 }
 
 /* ----------------------------------------------------------------
@@ -216,6 +289,16 @@ on_shmem_exit(pg_on_exit_callback function, Datum arg)
    on_shmem_exit_list[on_shmem_exit_index].arg = arg;
 
    ++on_shmem_exit_index;
+
+   if (!atexit_callback_setup)
+   {
+#ifdef HAVE_ATEXIT
+       atexit(atexit_callback);
+#else
+       on_exit(atexit_callback, NULL);
+#endif
+       atexit_callback_setup = true;
+   }
 }
 
 /* ----------------------------------------------------------------