[v2,3/3,gdb/dap] Fix race between dap exit and gdb exit
Checks
Context |
Check |
Description |
linaro-tcwg-bot/tcwg_gdb_build--master-arm |
success
|
Testing passed
|
linaro-tcwg-bot/tcwg_gdb_check--master-arm |
success
|
Testing passed
|
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 |
success
|
Testing passed
|
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 |
success
|
Testing passed
|
Commit Message
When DAP shuts down due to an EOF event, there's a race between:
- gdb's main thread handling a SIGHUP, and
- the DAP main thread exiting.
Fix this by waiting for DAP's main thread exit during the gdb_exiting event.
Tested on aarch64-linux.
PR dap/31380
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31380
---
gdb/python/lib/gdb/dap/startup.py | 20 +++++++++++++++++++-
gdb/testsuite/gdb.dap/eof.exp | 9 +++++++++
2 files changed, 28 insertions(+), 1 deletion(-)
Comments
>>>>> "Tom" == Tom de Vries <tdevries@suse.de> writes:
Tom> When DAP shuts down due to an EOF event, there's a race between:
Tom> - gdb's main thread handling a SIGHUP, and
Tom> - the DAP main thread exiting.
Tom> Fix this by waiting for DAP's main thread exit during the gdb_exiting event.
Tom> Tested on aarch64-linux.
Thanks, this is ok.
Approved-By: Tom Tromey <tom@tromey.com>
Tom> - start_thread("DAP", really_start_dap)
Tom> + # Note that we set _dap_thread in both the DAP and the GDB main thread.
Tom> + global _dap_thread
Tom> + _dap_thread = start_thread("DAP", really_start_dap)
Tom> +@in_gdb_thread
Tom> +def _on_gdb_exiting(event):
Tom> + if _dap_thread is None:
Tom> + # This can happen if the DAP module is imported, but the server not
Tom> + # started.
Tom> + thread_log("no dap thread to join")
Tom> + return
Tom> +
Tom> + thread_log("joining dap thread ...")
Tom> + _dap_thread.join()
Tom> + thread_log("joining dap thread done")
Tom> +
Tom> +
Tom> +gdb.events.gdb_exiting.connect(_on_gdb_exiting)
Originally I'd just pictured something like:
local = start_thread("DAP", ...)
gdb.events.gdb_exiting.connect(local.join)
... but wanting logging makes sense I suppose.
The global could still be removed if you care by a nested function.
Tom
On 2/21/24 22:10, Tom Tromey wrote:
>>>>>> "Tom" == Tom de Vries <tdevries@suse.de> writes:
>
> Tom> When DAP shuts down due to an EOF event, there's a race between:
> Tom> - gdb's main thread handling a SIGHUP, and
> Tom> - the DAP main thread exiting.
>
> Tom> Fix this by waiting for DAP's main thread exit during the gdb_exiting event.
>
> Tom> Tested on aarch64-linux.
>
> Thanks, this is ok.
> Approved-By: Tom Tromey <tom@tromey.com>
>
Thanks for the review.
> Tom> - start_thread("DAP", really_start_dap)
> Tom> + # Note that we set _dap_thread in both the DAP and the GDB main thread.
> Tom> + global _dap_thread
> Tom> + _dap_thread = start_thread("DAP", really_start_dap)
>
> Tom> +@in_gdb_thread
> Tom> +def _on_gdb_exiting(event):
> Tom> + if _dap_thread is None:
> Tom> + # This can happen if the DAP module is imported, but the server not
> Tom> + # started.
> Tom> + thread_log("no dap thread to join")
> Tom> + return
> Tom> +
> Tom> + thread_log("joining dap thread ...")
> Tom> + _dap_thread.join()
> Tom> + thread_log("joining dap thread done")
> Tom> +
> Tom> +
> Tom> +gdb.events.gdb_exiting.connect(_on_gdb_exiting)
>
> Originally I'd just pictured something like:
>
> local = start_thread("DAP", ...)
> gdb.events.gdb_exiting.connect(local.join)
>
Aha, I see.
> ... but wanting logging makes sense I suppose.
> The global could still be removed if you care by a nested function.
Done.
I've also make changed the log messages to use DAP upper-case, to be in
sync with other uses in the log file.
Committed as attached.
Thanks,
- Tom
@@ -93,7 +93,9 @@ def start_dap(target):
_dap_thread = threading.current_thread()
target()
- start_thread("DAP", really_start_dap)
+ # Note that we set _dap_thread in both the DAP and the GDB main thread.
+ global _dap_thread
+ _dap_thread = start_thread("DAP", really_start_dap)
def in_gdb_thread(func):
@@ -208,6 +210,22 @@ def exec_and_log(cmd):
log_stack()
+@in_gdb_thread
+def _on_gdb_exiting(event):
+ if _dap_thread is None:
+ # This can happen if the DAP module is imported, but the server not
+ # started.
+ thread_log("no dap thread to join")
+ return
+
+ thread_log("joining dap thread ...")
+ _dap_thread.join()
+ thread_log("joining dap thread done")
+
+
+gdb.events.gdb_exiting.connect(_on_gdb_exiting)
+
+
class Invoker(object):
"""A simple class that can invoke a gdb command."""
@@ -38,3 +38,12 @@ dap_check_log_file
# Check that first log message is present.
dap_check_log_file_re [string_to_regexp "starting DAP server"]
+
+# There should be one "READ:" for the initialize request, and at least one
+# "WROTE:" for the initialize response.
+dap_check_log_file_re "READ:"
+dap_check_log_file_re "WROTE:"
+
+# Check that all thread termination messages are there.
+dap_check_log_file_re "JSON writer: terminating"
+dap_check_log_file_re "DAP: terminating"