update thread list, delete exited threads
authorPedro Alves <palves@redhat.com>
Tue, 7 Apr 2015 14:47:22 +0000 (15:47 +0100)
committerPedro Alves <palves@redhat.com>
Tue, 7 Apr 2015 14:47:22 +0000 (15:47 +0100)
On GNU/Linux, if the running kernel supports clone events, then
linux-thread-db.c defers thread listing to the target beneath:

static void
thread_db_update_thread_list (struct target_ops *ops)
{
...
  if (target_has_execution && !thread_db_use_events ())
    ops->beneath->to_update_thread_list (ops->beneath);
  else
    thread_db_update_thread_list_td_ta_thr_iter (ops);
...
}

However, when live debugging, the target beneath, linux-nat.c, does
not implement the to_update_thread_list method.  The result is that if
a thread is marked exited (because it can't be deleted right now,
e.g., it was the selected thread), then it won't ever be deleted,
until the process exits or is killed/detached.

A similar thing happens with the remote.c target.  Because its
target_update_thread_list implementation skips exited threads when it
walks the current thread list looking for threads that no longer exits
on the target side, using ALL_NON_EXITED_THREADS_SAFE, stale exited
threads are never deleted.

This is not a big deal -- I can't think of any way this might be user
visible, other than gdb's memory growing a tiny bit whenever a thread
gets stuck in exited state.  Still, might as well clean things up
properly.

All other targets use prune_threads, so are unaffected.

The fix adds a ALL_THREADS_SAFE macro, that like
ALL_NON_EXITED_THREADS_SAFE, walks the thread list and allows deleting
the iterated thread, and uses that in places that are walking the
thread list in order to delete threads.  Actually, after converting
linux-nat.c and remote.c to use this, we find the only other user of
ALL_NON_EXITED_THREADS_SAFE is also walking the list to delete
threads.  So we convert that too, and end up deleting
ALL_NON_EXITED_THREADS_SAFE.

Tested on x86_64 Fedora 20, native and gdbserver.

gdb/ChangeLog
2015-04-07  Pedro Alves  <palves@redhat.com>

* gdbthread.h (ALL_NON_EXITED_THREADS_SAFE): Rename to ...
(ALL_THREADS_SAFE): ... this, and don't skip exited threads.
(delete_exited_threads): New declaration.
* infrun.c (follow_exec): Use ALL_THREADS_SAFE.
* linux-nat.c (linux_nat_update_thread_list): New function.
(linux_nat_add_target): Install it.
* remote.c (remote_update_thread_list): Use ALL_THREADS_SAFE.
* thread.c (prune_threads): Use ALL_THREADS_SAFE.
(delete_exited_threads): New function.

gdb/ChangeLog
gdb/gdbthread.h
gdb/infrun.c
gdb/linux-nat.c
gdb/remote.c
gdb/thread.c

index ef9dd65a7b83931f0567610fde3ed6de5e3fc401..b0c65a225adfde0b4f15e8095727774435ba49ba 100644 (file)
@@ -1,3 +1,15 @@
+2015-04-07  Pedro Alves  <palves@redhat.com>
+
+       * gdbthread.h (ALL_NON_EXITED_THREADS_SAFE): Rename to ...
+       (ALL_THREADS_SAFE): ... this, and don't skip exited threads.
+       (delete_exited_threads): New declaration.
+       * infrun.c (follow_exec): Use ALL_THREADS_SAFE.
+       * linux-nat.c (linux_nat_update_thread_list): New function.
+       (linux_nat_add_target): Install it.
+       * remote.c (remote_update_thread_list): Use ALL_THREADS_SAFE.
+       * thread.c (prune_threads): Use ALL_THREADS_SAFE.
+       (delete_exited_threads): New function.
+
 2015-04-07  Pedro Alves  <pedro@codesourcery.com>
 
        * infrun.c (resume) <displaced stepping debug output>: Get the
index bb15717b4146fd8a2201b08c7798fe5786491468..ff7cec2d0eaf333d0bd26fae0d351f7560e39eb4 100644 (file)
@@ -380,13 +380,12 @@ extern struct thread_info *iterate_over_threads (thread_callback_func, void *);
   for (T = thread_list; T; T = T->next) \
     if ((T)->state != THREAD_EXITED)
 
-/* Like ALL_NON_EXITED_THREADS, but allows deleting the currently
-   iterated thread.  */
-#define ALL_NON_EXITED_THREADS_SAFE(T, TMP)    \
+/* Traverse all threads, including those that have THREAD_EXITED
+   state.  Allows deleting the currently iterated thread.  */
+#define ALL_THREADS_SAFE(T, TMP)       \
   for ((T) = thread_list;                      \
        (T) != NULL ? ((TMP) = (T)->next, 1): 0;        \
-       (T) = (TMP))                            \
-    if ((T)->state != THREAD_EXITED)
+       (T) = (TMP))
 
 extern int thread_count (void);
 
@@ -484,6 +483,11 @@ extern void update_thread_list (void);
 
 extern void prune_threads (void);
 
+/* Delete threads marked THREAD_EXITED.  Unlike prune_threads, this
+   does not consult the target about whether the thread is alive right
+   now.  */
+extern void delete_exited_threads (void);
+
 /* Return true if PC is in the stepping range of THREAD.  */
 
 int pc_in_thread_step_range (CORE_ADDR pc, struct thread_info *thread);
index 607a6e4695cd6b16e76edd7dfebedef5f35f612b..d52b8ada21c3d5e1c75ff586de1df5817d888c16 100644 (file)
@@ -1099,7 +1099,7 @@ follow_exec (ptid_t ptid, char *execd_pathname)
      them.  Deleting them now rather than at the next user-visible
      stop provides a nicer sequence of events for user and MI
      notifications.  */
-  ALL_NON_EXITED_THREADS_SAFE (th, tmp)
+  ALL_THREADS_SAFE (th, tmp)
     if (ptid_get_pid (th->ptid) == pid && !ptid_equal (th->ptid, ptid))
       delete_thread (th->ptid);
 
index 04707dc68a350c35615248d3845a102b23b31f03..6c198cfe6386ffece519e16a3a78d8060e1e2cd0 100644 (file)
@@ -4030,6 +4030,23 @@ linux_nat_thread_alive (struct target_ops *ops, ptid_t ptid)
   return linux_thread_alive (ptid);
 }
 
+/* Implement the to_update_thread_list target method for this
+   target.  */
+
+static void
+linux_nat_update_thread_list (struct target_ops *ops)
+{
+  if (linux_supports_traceclone ())
+    {
+      /* With support for clone events, we add/delete threads from the
+        list as clone/exit events are processed, so just try deleting
+        exited threads still in the thread list.  */
+      delete_exited_threads ();
+    }
+  else
+    prune_threads ();
+}
+
 static char *
 linux_nat_pid_to_str (struct target_ops *ops, ptid_t ptid)
 {
@@ -4854,6 +4871,7 @@ linux_nat_add_target (struct target_ops *t)
   t->to_kill = linux_nat_kill;
   t->to_mourn_inferior = linux_nat_mourn_inferior;
   t->to_thread_alive = linux_nat_thread_alive;
+  t->to_update_thread_list = linux_nat_update_thread_list;
   t->to_pid_to_str = linux_nat_pid_to_str;
   t->to_thread_name = linux_nat_thread_name;
   t->to_has_thread_control = tc_schedlock;
index 69a67a85063f66e1949616166b8e30afa4a9a980..dcd24c43cca1a8491771d946cb1a8e369b467514 100644 (file)
@@ -2835,7 +2835,7 @@ remote_update_thread_list (struct target_ops *ops)
       /* CONTEXT now holds the current thread list on the remote
         target end.  Delete GDB-side threads no longer found on the
         target.  */
-      ALL_NON_EXITED_THREADS_SAFE (tp, tmp)
+      ALL_THREADS_SAFE (tp, tmp)
         {
          for (i = 0;
               VEC_iterate (thread_item_t, context.items, i, item);
index ec398f578d2f97e05d0147139af179e21a6765b1..80c87050df9b176429cba7b0262c2a10611b6df6 100644 (file)
@@ -625,16 +625,29 @@ thread_alive (struct thread_info *tp)
 void
 prune_threads (void)
 {
-  struct thread_info *tp, *next;
+  struct thread_info *tp, *tmp;
 
-  for (tp = thread_list; tp; tp = next)
+  ALL_THREADS_SAFE (tp, tmp)
     {
-      next = tp->next;
       if (!thread_alive (tp))
        delete_thread (tp->ptid);
     }
 }
 
+/* See gdbthreads.h.  */
+
+void
+delete_exited_threads (void)
+{
+  struct thread_info *tp, *tmp;
+
+  ALL_THREADS_SAFE (tp, tmp)
+    {
+      if (tp->state == THREAD_EXITED)
+       delete_thread (tp->ptid);
+    }
+}
+
 /* Disable storing stack temporaries for the thread whose id is
    stored in DATA.  */
 
This page took 0.039086 seconds and 4 git commands to generate.