Skip to content

Commit 2b3a1d9

Browse files
committed
prc: StreamReader should hold GIL for PyBytes_FromStringAndSize
This is due to python/cpython#21074, which causes a segfault in the latest Python master when creating a bytes object of size 0. readlines() has been reimplemented to use a C++ vector in order to prevent constantly re-locking and unlocking the GIL for every line.
1 parent 2402594 commit 2b3a1d9

File tree

3 files changed

+67
-16
lines changed

3 files changed

+67
-16
lines changed

dtool/src/prc/streamReader.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ class EXPCL_DTOOL_PRC StreamReader {
6666

6767
BLOCKING void skip_bytes(size_t size);
6868
BLOCKING size_t extract_bytes(unsigned char *into, size_t size);
69-
EXTENSION(BLOCKING PyObject *extract_bytes(size_t size));
69+
EXTENSION(PyObject *extract_bytes(size_t size));
7070

71-
EXTENSION(BLOCKING PyObject *readline());
72-
EXTENSION(BLOCKING PyObject *readlines());
71+
EXTENSION(PyObject *readline());
72+
EXTENSION(PyObject *readlines());
7373

7474
public:
7575
BLOCKING vector_uchar extract_bytes(size_t size);

dtool/src/prc/streamReader_ext.cxx

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
#ifdef HAVE_PYTHON
1717

18+
#include "vector_string.h"
19+
1820
/**
1921
* Extracts the indicated number of bytes in the stream and returns them as a
2022
* string (or bytes, in Python 3). Returns empty string at end-of-file.
@@ -23,13 +25,25 @@ PyObject *Extension<StreamReader>::
2325
extract_bytes(size_t size) {
2426
std::istream *in = _this->get_istream();
2527
if (in->eof() || in->fail() || size == 0) {
28+
// Note that this is only safe to call with size 0 while the GIL is held.
2629
return PyBytes_FromStringAndSize(nullptr, 0);
2730
}
2831

2932
PyObject *bytes = PyBytes_FromStringAndSize(nullptr, size);
30-
in->read(PyBytes_AS_STRING(bytes), size);
33+
char *buffer = (char *)PyBytes_AS_STRING(bytes);
34+
35+
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
36+
PyThreadState *_save;
37+
Py_UNBLOCK_THREADS
38+
#endif // HAVE_THREADS && !SIMPLE_THREADS
39+
40+
in->read(buffer, size);
3141
size_t read_bytes = in->gcount();
3242

43+
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
44+
Py_BLOCK_THREADS
45+
#endif // HAVE_THREADS && !SIMPLE_THREADS
46+
3347
if (read_bytes == size || _PyBytes_Resize(&bytes, read_bytes) == 0) {
3448
return bytes;
3549
} else {
@@ -47,6 +61,11 @@ extract_bytes(size_t size) {
4761
*/
4862
PyObject *Extension<StreamReader>::
4963
readline() {
64+
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
65+
PyThreadState *_save;
66+
Py_UNBLOCK_THREADS
67+
#endif // HAVE_THREADS && !SIMPLE_THREADS
68+
5069
std::istream *in = _this->get_istream();
5170

5271
std::string line;
@@ -60,6 +79,10 @@ readline() {
6079
ch = in->get();
6180
}
6281

82+
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
83+
Py_BLOCK_THREADS
84+
#endif // HAVE_THREADS && !SIMPLE_THREADS
85+
6386
#if PY_MAJOR_VERSION >= 3
6487
return PyBytes_FromStringAndSize(line.data(), line.size());
6588
#else
@@ -73,22 +96,50 @@ readline() {
7396
*/
7497
PyObject *Extension<StreamReader>::
7598
readlines() {
76-
PyObject *lst = PyList_New(0);
99+
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
100+
PyThreadState *_save;
101+
Py_UNBLOCK_THREADS
102+
#endif // HAVE_THREADS && !SIMPLE_THREADS
103+
104+
std::istream *in = _this->get_istream();
105+
vector_string lines;
106+
107+
while (true) {
108+
std::string line;
109+
int ch = in->get();
110+
while (ch != EOF && !in->fail()) {
111+
line += ch;
112+
if (ch == '\n' || in->eof()) {
113+
// Here's the newline character.
114+
break;
115+
}
116+
ch = in->get();
117+
}
118+
119+
if (line.empty()) {
120+
break;
121+
}
122+
123+
lines.push_back(std::move(line));
124+
}
125+
126+
#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
127+
Py_BLOCK_THREADS
128+
#endif // HAVE_THREADS && !SIMPLE_THREADS
129+
130+
PyObject *lst = PyList_New(lines.size());
77131
if (lst == nullptr) {
78132
return nullptr;
79133
}
80134

81-
PyObject *py_line = readline();
82-
135+
Py_ssize_t i = 0;
136+
for (const std::string &line : lines) {
83137
#if PY_MAJOR_VERSION >= 3
84-
while (PyBytes_GET_SIZE(py_line) > 0) {
138+
PyObject *py_line = PyBytes_FromStringAndSize(line.data(), line.size());
85139
#else
86-
while (PyString_GET_SIZE(py_line) > 0) {
140+
PyObject *py_line = PyString_FromStringAndSize(line.data(), line.size());
87141
#endif
88-
PyList_Append(lst, py_line);
89-
Py_DECREF(py_line);
90-
91-
py_line = readline();
142+
PyList_SET_ITEM(lst, i++, py_line);
92143
}
93144

94145
return lst;

dtool/src/prc/streamReader_ext.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@
2929
template<>
3030
class Extension<StreamReader> : public ExtensionBase<StreamReader> {
3131
public:
32-
BLOCKING PyObject *extract_bytes(size_t size);
33-
BLOCKING PyObject *readline();
34-
BLOCKING PyObject *readlines();
32+
PyObject *extract_bytes(size_t size);
33+
PyObject *readline();
34+
PyObject *readlines();
3535
};
3636

3737
#endif // HAVE_PYTHON

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy