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