diff --git a/nptl/Makefile b/nptl/Makefile
index de312b3477..a47a112dc5 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -391,7 +391,7 @@ tests += tst-cancelx2 tst-cancelx3 tst-cancelx4 tst-cancelx5 \
 	 tst-oncex3 tst-oncex4
 ifeq ($(build-shared),yes)
 tests += tst-atfork2 tst-tls4 tst-_res1 tst-fini1 tst-compat-forwarder \
-	 tst-audit-threads
+	 tst-audit-threads tst-atfork3
 tests-internal += tst-tls3 tst-tls3-malloc tst-tls5 tst-stackguard1
 tests-nolibpthread += tst-fini1
 ifeq ($(have-z-execstack),yes)
@@ -404,13 +404,14 @@ modules-names = tst-atfork2mod tst-tls3mod tst-tls4moda tst-tls4modb \
 		tst-tls5modd tst-tls5mode tst-tls5modf tst-stack4mod \
 		tst-_res1mod1 tst-_res1mod2 tst-execstack-mod tst-fini1mod \
 		tst-join7mod tst-compat-forwarder-mod tst-audit-threads-mod1 \
-		tst-audit-threads-mod2
+		tst-audit-threads-mod2 tst-atfork3mod
 extra-test-objs += $(addsuffix .os,$(strip $(modules-names))) \
 		   tst-cleanup4aux.o tst-cleanupx4aux.o
 test-extras += tst-cleanup4aux tst-cleanupx4aux
 test-modules = $(addprefix $(objpfx),$(addsuffix .so,$(modules-names)))
 
 tst-atfork2mod.so-no-z-defs = yes
+tst-atfork3mod.so-no-z-defs = yes
 tst-tls3mod.so-no-z-defs = yes
 tst-tls5mod.so-no-z-defs = yes
 tst-tls5moda.so-no-z-defs = yes
@@ -547,9 +548,12 @@ tst-cancelx7-ARGS = $(tst-cancel7-ARGS)
 tst-umask1-ARGS = $(objpfx)tst-umask1.temp
 
 $(objpfx)tst-atfork2: $(libdl) $(shared-thread-library)
+$(objpfx)tst-atfork3: $(libdl) $(shared-thread-library)
 LDFLAGS-tst-atfork2 = -rdynamic
+LDFLAGS-tst-atfork3 = -rdynamic
 tst-atfork2-ENV = MALLOC_TRACE=$(objpfx)tst-atfork2.mtrace
 $(objpfx)tst-atfork2mod.so: $(shared-thread-library)
+$(objpfx)tst-atfork3mod.so: $(shared-thread-library)
 
 tst-stack3-ENV = MALLOC_TRACE=$(objpfx)tst-stack3.mtrace
 $(objpfx)tst-stack3-mem.out: $(objpfx)tst-stack3.out
@@ -649,7 +653,7 @@ $(addprefix $(objpfx), $(tests-reverse)): \
 $(objpfx)../libc.so: $(common-objpfx)libc.so ;
 $(addprefix $(objpfx),$(tests-static) $(xtests-static)): $(objpfx)libpthread.a
 
-$(objpfx)tst-atfork2.out: $(objpfx)tst-atfork2mod.so
+$(objpfx)tst-atfork3.out: $(objpfx)tst-atfork3mod.so
 else
 $(addprefix $(objpfx),$(tests) $(test-srcs)): $(objpfx)libpthread.a
 endif
diff --git a/nptl/register-atfork.c b/nptl/register-atfork.c
index 80a1becb5f..09a61fc210 100644
--- a/nptl/register-atfork.c
+++ b/nptl/register-atfork.c
@@ -20,131 +20,151 @@
 #include <stdlib.h>
 #include <string.h>
 #include <fork.h>
-#include <atomic.h>
+#include <libc-lock.h>
 
-#define DYNARRAY_ELEMENT           struct fork_handler
-#define DYNARRAY_STRUCT            fork_handler_list
-#define DYNARRAY_PREFIX            fork_handler_list_
-#define DYNARRAY_INITIAL_SIZE      48
-#include <malloc/dynarray-skeleton.c>
+/* The codes uses a recursive mutex plus a linked-list to allow an atfork
+   handler to modify the fork handler list.  A real usercase is when callback
+   calls dlclose to unload a specific library, where the library itself has an
+   atfork handler and thus is removal will trigger __unregister_atfork.
 
-static struct fork_handler_list fork_handlers;
-static bool fork_handler_init = false;
+   On __run_fork_handlers, if the callback triggers __unregister_atfork
+   the fork_handlers_remove_if removes the elements to make sure the
+   walking can progress (either fowards or backwards).  */
 
-static int atfork_lock = LLL_LOCK_INITIALIZER;
+struct fork_handler_list_t
+{
+  struct fork_handler *first;
+  struct fork_handler *last;
+};
+static struct fork_handler_list_t fork_handlers = { NULL, NULL };
+__libc_lock_define_initialized_recursive (static, atfork_lock);
+
+/* Add the elements ENTRY on the end of FORK_HANDLE_LIST.  */
+static void
+fork_handlers_push_back (struct fork_handler *entry)
+{
+  entry->next = NULL;
+  entry->prev = fork_handlers.last;
+  if (entry->prev != NULL)
+    entry->prev->next = entry;
+  if (fork_handlers.first == NULL)
+    fork_handlers.first = entry;
+  fork_handlers.last = entry;
+}
 
-int
-__register_atfork (void (*prepare) (void), void (*parent) (void),
-		   void (*child) (void), void *dso_handle)
+/* Remove the element ENTRY from the list FORK_HANDLE_LIST, updating
+   its internal state.  */
+static void
+fork_handlers_remove (struct fork_handler *entry)
 {
-  lll_lock (atfork_lock, LLL_PRIVATE);
+  if (entry->prev != NULL)
+    entry->prev->next = entry->next;
+  else
+    fork_handlers.first = entry->next;
 
-  if (!fork_handler_init)
-    {
-      fork_handler_list_init (&fork_handlers);
-      fork_handler_init = true;
-    }
+  if (entry->next != NULL)
+    entry->next->prev = entry->prev;
+  else
+    fork_handlers.last = entry->prev;
 
-  struct fork_handler *newp = fork_handler_list_emplace (&fork_handlers);
-  if (newp != NULL)
+  free (entry);
+}
+
+/* Remove all the elements that has its internal dso_handle equal to
+   DSO_HANDLE.  */
+static void
+fork_handlers_remove_if (void *dso_handle)
+{
+  struct fork_handler *it = fork_handlers.first;
+  while (it != NULL)
     {
-      newp->prepare_handler = prepare;
-      newp->parent_handler = parent;
-      newp->child_handler = child;
-      newp->dso_handle = dso_handle;
+      if (it->dso_handle == dso_handle)
+	{
+	  struct fork_handler *entry = it;
+	  it = it->next;
+	  fork_handlers_remove (entry);
+	}
+      else
+	it = it->next;
     }
-
-  /* Release the lock.  */
-  lll_unlock (atfork_lock, LLL_PRIVATE);
-
-  return newp == NULL ? ENOMEM : 0;
 }
-libc_hidden_def (__register_atfork)
 
-static struct fork_handler *
-fork_handler_list_find (struct fork_handler_list *fork_handlers,
-			void *dso_handle)
+/* Remove all elements from FORK_HANDLE_LIST.  */
+static void
+fork_handlers_remove_all (void)
 {
-  for (size_t i = 0; i < fork_handler_list_size (fork_handlers); i++)
+  struct fork_handler *it = fork_handlers.first;
+  while (it != NULL)
     {
-      struct fork_handler *elem = fork_handler_list_at (fork_handlers, i);
-      if (elem->dso_handle == dso_handle)
-	return elem;
+      struct fork_handler *entry = it;
+      it = it->next;
+      fork_handlers_remove (entry);
     }
-  return NULL;
 }
 
-void
-__unregister_atfork (void *dso_handle)
+int
+__register_atfork (void (*prepare) (void), void (*parent) (void),
+		   void (*child) (void), void *dso_handle)
 {
-  lll_lock (atfork_lock, LLL_PRIVATE);
-
-  struct fork_handler *first = fork_handler_list_find (&fork_handlers,
-						       dso_handle);
-  /* Removing is done by shifting the elements in the way the elements
-     that are not to be removed appear in the beginning in dynarray.
-     This avoid the quadradic run-time if a naive strategy to remove and
-     shift one element at time.  */
-  if (first != NULL)
-    {
-      struct fork_handler *new_end = first;
-      first++;
-      for (; first != fork_handler_list_end (&fork_handlers); ++first)
-	{
-	  if (first->dso_handle != dso_handle)
-	    {
-	      *new_end = *first;
-	      ++new_end;
-	    }
-	}
+  struct fork_handler *entry = malloc (sizeof (struct fork_handler));
+  if (entry == NULL)
+    return ENOMEM;
 
-      ptrdiff_t removed = first - new_end;
-      for (size_t i = 0; i < removed; i++)
-	fork_handler_list_remove_last (&fork_handlers);
-    }
+  entry->prepare_handler = prepare;
+  entry->parent_handler = parent;
+  entry->child_handler = child;
+  entry->dso_handle = dso_handle;
+
+  __libc_lock_lock_recursive (atfork_lock);
+  fork_handlers_push_back (entry);
+  __libc_lock_unlock_recursive (atfork_lock);
 
-  lll_unlock (atfork_lock, LLL_PRIVATE);
+  return 0;
 }
+libc_hidden_def (__register_atfork)
 
 void
-__run_fork_handlers (enum __run_fork_handler_type who, _Bool do_locking)
+__unregister_atfork (void *dso_handle)
 {
-  struct fork_handler *runp;
+  __libc_lock_lock_recursive (atfork_lock);
+  fork_handlers_remove_if (dso_handle);
+  __libc_lock_unlock_recursive (atfork_lock);
+}
 
+void
+__run_fork_handlers (enum __run_fork_handler_type who, _Bool do_locking)
+{
   if (who == atfork_run_prepare)
     {
       if (do_locking)
-	lll_lock (atfork_lock, LLL_PRIVATE);
-      size_t sl = fork_handler_list_size (&fork_handlers);
-      for (size_t i = sl; i > 0; i--)
+	__libc_lock_lock_recursive (atfork_lock);
+
+      for (struct fork_handler *it = fork_handlers.last; it != NULL;
+	   it = it->prev)
 	{
-	  runp = fork_handler_list_at (&fork_handlers, i - 1);
-	  if (runp->prepare_handler != NULL)
-	    runp->prepare_handler ();
+	  if (it->prepare_handler != NULL)
+	    it->prepare_handler ();
 	}
     }
   else
     {
-      size_t sl = fork_handler_list_size (&fork_handlers);
-      for (size_t i = 0; i < sl; i++)
+      for (struct fork_handler *it = fork_handlers.first; it != NULL;
+	   it = it->next)
 	{
-	  runp = fork_handler_list_at (&fork_handlers, i);
-	  if (who == atfork_run_child && runp->child_handler)
-	    runp->child_handler ();
-	  else if (who == atfork_run_parent && runp->parent_handler)
-	    runp->parent_handler ();
-	}
+	  if (who == atfork_run_child && it->child_handler != NULL)
+	    it->child_handler ();
+	  else if (who == atfork_run_parent && it->parent_handler != NULL)
+	    it->parent_handler ();
+        }
       if (do_locking)
-	lll_unlock (atfork_lock, LLL_PRIVATE);
+	__libc_lock_unlock_recursive (atfork_lock);
     }
 }
 
 
 libc_freeres_fn (free_mem)
 {
-  lll_lock (atfork_lock, LLL_PRIVATE);
-
-  fork_handler_list_free (&fork_handlers);
-
-  lll_unlock (atfork_lock, LLL_PRIVATE);
+  __libc_lock_lock_recursive (atfork_lock);
+  fork_handlers_remove_all ();
+  __libc_lock_unlock_recursive (atfork_lock);
 }
diff --git a/nptl/tst-atfork3.c b/nptl/tst-atfork3.c
new file mode 100644
index 0000000000..1435ecc86a
--- /dev/null
+++ b/nptl/tst-atfork3.c
@@ -0,0 +1,118 @@
+/* Check if pthread_atfork handler can call dlclose (BZ#24595)
+   Copyright (C) 2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <dlfcn.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include <support/check.h>
+#include <support/xthread.h>
+#include <support/capture_subprocess.h>
+
+/* Check if pthread_atfork handlers do not deadlock when calling a function
+   that might alter the internal fork handle list, such as dlclose.
+
+   The test registers a callback set with pthread_atfork(), dlopen() a shared
+   library (nptl/tst-atfork3mod.c), calls an exported symbol from the library
+   (which in turn also registers atfork handlers), and calls fork to trigger
+   the callbacks.  */
+
+static void *handler;
+static bool run_dlclose_prepare;
+static bool run_dlclose_parent;
+static bool run_dlclose_child;
+
+static void
+prepare (void)
+{
+  if (run_dlclose_prepare)
+    TEST_COMPARE (dlclose (handler), 0);
+}
+
+static void
+parent (void)
+{
+  if (run_dlclose_parent)
+    TEST_COMPARE (dlclose (handler), 0);
+}
+
+static void
+child (void)
+{
+  if (run_dlclose_child)
+    TEST_COMPARE (dlclose (handler), 0);
+}
+
+static void
+proc_func (void *closure)
+{
+}
+
+static void
+do_test_generic (bool dlclose_prepare, bool dlclose_parent, bool dlclose_child)
+{
+  run_dlclose_prepare = dlclose_prepare;
+  run_dlclose_parent = dlclose_parent;
+  run_dlclose_child = dlclose_child;
+
+  handler = dlopen ("tst-atfork3mod.so", RTLD_NOW);
+  TEST_VERIFY (handler != NULL);
+
+  int (*atfork3mod_func)(void);
+  atfork3mod_func = dlsym (handler, "atfork3mod_func");
+  TEST_VERIFY (atfork3mod_func != NULL);
+
+  atfork3mod_func ();
+
+  struct support_capture_subprocess proc
+    = support_capture_subprocess (proc_func, NULL);
+  support_capture_subprocess_check (&proc, "tst-atfork3", 0, sc_allow_none);
+
+  handler = atfork3mod_func = NULL;
+}
+
+static void *
+thread_func (void *closure)
+{
+  return NULL;
+}
+
+static int
+do_test (void)
+{
+  {
+    /* Make the process acts as multithread.  */
+    pthread_attr_t attr;
+    xpthread_attr_init (&attr);
+    xpthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+    xpthread_create (&attr, thread_func, NULL);
+  }
+
+  TEST_COMPARE (pthread_atfork (prepare, parent, child), 0);
+
+  do_test_generic (true  /* prepare */, false /* parent */, false /* child */);
+  do_test_generic (false /* prepare */, true  /* parent */, false /* child */);
+  do_test_generic (false /* prepare */, false /* parent */, true  /* child */);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/nptl/tst-atfork3mod.c b/nptl/tst-atfork3mod.c
new file mode 100644
index 0000000000..7449962e57
--- /dev/null
+++ b/nptl/tst-atfork3mod.c
@@ -0,0 +1,45 @@
+/* Copyright (C) 2003-2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+   Contributed by Ulrich Drepper <drepper@redhat.com>, 2003.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <pthread.h>
+
+#include <support/check.h>
+
+static void
+mod_prepare (void)
+{
+}
+
+static void
+mod_parent (void)
+{
+}
+
+static void
+mod_child (void)
+{
+}
+
+int atfork3mod_func (void)
+{
+  TEST_COMPARE (pthread_atfork (mod_prepare, mod_parent, mod_child), 0);
+
+  return 0;
+}
diff --git a/sysdeps/nptl/fork.h b/sysdeps/nptl/fork.h
index 99ed76034b..c742252f8c 100644
--- a/sysdeps/nptl/fork.h
+++ b/sysdeps/nptl/fork.h
@@ -31,6 +31,8 @@ struct fork_handler
   void (*parent_handler) (void);
   void (*child_handler) (void);
   void *dso_handle;
+  struct fork_handler *next;
+  struct fork_handler *prev;
 };
 
 /* Function to call to unregister fork handlers.  */
