Skip to content

Commit 47e8f07

Browse files
ivanivanov884jeffmahoney
authored andcommitted
gdb-python-gil.patch
;; Fix Python GIL with gdb.execute("continue") (Phil Muldoon, BZ 1116957). ;;=push
1 parent 079dab6 commit 47e8f07

File tree

6 files changed

+157
-5
lines changed

6 files changed

+157
-5
lines changed

gdb/doc/python.texi

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,14 @@ returned as a string. The default is @code{False}, in which case the
232232
return value is @code{None}. If @var{to_string} is @code{True}, the
233233
@value{GDBN} virtual terminal will be temporarily set to unlimited width
234234
and height, and its pagination will be disabled; @pxref{Screen Size}.
235+
236+
The @var{release_gil} flag specifies whether @value{GDBN} ought to
237+
release the Python GIL before executing the command. This is useful
238+
in multi-threaded Python programs where by default the Python
239+
interpreter will acquire the GIL and lock other threads from
240+
executing. After the command has completed executing in @value{GDBN}
241+
the Python GIL is reacquired. This flag must be a boolean value. If
242+
omitted, it defaults to @code{False}.
235243
@end defun
236244

237245
@findex gdb.breakpoints

gdb/python/python-internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ typedef int Py_ssize_t;
148148
#define PyGILState_Release(ARG) ((void)(ARG))
149149
#define PyEval_InitThreads()
150150
#define PyThreadState_Swap(ARG) ((void)(ARG))
151+
#define PyEval_SaveThread() ((void)(ARG))
152+
#define PyEval_RestoreThread(ARG) ((void)(ARG))
151153
#define PyEval_ReleaseLock()
152154
#endif
153155

gdb/python/python.c

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -556,12 +556,16 @@ execute_gdb_command (PyObject *self, PyObject *args, PyObject *kw)
556556
{
557557
const char *arg;
558558
PyObject *from_tty_obj = NULL, *to_string_obj = NULL;
559-
int from_tty, to_string;
560-
static const char *keywords[] = { "command", "from_tty", "to_string", NULL };
559+
int from_tty, to_string, release_gil;
560+
static const char *keywords[] = {"command", "from_tty", "to_string", "release_gil", NULL };
561+
PyObject *release_gil_obj = NULL;
562+
/* Initialize it just to avoid a GCC false warning. */
563+
PyThreadState *state = NULL;
561564

562-
if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|O!O!", keywords, &arg,
565+
if (!gdb_PyArg_ParseTupleAndKeywords (args, kw, "s|O!O!O!", keywords, &arg,
563566
&PyBool_Type, &from_tty_obj,
564-
&PyBool_Type, &to_string_obj))
567+
&PyBool_Type, &to_string_obj,
568+
&PyBool_Type, &release_gil_obj))
565569
return NULL;
566570

567571
from_tty = 0;
@@ -582,6 +586,15 @@ execute_gdb_command (PyObject *self, PyObject *args, PyObject *kw)
582586
to_string = cmp;
583587
}
584588

589+
release_gil = 0;
590+
if (release_gil_obj)
591+
{
592+
int cmp = PyObject_IsTrue (release_gil_obj);
593+
if (cmp < 0)
594+
return NULL;
595+
release_gil = cmp;
596+
}
597+
585598
std::string to_string_res;
586599

587600
TRY
@@ -602,6 +615,13 @@ execute_gdb_command (PyObject *self, PyObject *args, PyObject *kw)
602615

603616
counted_command_line lines = read_command_lines_1 (reader, 1, nullptr);
604617

618+
/* In the case of long running GDB commands, allow the user to
619+
release the Python GIL acquired by Python. Restore the GIL
620+
after the command has completed before handing back to
621+
Python. */
622+
if (release_gil)
623+
state = PyEval_SaveThread();
624+
605625
scoped_restore save_async = make_scoped_restore (&current_ui->async, 0);
606626

607627
scoped_restore save_uiout = make_scoped_restore (&current_uiout);
@@ -617,10 +637,22 @@ execute_gdb_command (PyObject *self, PyObject *args, PyObject *kw)
617637
from_tty);
618638
else
619639
execute_control_commands (lines.get (), from_tty);
640+
641+
/* Reacquire the GIL if it was released earlier. */
642+
if (release_gil)
643+
PyEval_RestoreThread (state);
620644
}
621645
CATCH (except, RETURN_MASK_ALL)
622646
{
623-
GDB_PY_HANDLE_EXCEPTION (except);
647+
if (except.reason < 0)
648+
{
649+
/* Reacquire the GIL if it was released earlier. */
650+
if (release_gil)
651+
PyEval_RestoreThread (state);
652+
653+
gdbpy_convert_exception (except);
654+
return NULL;
655+
}
624656
}
625657
END_CATCH
626658

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include <stdio.h>
2+
#include <unistd.h>
3+
4+
int
5+
main (void)
6+
{
7+
int i;
8+
for (i = 0; i < 10; i++)
9+
{
10+
sleep (1); /* break-here */
11+
printf ("Sleeping %d\n", i);
12+
}
13+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Copyright (C) 2014 Free Software Foundation, Inc.
2+
3+
# This program is free software; you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation; either version 3 of the License, or
6+
# (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
standard_testfile .c .py
17+
set executable $testfile
18+
19+
if { [prepare_for_testing $testfile.exp $executable $srcfile] } {
20+
return -1
21+
}
22+
23+
# Skip all tests if Python scripting is not enabled.
24+
if { [skip_python_tests] } { continue }
25+
26+
if ![runto_main] {
27+
return -1
28+
}
29+
30+
gdb_breakpoint $srcfile:[gdb_get_line_number "break-here"] temporary
31+
gdb_continue_to_breakpoint "break-here" ".* break-here .*"
32+
33+
set test "response"
34+
set timeout 60
35+
set sleeping_last -1
36+
set hello_last 0
37+
set minimal 5
38+
gdb_test_multiple "python exec (open ('$srcdir/$subdir/$srcfile2').read ())" $test {
39+
-re "Error: unable to start thread\r\n" {
40+
fail $test
41+
# Not $gdb_prompt-synced!
42+
}
43+
-re "Sleeping (\[0-9\]+)\r\n" {
44+
set n $expect_out(1,string)
45+
if { $sleeping_last + 1 != $n } {
46+
fail $test
47+
} else {
48+
set sleeping_last $n
49+
if { $sleeping_last >= $minimal && $hello_last >= $minimal } {
50+
pass $test
51+
} else {
52+
exp_continue
53+
}
54+
}
55+
}
56+
-re "Hello \\( (\[0-9\]+) \\)\r\n" {
57+
set n $expect_out(1,string)
58+
if { $hello_last + 1 != $n } {
59+
fail $test
60+
} else {
61+
set hello_last $n
62+
if { $sleeping_last >= $minimal && $hello_last >= $minimal } {
63+
pass $test
64+
} else {
65+
exp_continue
66+
}
67+
}
68+
}
69+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
try:
2+
import thread
3+
except:
4+
import _thread
5+
import time
6+
import gdb
7+
8+
# Define a function for the thread
9+
def print_thread_hello():
10+
count = 0
11+
while count < 10:
12+
time.sleep(1)
13+
count += 1
14+
print ("Hello ( %d )" % count)
15+
16+
# Create a threads a continue
17+
try:
18+
thread.start_new_thread (print_thread_hello, ())
19+
gdb.execute ("continue", release_gil=True)
20+
except:
21+
try:
22+
_thread.start_new_thread (print_thread_hello, ())
23+
gdb.execute ("continue", release_gil=True)
24+
except:
25+
print ("Error: unable to start thread")
26+
27+
while 1:
28+
pass

0 commit comments

Comments
 (0)