8000 Adds test for multithreaded writes to files. · elistevens/cpython@dca6eb6 · GitHub
[go: up one dir, main page]

Skip to content

Commit dca6eb6

Browse files
committed
Adds test for multithreaded writes to files.
Multithreaded writes to files (including standard calls to print() from threads) can result in assertion failures and potentially missed output due to race conditions in _io_TextIOWrapper_write_impl and _textiowrapper_writeflush. See: python#118138
1 parent 100c7ab commit dca6eb6

File tree

2 files changed

+40
-0
lines changed

2 files changed

+40
-0
lines changed

Lib/test/test_io_threading.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import unittest
2+
from concurrent.futures import ThreadPoolExecutor
3+
4+
5+
NUM_THREADS = 100
6+
NUM_LOOPS = 10
7+
8+
def looping_print_to_file(f, i):
9+
for j in range(NUM_LOOPS):
10+
# p = 'x' * 90 + '123456789'
11+
p = f"{i:2}i,{j}j\n" + '0123456789' * 10
12+
print(p, file=f)
13+
14+
class IoThreadingTestCase(unittest.TestCase):
15+
def test_io_threading(self):
16+
with ThreadPoolExecutor(max_workers=NUM_THREADS) as executor:
17+
with open('test_io_threading.out', 'w') as f:
18+
for i in range(NUM_THREADS):
19+
executor.submit(looping_print_to_file, f, i)
20+
21+
executor.shutdown(wait=True)
22+
23+
with open('test_io_threading.out', 'r') as f:
24+
lines = set(x.rstrip() for x in f.readlines() if ',' in x)
25+
26+
assert len(lines) == NUM_THREADS * NUM_LOOPS, repr(len(lines))
27+
28+
for i in range(NUM_THREADS):
29+
for j in range(NUM_LOOPS):
30+
p = f"{i:2}i,{j}j"
31+
32+
assert p in lines, repr(p)
33+
34+
35+
if __name__ == "__main__":
36+
unittest.main()

Modules/_io/textio.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1729,6 +1729,10 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
17291729
Py_DECREF(b);
17301730
return NULL;
17311731
}
1732+
// The assumption that self won't have been modified from another
1733+
// thread between the flush above and the assignment below is
1734+
// incorrect.
1735+
assert(self->pending_bytes == NULL);
17321736
self->pending_bytes = b;
17331737
}
17341738
else if (!PyList_CheckExact(self->pending_bytes)) {

0 commit comments

Comments
 (0)
0