ce426f
Backport of these upstream commits:
ce426f
ce426f
commit 29d794863cd6e03115d3670707cc873a9965ba92
ce426f
Author: Florian Weimer <fweimer@redhat.com>
ce426f
Date:   Thu Apr 14 09:17:02 2016 +0200
ce426f
ce426f
    malloc: Run fork handler as late as possible [BZ #19431]
ce426f
    
ce426f
    Previously, a thread M invoking fork would acquire locks in this order:
ce426f
    
ce426f
      (M1) malloc arena locks (in the registered fork handler)
ce426f
      (M2) libio list lock
ce426f
    
ce426f
    A thread F invoking flush (NULL) would acquire locks in this order:
ce426f
    
ce426f
      (F1) libio list lock
ce426f
      (F2) individual _IO_FILE locks
ce426f
    
ce426f
    A thread G running getdelim would use this order:
ce426f
    
ce426f
      (G1) _IO_FILE lock
ce426f
      (G2) malloc arena lock
ce426f
    
ce426f
    After executing (M1), (F1), (G1), none of the threads can make progress.
ce426f
    
ce426f
    This commit changes the fork lock order to:
ce426f
    
ce426f
      (M'1) libio list lock
ce426f
      (M'2) malloc arena locks
ce426f
    
ce426f
    It explicitly encodes the lock order in the implementations of fork,
ce426f
    and does not rely on the registration order, thus avoiding the deadlock.
ce426f
ce426f
commit 186fe877f3df0b84d57dfbf0386f6332c6aa69bc
ce426f
Author: Florian Weimer <fweimer@redhat.com>
ce426f
Date:   Thu Apr 14 12:53:03 2016 +0200
ce426f
ce426f
    malloc: Add missing internal_function attributes on function definitions
ce426f
    
ce426f
    Fixes build on i386 after commit 29d794863cd6e03115d3670707cc873a9965ba92.
ce426f
ce426f
Index: b/malloc/Makefile
ce426f
===================================================================
ce426f
--- a/malloc/Makefile
ce426f
+++ b/malloc/Makefile
ce426f
@@ -28,7 +28,7 @@ tests := mallocbug tst-malloc tst-valloc
ce426f
 	 tst-mallocstate tst-mcheck tst-mallocfork tst-trim1 \
ce426f
 	 tst-malloc-usable \
ce426f
 	 tst-malloc-backtrace tst-malloc-thread-exit \
ce426f
-	 tst-malloc-thread-fail
ce426f
+	 tst-malloc-thread-fail tst-malloc-fork-deadlock
ce426f
 test-srcs = tst-mtrace
ce426f
 
ce426f
 routines = malloc morecore mcheck mtrace obstack
ce426f
@@ -49,6 +49,7 @@ $(objpfx)tst-malloc-thread-fail: $(commo
ce426f
 			       $(common-objpfx)nptl/libpthread_nonshared.a
ce426f
 $(objpfx)tst-malloc-thread-exit: $(common-objpfx)nptl/libpthread.so \
ce426f
 			       $(common-objpfx)nptl/libpthread_nonshared.a
ce426f
+$(objpfx)tst-malloc-fork-deadlock: $(shared-thread-library)
ce426f
 
ce426f
 # These should be removed by `make clean'.
ce426f
 extra-objs = mcheck-init.o libmcheck.a
ce426f
Index: b/malloc/arena.c
ce426f
===================================================================
ce426f
--- a/malloc/arena.c
ce426f
+++ b/malloc/arena.c
ce426f
@@ -162,10 +162,6 @@ static void           (*save_free_hook)
ce426f
 					 const __malloc_ptr_t);
ce426f
 static void*        save_arena;
ce426f
 
ce426f
-#ifdef ATFORK_MEM
ce426f
-ATFORK_MEM;
ce426f
-#endif
ce426f
-
ce426f
 /* Magic value for the thread-specific arena pointer when
ce426f
    malloc_atfork() is in use.  */
ce426f
 
ce426f
@@ -228,14 +224,15 @@ free_atfork(void* mem, const void *calle
ce426f
 /* Counter for number of times the list is locked by the same thread.  */
ce426f
 static unsigned int atfork_recursive_cntr;
ce426f
 
ce426f
-/* The following two functions are registered via thread_atfork() to
ce426f
-   make sure that the mutexes remain in a consistent state in the
ce426f
-   fork()ed version of a thread.  Also adapt the malloc and free hooks
ce426f
-   temporarily, because the `atfork' handler mechanism may use
ce426f
-   malloc/free internally (e.g. in LinuxThreads). */
ce426f
+/* The following three functions are called around fork from a
ce426f
+   multi-threaded process.  We do not use the general fork handler
ce426f
+   mechanism to make sure that our handlers are the last ones being
ce426f
+   called, so that other fork handlers can use the malloc
ce426f
+   subsystem.  */
ce426f
 
ce426f
-static void
ce426f
-ptmalloc_lock_all (void)
ce426f
+void
ce426f
+internal_function
ce426f
+__malloc_fork_lock_parent (void)
ce426f
 {
ce426f
   mstate ar_ptr;
ce426f
 
ce426f
@@ -243,7 +240,7 @@ ptmalloc_lock_all (void)
ce426f
     return;
ce426f
 
ce426f
   /* We do not acquire free_list_lock here because we completely
ce426f
-     reconstruct free_list in ptmalloc_unlock_all2.  */
ce426f
+     reconstruct free_list in __malloc_fork_unlock_child.  */
ce426f
 
ce426f
   if (mutex_trylock(&list_lock))
ce426f
     {
ce426f
@@ -268,7 +265,7 @@ ptmalloc_lock_all (void)
ce426f
   __free_hook = free_atfork;
ce426f
   /* Only the current thread may perform malloc/free calls now.
ce426f
      save_arena will be reattached to the current thread, in
ce426f
-     ptmalloc_lock_all, so save_arena->attached_threads is not
ce426f
+     __malloc_fork_lock_parent, so save_arena->attached_threads is not
ce426f
      updated.  */
ce426f
   tsd_getspecific(arena_key, save_arena);
ce426f
   tsd_setspecific(arena_key, ATFORK_ARENA_PTR);
ce426f
@@ -276,8 +273,9 @@ ptmalloc_lock_all (void)
ce426f
   ++atfork_recursive_cntr;
ce426f
 }
ce426f
 
ce426f
-static void
ce426f
-ptmalloc_unlock_all (void)
ce426f
+void
ce426f
+internal_function
ce426f
+__malloc_fork_unlock_parent (void)
ce426f
 {
ce426f
   mstate ar_ptr;
ce426f
 
ce426f
@@ -286,8 +284,8 @@ ptmalloc_unlock_all (void)
ce426f
   if (--atfork_recursive_cntr != 0)
ce426f
     return;
ce426f
   /* Replace ATFORK_ARENA_PTR with save_arena.
ce426f
-     save_arena->attached_threads was not changed in ptmalloc_lock_all
ce426f
-     and is still correct.  */
ce426f
+     save_arena->attached_threads was not changed in
ce426f
+     __malloc_fork_lock_parent and is still correct.  */
ce426f
   tsd_setspecific(arena_key, save_arena);
ce426f
   __malloc_hook = save_malloc_hook;
ce426f
   __free_hook = save_free_hook;
ce426f
@@ -299,15 +297,9 @@ ptmalloc_unlock_all (void)
ce426f
   (void)mutex_unlock(&list_lock);
ce426f
 }
ce426f
 
ce426f
-# ifdef __linux__
ce426f
-
ce426f
-/* In NPTL, unlocking a mutex in the child process after a
ce426f
-   fork() is currently unsafe, whereas re-initializing it is safe and
ce426f
-   does not leak resources.  Therefore, a special atfork handler is
ce426f
-   installed for the child. */
ce426f
-
ce426f
-static void
ce426f
-ptmalloc_unlock_all2 (void)
ce426f
+void
ce426f
+internal_function
ce426f
+__malloc_fork_unlock_child (void)
ce426f
 {
ce426f
   mstate ar_ptr;
ce426f
 
ce426f
@@ -338,12 +330,6 @@ ptmalloc_unlock_all2 (void)
ce426f
   atfork_recursive_cntr = 0;
ce426f
 }
ce426f
 
ce426f
-# else
ce426f
-
ce426f
-#  define ptmalloc_unlock_all2 ptmalloc_unlock_all
ce426f
-
ce426f
-# endif
ce426f
-
ce426f
 #endif  /* !NO_THREADS */
ce426f
 
ce426f
 /* Initialization routine. */
ce426f
@@ -413,7 +399,6 @@ ptmalloc_init (void)
ce426f
 
ce426f
   tsd_key_create(&arena_key, NULL);
ce426f
   tsd_setspecific(arena_key, (void *)&main_arena);
ce426f
-  thread_atfork(ptmalloc_lock_all, ptmalloc_unlock_all, ptmalloc_unlock_all2);
ce426f
   const char *s = NULL;
ce426f
   if (__builtin_expect (_environ != NULL, 1))
ce426f
     {
ce426f
@@ -487,12 +472,6 @@ ptmalloc_init (void)
ce426f
   __malloc_initialized = 1;
ce426f
 }
ce426f
 
ce426f
-/* There are platforms (e.g. Hurd) with a link-time hook mechanism. */
ce426f
-#ifdef thread_atfork_static
ce426f
-thread_atfork_static(ptmalloc_lock_all, ptmalloc_unlock_all, \
ce426f
-		     ptmalloc_unlock_all2)
ce426f
-#endif
ce426f
-
ce426f
 
ce426f
 
ce426f
 /* Managing heaps and arenas (for concurrent threads) */
ce426f
@@ -827,7 +806,8 @@ _int_new_arena(size_t size)
ce426f
      limit is reached).  At this point, some arena has to be attached
ce426f
      to two threads.  We could acquire the arena lock before list_lock
ce426f
      to make it less likely that reused_arena picks this new arena,
ce426f
-     but this could result in a deadlock with ptmalloc_lock_all.  */
ce426f
+     but this could result in a deadlock with
ce426f
+     __malloc_fork_lock_parent.  */
ce426f
 
ce426f
   (void) mutex_lock (&a->mutex);
ce426f
 
ce426f
Index: b/malloc/malloc-internal.h
ce426f
===================================================================
ce426f
--- /dev/null
ce426f
+++ b/malloc/malloc-internal.h
ce426f
@@ -0,0 +1,32 @@
ce426f
+/* Internal declarations for malloc, for use within libc.
ce426f
+   Copyright (C) 2016 Free Software Foundation, Inc.
ce426f
+   This file is part of the GNU C Library.
ce426f
+
ce426f
+   The GNU C Library is free software; you can redistribute it and/or
ce426f
+   modify it under the terms of the GNU Lesser General Public License as
ce426f
+   published by the Free Software Foundation; either version 2.1 of the
ce426f
+   License, or (at your option) any later version.
ce426f
+
ce426f
+   The GNU C Library is distributed in the hope that it will be useful,
ce426f
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
ce426f
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
ce426f
+   Lesser General Public License for more details.
ce426f
+
ce426f
+   You should have received a copy of the GNU Lesser General Public
ce426f
+   License along with the GNU C Library; see the file COPYING.LIB.  If
ce426f
+   not, see <http://www.gnu.org/licenses/>.  */
ce426f
+
ce426f
+#ifndef _MALLOC_PRIVATE_H
ce426f
+#define _MALLOC_PRIVATE_H
ce426f
+
ce426f
+/* Called in the parent process before a fork.  */
ce426f
+void __malloc_fork_lock_parent (void) internal_function attribute_hidden;
ce426f
+
ce426f
+/* Called in the parent process after a fork.  */
ce426f
+void __malloc_fork_unlock_parent (void) internal_function attribute_hidden;
ce426f
+
ce426f
+/* Called in the child process after a fork.  */
ce426f
+void __malloc_fork_unlock_child (void) internal_function attribute_hidden;
ce426f
+
ce426f
+
ce426f
+#endif /* _MALLOC_PRIVATE_H */
ce426f
Index: b/malloc/malloc.c
ce426f
===================================================================
ce426f
--- a/malloc/malloc.c
ce426f
+++ b/malloc/malloc.c
ce426f
@@ -291,6 +291,7 @@ __malloc_assert (const char *assertion,
ce426f
 }
ce426f
 #endif
ce426f
 
ce426f
+#include <malloc/malloc-internal.h>
ce426f
 
ce426f
 /*
ce426f
   INTERNAL_SIZE_T is the word-size used for internal bookkeeping
ce426f
Index: b/malloc/tst-malloc-fork-deadlock.c
ce426f
===================================================================
ce426f
--- /dev/null
ce426f
+++ b/malloc/tst-malloc-fork-deadlock.c
ce426f
@@ -0,0 +1,220 @@
ce426f
+/* Test concurrent fork, getline, and fflush (NULL).
ce426f
+   Copyright (C) 2016 Free Software Foundation, Inc.
ce426f
+   This file is part of the GNU C Library.
ce426f
+
ce426f
+   The GNU C Library is free software; you can redistribute it and/or
ce426f
+   modify it under the terms of the GNU Lesser General Public License as
ce426f
+   published by the Free Software Foundation; either version 2.1 of the
ce426f
+   License, or (at your option) any later version.
ce426f
+
ce426f
+   The GNU C Library is distributed in the hope that it will be useful,
ce426f
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
ce426f
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
ce426f
+   Lesser General Public License for more details.
ce426f
+
ce426f
+   You should have received a copy of the GNU Lesser General Public
ce426f
+   License along with the GNU C Library; see the file COPYING.LIB.  If
ce426f
+   not, see <http://www.gnu.org/licenses/>.  */
ce426f
+
ce426f
+#include <sys/wait.h>
ce426f
+#include <unistd.h>
ce426f
+#include <errno.h>
ce426f
+#include <stdio.h>
ce426f
+#include <pthread.h>
ce426f
+#include <stdbool.h>
ce426f
+#include <stdlib.h>
ce426f
+#include <malloc.h>
ce426f
+#include <time.h>
ce426f
+#include <string.h>
ce426f
+#include <signal.h>
ce426f
+
ce426f
+static int do_test (void);
ce426f
+#define TEST_FUNCTION do_test ()
ce426f
+#include "../test-skeleton.c"
ce426f
+
ce426f
+enum {
ce426f
+  /* Number of threads which call fork.  */
ce426f
+  fork_thread_count = 4,
ce426f
+  /* Number of threads which call getline (and, indirectly,
ce426f
+     malloc).  */
ce426f
+  read_thread_count = 8,
ce426f
+};
ce426f
+
ce426f
+static bool termination_requested;
ce426f
+
ce426f
+static void *
ce426f
+fork_thread_function (void *closure)
ce426f
+{
ce426f
+  while (!__atomic_load_n (&termination_requested, __ATOMIC_RELAXED))
ce426f
+    {
ce426f
+      pid_t pid = fork ();
ce426f
+      if (pid < 0)
ce426f
+        {
ce426f
+          printf ("error: fork: %m\n");
ce426f
+          abort ();
ce426f
+        }
ce426f
+      else if (pid == 0)
ce426f
+        _exit (17);
ce426f
+
ce426f
+      int status;
ce426f
+      if (waitpid (pid, &status, 0) < 0)
ce426f
+        {
ce426f
+          printf ("error: waitpid: %m\n");
ce426f
+          abort ();
ce426f
+        }
ce426f
+      if (!WIFEXITED (status) || WEXITSTATUS (status) != 17)
ce426f
+        {
ce426f
+          printf ("error: waitpid returned invalid status: %d\n", status);
ce426f
+          abort ();
ce426f
+        }
ce426f
+    }
ce426f
+  return NULL;
ce426f
+}
ce426f
+
ce426f
+static char *file_to_read;
ce426f
+
ce426f
+static void *
ce426f
+read_thread_function (void *closure)
ce426f
+{
ce426f
+  FILE *f = fopen (file_to_read, "r");
ce426f
+  if (f == NULL)
ce426f
+    {
ce426f
+      printf ("error: fopen (%s): %m\n", file_to_read);
ce426f
+      abort ();
ce426f
+    }
ce426f
+
ce426f
+  while (!__atomic_load_n (&termination_requested, __ATOMIC_RELAXED))
ce426f
+    {
ce426f
+      rewind (f);
ce426f
+      char *line = NULL;
ce426f
+      size_t line_allocated = 0;
ce426f
+      ssize_t ret = getline (&line, &line_allocated, f);
ce426f
+      if (ret < 0)
ce426f
+        {
ce426f
+          printf ("error: getline: %m\n");
ce426f
+          abort ();
ce426f
+        }
ce426f
+      free (line);
ce426f
+    }
ce426f
+  fclose (f);
ce426f
+
ce426f
+  return NULL;
ce426f
+}
ce426f
+
ce426f
+static void *
ce426f
+flushall_thread_function (void *closure)
ce426f
+{
ce426f
+  while (!__atomic_load_n (&termination_requested, __ATOMIC_RELAXED))
ce426f
+    if (fflush (NULL) != 0)
ce426f
+      {
ce426f
+        printf ("error: fflush (NULL): %m\n");
ce426f
+        abort ();
ce426f
+      }
ce426f
+  return NULL;
ce426f
+}
ce426f
+
ce426f
+static void
ce426f
+create_threads (pthread_t *threads, size_t count, void *(*func) (void *))
ce426f
+{
ce426f
+  for (size_t i = 0; i < count; ++i)
ce426f
+    {
ce426f
+      int ret = pthread_create (threads + i, NULL, func, NULL);
ce426f
+      if (ret != 0)
ce426f
+        {
ce426f
+          errno = ret;
ce426f
+          printf ("error: pthread_create: %m\n");
ce426f
+          abort ();
ce426f
+        }
ce426f
+    }
ce426f
+}
ce426f
+
ce426f
+static void
ce426f
+join_threads (pthread_t *threads, size_t count)
ce426f
+{
ce426f
+  for (size_t i = 0; i < count; ++i)
ce426f
+    {
ce426f
+      int ret = pthread_join (threads[i], NULL);
ce426f
+      if (ret != 0)
ce426f
+        {
ce426f
+          errno = ret;
ce426f
+          printf ("error: pthread_join: %m\n");
ce426f
+          abort ();
ce426f
+        }
ce426f
+    }
ce426f
+}
ce426f
+
ce426f
+/* Create a file which consists of a single long line, and assigns
ce426f
+   file_to_read.  The hope is that this triggers an allocation in
ce426f
+   getline which needs a lock.  */
ce426f
+static void
ce426f
+create_file_with_large_line (void)
ce426f
+{
ce426f
+  int fd = create_temp_file ("bug19431-large-line", &file_to_read);
ce426f
+  if (fd < 0)
ce426f
+    {
ce426f
+      printf ("error: create_temp_file: %m\n");
ce426f
+      abort ();
ce426f
+    }
ce426f
+  FILE *f = fdopen (fd, "w+");
ce426f
+  if (f == NULL)
ce426f
+    {
ce426f
+      printf ("error: fdopen: %m\n");
ce426f
+      abort ();
ce426f
+    }
ce426f
+  for (int i = 0; i < 50000; ++i)
ce426f
+    fputc ('x', f);
ce426f
+  fputc ('\n', f);
ce426f
+  if (ferror (f))
ce426f
+    {
ce426f
+      printf ("error: fputc: %m\n");
ce426f
+      abort ();
ce426f
+    }
ce426f
+  if (fclose (f) != 0)
ce426f
+    {
ce426f
+      printf ("error: fclose: %m\n");
ce426f
+      abort ();
ce426f
+    }
ce426f
+}
ce426f
+
ce426f
+static int
ce426f
+do_test (void)
ce426f
+{
ce426f
+  /* Make sure that we do not exceed the arena limit with the number
ce426f
+     of threads we configured.  */
ce426f
+  if (mallopt (M_ARENA_MAX, 400) == 0)
ce426f
+    {
ce426f
+      printf ("error: mallopt (M_ARENA_MAX) failed\n");
ce426f
+      return 1;
ce426f
+    }
ce426f
+
ce426f
+  /* Leave some room for shutting down all threads gracefully.  */
ce426f
+  int timeout = 3;
ce426f
+  if (timeout > TIMEOUT)
ce426f
+    timeout = TIMEOUT - 1;
ce426f
+
ce426f
+  create_file_with_large_line ();
ce426f
+
ce426f
+  pthread_t fork_threads[fork_thread_count];
ce426f
+  create_threads (fork_threads, fork_thread_count, fork_thread_function);
ce426f
+  pthread_t read_threads[read_thread_count];
ce426f
+  create_threads (read_threads, read_thread_count, read_thread_function);
ce426f
+  pthread_t flushall_threads[1];
ce426f
+  create_threads (flushall_threads, 1, flushall_thread_function);
ce426f
+
ce426f
+  struct timespec ts = {timeout, 0};
ce426f
+  if (nanosleep (&ts, NULL))
ce426f
+    {
ce426f
+      printf ("error: error: nanosleep: %m\n");
ce426f
+      abort ();
ce426f
+    }
ce426f
+
ce426f
+  __atomic_store_n (&termination_requested, true, __ATOMIC_RELAXED);
ce426f
+
ce426f
+  join_threads (flushall_threads, 1);
ce426f
+  join_threads (read_threads, read_thread_count);
ce426f
+  join_threads (fork_threads, fork_thread_count);
ce426f
+
ce426f
+  free (file_to_read);
ce426f
+
ce426f
+  return 0;
ce426f
+}
ce426f
Index: b/manual/memory.texi
ce426f
===================================================================
ce426f
--- a/manual/memory.texi
ce426f
+++ b/manual/memory.texi
ce426f
@@ -1055,14 +1055,6 @@ systems that do not support @w{ISO C11}.
ce426f
 @c     _dl_addr_inside_object ok
ce426f
 @c    determine_info ok
ce426f
 @c    __rtld_lock_unlock_recursive (dl_load_lock) @aculock
ce426f
-@c   thread_atfork @asulock @aculock @acsfd @acsmem
ce426f
-@c    __register_atfork @asulock @aculock @acsfd @acsmem
ce426f
-@c     lll_lock (__fork_lock) @asulock @aculock
ce426f
-@c     fork_handler_alloc @asulock @aculock @acsfd @acsmem
ce426f
-@c      calloc dup @asulock @aculock @acsfd @acsmem
ce426f
-@c     __linkin_atfork ok
ce426f
-@c      catomic_compare_and_exchange_bool_acq ok
ce426f
-@c     lll_unlock (__fork_lock) @aculock
ce426f
 @c   *_environ @mtsenv
ce426f
 @c   next_env_entry ok
ce426f
 @c   strcspn dup ok
ce426f
Index: b/nptl/sysdeps/unix/sysv/linux/fork.c
ce426f
===================================================================
ce426f
--- a/nptl/sysdeps/unix/sysv/linux/fork.c
ce426f
+++ b/nptl/sysdeps/unix/sysv/linux/fork.c
ce426f
@@ -29,7 +29,7 @@
ce426f
 #include <bits/stdio-lock.h>
ce426f
 #include <atomic.h>
ce426f
 #include <pthreadP.h>
ce426f
-
ce426f
+#include <malloc/malloc-internal.h>
ce426f
 
ce426f
 unsigned long int *__fork_generation_pointer;
ce426f
 
ce426f
@@ -116,6 +116,11 @@ __libc_fork (void)
ce426f
 
ce426f
   _IO_list_lock ();
ce426f
 
ce426f
+  /* Acquire malloc locks.  This needs to come last because fork
ce426f
+     handlers may use malloc, and the libio list lock has an indirect
ce426f
+     malloc dependency as well (via the getdelim function).  */
ce426f
+  __malloc_fork_lock_parent ();
ce426f
+
ce426f
 #ifndef NDEBUG
ce426f
   pid_t ppid = THREAD_GETMEM (THREAD_SELF, tid);
ce426f
 #endif
ce426f
@@ -172,6 +177,9 @@ __libc_fork (void)
ce426f
 # endif
ce426f
 #endif
ce426f
 
ce426f
+      /* Release malloc locks.  */
ce426f
+      __malloc_fork_unlock_child ();
ce426f
+
ce426f
       /* Reset the file list.  These are recursive mutexes.  */
ce426f
       fresetlockfiles ();
ce426f
 
ce426f
@@ -213,6 +221,9 @@ __libc_fork (void)
ce426f
       /* Restore the PID value.  */
ce426f
       THREAD_SETMEM (THREAD_SELF, pid, parentpid);
ce426f
 
ce426f
+      /* Release malloc locks, parent process variant.  */
ce426f
+      __malloc_fork_unlock_parent ();
ce426f
+
ce426f
       /* We execute this even if the 'fork' call failed.  */
ce426f
       _IO_list_unlock ();
ce426f