gdb: fix "set scheduler-locking" thread exit hang
authorSimon Marchi <simon.marchi@efficios.com>
Mon, 8 Nov 2021 21:49:21 +0000 (16:49 -0500)
committerSimon Marchi <simon.marchi@polymtl.ca>
Thu, 11 Nov 2021 18:28:10 +0000 (13:28 -0500)
commit4d772ea24dac5d69d913bd0fb748b3070dc8bba6
tree2c9c1de2cd360adee95011386ec9bb3e434e0273
parentfdf95218bc08c60ef4528eb67b03643fdd16f036
gdb: fix "set scheduler-locking" thread exit hang

GDB hangs when doing this:

 - launch inferior with multiple threads
 - multiple threads hit some breakpoint(s)
 - one breakpoint hit is presented as a stop, the rest are saved as
   pending wait statuses
 - "set scheduler-locking on"
 - resume the currently selected thread (because of scheduler-locking,
   it's the only one resumed), let it execute until exit
 - GDB hangs, not showing the prompt, impossible to interrupt with ^C

When the resumed thread exits, we expect the target to return a
TARGET_WAITKIND_NO_RESUMED event, and that's what we see:

    [infrun] fetch_inferior_event: enter
      [infrun] scoped_disable_commit_resumed: reason=handling event
      [infrun] random_pending_event_thread: None found.
    [Thread 0x7ffff7d9c700 (LWP 309357) exited]
      [infrun] print_target_wait_results: target_wait (-1.0.0 [process -1], status) =
      [infrun] print_target_wait_results:   -1.0.0 [process -1],
      [infrun] print_target_wait_results:   status->kind = no-resumed
      [infrun] handle_inferior_event: status->kind = no-resumed
      [infrun] handle_no_resumed: TARGET_WAITKIND_NO_RESUMED (ignoring: found resumed)
      [infrun] prepare_to_wait: prepare_to_wait
      [infrun] reset: reason=handling event
      [infrun] maybe_set_commit_resumed_all_targets: not requesting commit-resumed for target native, no resumed threads
    [infrun] fetch_inferior_event: exit

The problem is in handle_no_resumed: we check if some other thread is
actually resumed, to see if we should ignore that event (see comments in
that function for more info).  If this condition is true:

    (thread->executing () || thread->has_pending_waitstatus ())

... then we ignore the event.  The problem is that there are some non-resumed
threads with a pending event, which makes us ignore the event.  But these
threads are not resumed, so we end up waiting while nothing executes, hence
waiting for ever.

My first fix was to change the condition to:

    (thread->executing ()
     || (thread->resumed () && thread->has_pending_waitstatus ()))

... but then it occured to me that we could simply check for:

    (thread->resumed ())

Since "executing" implies "resumed", checking simply for "resumed"
covers threads that are resumed and executing, as well as threads that
are resumed with a pending status, which is what we want.

Change-Id: Ie796290f8ae7f34c026ca3a8fcef7397414f4780
gdb/infrun.c
gdb/testsuite/gdb.threads/schedlock-thread-exit.c [new file with mode: 0644]
gdb/testsuite/gdb.threads/schedlock-thread-exit.exp [new file with mode: 0644]