Complete rewrite of the atfork handler implementation. It is now
authordrepper <drepper>
Fri, 9 May 2003 03:02:58 +0000 (03:02 +0000)
committerdrepper <drepper>
Fri, 9 May 2003 03:02:58 +0000 (03:02 +0000)
lockless in fork().

nptl/sysdeps/unix/sysv/linux/fork.c
nptl/sysdeps/unix/sysv/linux/register-atfork.c
nptl/sysdeps/unix/sysv/linux/unregister-atfork.c

index 94d8995..8bca6b4 100644 (file)
 #include <hp-timing.h>
 #include <ldsodefs.h>
 #include <bits/stdio-lock.h>
+#include <atomic.h>
 
 
 unsigned long int *__fork_generation_pointer;
 
 
-lll_lock_t __fork_lock = LLL_LOCK_INITIALIZER;
-LIST_HEAD (__fork_prepare_list);
-LIST_HEAD (__fork_parent_list);
-LIST_HEAD (__fork_child_list);
+
+/* The single linked list of all currently registered for handlers.  */
+struct fork_handler *__fork_handlers;
 
 
 static void
@@ -53,20 +53,62 @@ pid_t
 __libc_fork (void)
 {
   pid_t pid;
-  list_t *runp;
-
-  /* Get the lock so that the set of registered handlers is not
-     inconsistent or changes beneath us.  */
-  lll_lock (__fork_lock);
-
-  /* Run all the registered preparation handlers.  In reverse order.  */
-  list_for_each_prev (runp, &__fork_prepare_list)
+  struct used_handler
+  {
+    struct fork_handler *handler;
+    struct used_handler *next;
+  } *allp = NULL;
+
+  /* Run all the registered preparation handlers.  In reverse order.
+     While doing this we build up a list of all the entries.  */
+  struct fork_handler *runp;
+  while ((runp = __fork_handlers) != NULL)
     {
-      struct fork_handler *curp;
-
-      curp = list_entry (runp, struct fork_handler, list);
+      unsigned int oldval = runp->refcntr;
+
+      if (oldval == 0)
+       /* This means some other thread removed the list just after
+          the pointer has been loaded.  Try again.  Either the list
+          is empty or we can retry it.  */
+       continue;
+
+      /* Bump the reference counter.  */
+      if (atomic_compare_and_exchange_bool_acq (&__fork_handlers->refcntr,
+                                               oldval + 1, oldval))
+       /* The value changed, try again.  */
+       continue;
+
+      /* We bumped the reference counter for the first entry in the
+        list.  That means that none of the following entries will
+        just go away.  The unloading code works in the order of the
+        list.
+
+         While executing the registered handlers we are building a
+         list of all the entries so that we can go backward later on.  */
+      while (1)
+       {
+         /* Execute the handler if there is one.  */
+         if (runp->prepare_handler != NULL)
+           runp->prepare_handler ();
+
+         /* Create a new element for the list.  */
+         struct used_handler *newp
+           = (struct used_handler *) alloca (sizeof (*newp));
+         newp->handler = runp;
+         newp->next = allp;
+         allp = newp;
+
+         /* Advance to the next handler.  */
+         runp = runp->next;
+         if (runp == NULL)
+           break;
+
+         /* Bump the reference counter for the next entry.  */
+         atomic_increment (&runp->refcntr);
+       }
 
-      curp->handler ();
+      /* We are done.  */
+      break;
     }
 
   _IO_list_lock ();
@@ -107,13 +149,22 @@ __libc_fork (void)
       _IO_list_resetlock ();
 
       /* Run the handlers registered for the child.  */
-      list_for_each (runp, &__fork_child_list)
+      while (allp != NULL)
        {
-         struct fork_handler *curp;
+         if (allp->handler->child_handler != NULL)
+           allp->handler->child_handler ();
 
-         curp = list_entry (runp, struct fork_handler, list);
+         /* Note that we do not have to wake any possible waiter.
+            This is the only thread in the new process.  */
+         --allp->handler->refcntr;
 
-         curp->handler ();
+         /* XXX We could at this point look through the object pool
+            and mark all objects not on the __fork_handlers list as
+            unused.  This is necessary in case the fork() happened
+            while another thread called dlclose() and that call had
+            to create a new list.  */
+
+         allp = allp->next;
        }
 
       /* Initialize the fork lock.  */
@@ -127,17 +178,17 @@ __libc_fork (void)
       _IO_list_unlock ();
 
       /* Run the handlers registered for the parent.  */
-      list_for_each (runp, &__fork_parent_list)
+      while (allp != NULL)
        {
-         struct fork_handler *curp;
+         if (allp->handler->parent_handler != NULL)
+           allp->handler->parent_handler ();
 
-         curp = list_entry (runp, struct fork_handler, list);
+         if (atomic_decrement_and_test (&allp->handler->refcntr)
+             && allp->handler->need_signal)
+           lll_futex_wake (allp->handler->refcntr, 1);
 
-         curp->handler ();
+         allp = allp->next;
        }
-
-      /* Release the for lock.  */
-      lll_unlock (__fork_lock);
     }
 
   return pid;
index 6dbc163..299ae9d 100644 (file)
 
 #include <errno.h>
 #include <stdlib.h>
+#include <string.h>
 #include "fork.h"
 
 
-/* Defined in libc_pthread_init.c.  */
-extern struct fork_handler __pthread_child_handler attribute_hidden;
+/* Lock to protect allocation and deallocation of fork handlers.  */
+lll_lock_t __fork_lock = LLL_LOCK_INITIALIZER;
 
 
-int
-__register_atfork (prepare, parent, child, dso_handle)
-     void (*prepare) (void);
-     void (*parent) (void);
-     void (*child) (void);
-     void *dso_handle;
+/* Number of pre-allocated handler entries.  */
+#define NHANDLER 48
+
+/* Memory pool for fork handler structures.  */
+static struct fork_handler_pool
 {
-  struct fork_handler *new_prepare = NULL;
-  struct fork_handler *new_parent = NULL;
-  struct fork_handler *new_child = NULL;
+  struct fork_handler_pool *next;
+  struct fork_handler mem[NHANDLER];
+} fork_handler_pool;
 
-  if (prepare != NULL)
-    {
-      new_prepare = (struct fork_handler *) malloc (sizeof (*new_prepare));
-      if (new_prepare == NULL)
-       goto out1;
 
-      new_prepare->handler = prepare;
-      new_prepare->dso_handle = dso_handle;
-    }
+static struct fork_handler *
+fork_handler_alloc (void)
+{
+  struct fork_handler_pool *runp = &fork_handler_pool;
+  struct fork_handler *result = NULL;
+  unsigned int i;
 
-  if (parent != NULL)
+  do
     {
-      new_parent = (struct fork_handler *) malloc (sizeof (*new_parent));
-      if (new_parent == NULL)
-       goto out2;
-
-      new_parent->handler = parent;
-      new_parent->dso_handle = dso_handle;
+      /* Search for an empty entry.  */
+      for (i = 0; i < NHANDLER; ++i)
+       if (runp->mem[i].refcntr == 0)
+         goto found;
     }
+  while ((runp = runp->next) != NULL);
 
-  if (child != NULL)
+  /* We have to allocate a new entry.  */
+  runp = (struct fork_handler_pool *) calloc (1, sizeof (*runp));
+  if (runp != NULL)
     {
-      new_child = (struct fork_handler *) malloc (sizeof (*new_child));
-      if (new_child == NULL)
-       {
-         free (new_parent);
-       out2:
-         free (new_prepare);
-       out1:
-         return errno;
-       }
-
-      new_child->handler = child;
-      new_child->dso_handle = dso_handle;
+      /* Enqueue the new memory pool into the list.  */
+      runp->next = fork_handler_pool.next;
+      fork_handler_pool.next = runp;
+
+      /* We use the last entry on the page.  This means when we start
+        searching from the front the next time we will find the first
+        entry unused.  */
+      i = NHANDLER - 1;
+
+    found:
+      result = &runp->mem[i];
+      result->refcntr = 1;
+      result->need_signal = 0;
     }
 
-  /* Get the lock to not conflict with running forks.  */
-  lll_lock (__fork_lock);
-
-  /* Now that we have all the handlers allocate enqueue them.  */
-  if (new_prepare != NULL)
-    list_add_tail (&new_prepare->list, &__fork_prepare_list);
-  if (new_parent != NULL)
-    list_add_tail (&new_parent->list, &__fork_parent_list);
-  if (new_child != NULL)
-    list_add_tail (&new_child->list, &__fork_child_list);
-
-  /* Release the lock.  */
-  lll_unlock (__fork_lock);
-
-  return 0;
+  return result;
 }
 
 
-/* Three static memory blocks used when registering malloc.  */
-static struct fork_handler malloc_prepare;
-static struct fork_handler malloc_parent;
-static struct fork_handler malloc_child;
-
-
-void
-attribute_hidden
-__register_atfork_malloc (prepare, parent, child, dso_handle)
+int
+__register_atfork (prepare, parent, child, dso_handle)
      void (*prepare) (void);
      void (*parent) (void);
      void (*child) (void);
      void *dso_handle;
 {
-  /* Pre-fork handler.  */
-  malloc_prepare.handler = prepare;
-  malloc_prepare.dso_handle = dso_handle;
-
-  /* Parent handler.  */
-  malloc_parent.handler = parent;
-  malloc_parent.dso_handle = dso_handle;
-
-  /* Child handler.  */
-  malloc_child.handler = child;
-  malloc_child.dso_handle = dso_handle;
-
-  /* Get the lock to not conflict with running forks.  */
+  /* Get the lock to not conflict with other allocations.  */
   lll_lock (__fork_lock);
 
-  /* Now that we have all the handlers allocate enqueue them.  */
-  list_add_tail (&malloc_prepare.list, &__fork_prepare_list);
-  list_add_tail (&malloc_parent.list, &__fork_parent_list);
-  list_add_tail (&malloc_child.list, &__fork_child_list);
+  struct fork_handler *newp = fork_handler_alloc ();
+
+  if (newp != NULL)
+    {
+      /* Initialize the new record.  */
+      newp->prepare_handler = prepare;
+      newp->parent_handler = parent;
+      newp->child_handler = child;
+      newp->dso_handle = dso_handle;
+
+      newp->next = __fork_handlers;
+      __fork_handlers = newp;
+    }
 
   /* Release the lock.  */
   lll_unlock (__fork_lock);
+
+  return newp == NULL ? ENOMEM : 0;
 }
 
 
@@ -135,36 +113,22 @@ libc_freeres_fn (free_mem)
   /* Get the lock to not conflict with running forks.  */
   lll_lock (__fork_lock);
 
-  list_t *runp;
-  list_t *prevp;
-
-  list_for_each_prev_safe (runp, prevp, &__fork_prepare_list)
-    {
-      list_del (runp);
+  /* No more fork handlers.  */
+  __fork_handlers = NULL;
 
-      struct fork_handler *p = list_entry (runp, struct fork_handler, list);
-      if (p != &malloc_prepare)
-       free (p);
-    }
+  /* Free eventually alloated memory blocks for the object pool.  */
+  struct fork_handler_pool *runp = fork_handler_pool.next;
 
-  list_for_each_prev_safe (runp, prevp, &__fork_parent_list)
-    {
-      list_del (runp);
+  memset (&fork_handler_pool, '\0', sizeof (fork_handler_pool));
 
-      struct fork_handler *p = list_entry (runp, struct fork_handler, list);
-      if (p != &malloc_parent)
-       free (p);
-    }
+  /* Release the lock.  */
+  lll_unlock (__fork_lock);
 
-  list_for_each_prev_safe (runp, prevp, &__fork_child_list)
+  /* We can free the memory after releasing the lock.  */
+  while (runp != NULL)
     {
-      list_del (runp);
-
-      struct fork_handler *p = list_entry (runp, struct fork_handler, list);
-      if (p != &__pthread_child_handler && p != &malloc_child)
-       free (p);
+      struct fork_handler_pool *oldp;
+      runp = runp->next;
+      free (oldp);
     }
-
-  /* Release the lock.  */
-  lll_unlock (__fork_lock);
 }
index e9eb191..ac4b3c5 100644 (file)
 #include <errno.h>
 #include <stdlib.h>
 #include "fork.h"
-
-
-/* Defined in libc_pthread_init.c.  */
-extern struct fork_handler __pthread_child_handler attribute_hidden;
-/* Three static memory blocks used when registering malloc.  */
-static struct fork_handler malloc_prepare;
-static struct fork_handler malloc_parent;
-static struct fork_handler malloc_child;
+#include <atomic.h>
 
 
 void
 __unregister_atfork (dso_handle)
      void *dso_handle;
 {
-  /* Get the lock to not conflict with running forks.  */
-  lll_lock (__fork_lock);
-
-  list_t *runp;
-  list_t *prevp;
-
-  list_for_each_prev_safe (runp, prevp, &__fork_prepare_list)
-    if (list_entry (runp, struct fork_handler, list)->dso_handle == dso_handle)
+  /* Check whether there is any entry in the list which we have to
+     remove.  It is likely that this is not the case so don't bother
+     getting the lock.
+
+     We do not worry about other threads adding entries for this DSO
+     right this moment.  If this happens this is a race and we can do
+     whatever we please.  The program will crash anyway seen.  */
+  struct fork_handler *runp = __fork_handlers;
+  struct fork_handler *lastp = NULL;
+
+  while (runp != NULL)
+    if (runp->dso_handle == dso_handle)
+      break;
+    else
       {
-       list_del (runp);
-
-       struct fork_handler *p = list_entry (runp, struct fork_handler, list);
-       if (p != &malloc_prepare)
-         free (p);
+       lastp = runp;
+       runp = runp->next;
       }
 
-  list_for_each_prev_safe (runp, prevp, &__fork_parent_list)
-    if (list_entry (runp, struct fork_handler, list)->dso_handle == dso_handle)
-      {
-       list_del (runp);
+  if (runp == NULL)
+    /* Nothing to do.  */
+    return;
 
-       struct fork_handler *p = list_entry (runp, struct fork_handler, list);
-       if (p != &malloc_parent)
-         free (p);
-      }
-
-  list_for_each_prev_safe (runp, prevp, &__fork_child_list)
-    if (list_entry (runp, struct fork_handler, list)->dso_handle == dso_handle)
-      {
-       list_del (runp);
+  /* Get the lock to not conflict with additions or deletions.  Note
+     that there couldn't have been another thread deleting something.
+     The __unregister_atfork function is only called from the
+     dlclose() code which itself serializes the operations.  */
+  lll_lock (__fork_lock);
 
-       struct fork_handler *p = list_entry (runp, struct fork_handler, list);
-       if (p != &__pthread_child_handler && p != &malloc_child)
-         free (p);
-      }
+  /* We have to create a new list with all the entries we don't remove.  */
+  struct deleted_handler
+  {
+    struct fork_handler *handler;
+    struct deleted_handler *next;
+  } *deleted = NULL;
+
+  /* Remove the entries for the DSO which is unloaded from the list.
+     It's a single linked list so readers are.  */
+  do
+    {
+      if (runp->dso_handle == dso_handle)
+       {
+         if (lastp == NULL)
+           __fork_handlers = runp->next;
+         else
+           lastp->next = runp->next;
+
+         /* We cannot overwrite the ->next element now.  Put the deleted
+            entries in a separate list.  */
+         struct deleted_handler *newp = alloca (sizeof (*newp));
+         newp->handler = runp;
+         newp->next = deleted;
+         deleted = newp;
+       }
+      else
+       lastp = runp;
+
+      runp = runp->next;
+    }
+  while (runp != NULL);
 
   /* Release the lock.  */
   lll_unlock (__fork_lock);
+
+  /* Walk the list of all entries which have to be deleted.  */
+  while (deleted != NULL)
+    {
+      /* We need to be informed by possible current users.  */
+      deleted->handler->need_signal = 1;
+      /* Make sure this gets written out first.  */
+      atomic_write_barrier ();
+
+      /* Decrement the reference counter.  If it does not reach zero
+        wait for the last user.  */
+      atomic_decrement (&deleted->handler->refcntr);
+      unsigned int val;
+      while ((val = deleted->handler->refcntr) != 0)
+       lll_futex_wait (deleted->handler->refcntr, val);
+
+      deleted = deleted->next;
+    }
 }