Skip to content

Commit bbb0f2d

Browse files
authored
[3.14] gh-144777: Fix data races in IncrementalNewlineDecoder (gh-144971) (#145143)
1 parent 8e29215 commit bbb0f2d

File tree

4 files changed

+84
-7
lines changed

4 files changed

+84
-7
lines changed

Lib/test/test_free_threading/test_io.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
import codecs
2+
import io
13
import threading
24
from unittest import TestCase
35
from test.support import threading_helper
6+
from test.support.threading_helper import run_concurrently
47
from random import randint
58
from io import BytesIO
69
from sys import getsizeof
710

11+
threading_helper.requires_working_threading(module=True)
12+
813

914
class TestBytesIO(TestCase):
1015
# Test pretty much everything that can break under free-threading.
@@ -107,3 +112,54 @@ def sizeof(barrier, b, *ignore):
107112
self.check([truncate] + [sizeof] * 10, BytesIO(b'0\n'*204800))
108113

109114
# no tests for seek or tell because they don't break anything
115+
116+
117+
class IncrementalNewlineDecoderTest(TestCase):
118+
def make_decoder(self):
119+
utf8_decoder = codecs.getincrementaldecoder('utf-8')()
120+
return io.IncrementalNewlineDecoder(utf8_decoder, translate=True)
121+
122+
def test_concurrent_reset(self):
123+
decoder = self.make_decoder()
124+
125+
def worker():
126+
for _ in range(100):
127+
decoder.reset()
128+
129+
run_concurrently(worker_func=worker, nthreads=2)
130+
131+
def test_concurrent_decode(self):
132+
decoder = self.make_decoder()
133+
134+
def worker():
135+
for _ in range(100):
136+
decoder.decode(b"line\r\n", final=False)
137+
138+
run_concurrently(worker_func=worker, nthreads=2)
139+
140+
def test_concurrent_getstate_setstate(self):
141+
decoder = self.make_decoder()
142+
state = decoder.getstate()
143+
144+
def getstate_worker():
145+
for _ in range(100):
146+
decoder.getstate()
147+
148+
def setstate_worker():
149+
for _ in range(100):
150+
decoder.setstate(state)
151+
152+
run_concurrently([getstate_worker] * 2 + [setstate_worker] * 2)
153+
154+
def test_concurrent_decode_and_reset(self):
155+
decoder = self.make_decoder()
156+
157+
def decode_worker():
158+
for _ in range(100):
159+
decoder.decode(b"line\r\n", final=False)
160+
161+
def reset_worker():
162+
for _ in range(100):
163+
decoder.reset()
164+
165+
run_concurrently([decode_worker] * 2 + [reset_worker] * 2)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix data races in :class:`io.IncrementalNewlineDecoder` in the :term:`free-threaded build`.

Modules/_io/clinic/textio.c.h

Lines changed: 19 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/_io/textio.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,7 @@ _PyIncrementalNewlineDecoder_decode(PyObject *myself,
518518
}
519519

520520
/*[clinic input]
521+
@critical_section
521522
_io.IncrementalNewlineDecoder.decode
522523
input: object
523524
final: bool = False
@@ -526,18 +527,19 @@ _io.IncrementalNewlineDecoder.decode
526527
static PyObject *
527528
_io_IncrementalNewlineDecoder_decode_impl(nldecoder_object *self,
528529
PyObject *input, int final)
529-
/*[clinic end generated code: output=0d486755bb37a66e input=90e223c70322c5cd]*/
530+
/*[clinic end generated code: output=0d486755bb37a66e input=9475d16a73168504]*/
530531
{
531532
return _PyIncrementalNewlineDecoder_decode((PyObject *) self, input, final);
532533
}
533534

534535
/*[clinic input]
536+
@critical_section
535537
_io.IncrementalNewlineDecoder.getstate
536538
[clinic start generated code]*/
537539

538540
static PyObject *
539541
_io_IncrementalNewlineDecoder_getstate_impl(nldecoder_object *self)
540-
/*[clinic end generated code: output=f0d2c9c136f4e0d0 input=f8ff101825e32e7f]*/
542+
/*[clinic end generated code: output=f0d2c9c136f4e0d0 input=dc3e1f27aa850f12]*/
541543
{
542544
PyObject *buffer;
543545
unsigned long long flag;
@@ -575,6 +577,7 @@ _io_IncrementalNewlineDecoder_getstate_impl(nldecoder_object *self)
575577
}
576578

577579
/*[clinic input]
580+
@critical_section
578581
_io.IncrementalNewlineDecoder.setstate
579582
state: object
580583
/
@@ -583,7 +586,7 @@ _io.IncrementalNewlineDecoder.setstate
583586
static PyObject *
584587
_io_IncrementalNewlineDecoder_setstate_impl(nldecoder_object *self,
585588
PyObject *state)
586-
/*[clinic end generated code: output=09135cb6e78a1dc8 input=c53fb505a76dbbe2]*/
589+
/*[clinic end generated code: output=09135cb6e78a1dc8 input=275fd3982d2b08cb]*/
587590
{
588591
PyObject *buffer;
589592
unsigned long long flag;
@@ -613,12 +616,13 @@ _io_IncrementalNewlineDecoder_setstate_impl(nldecoder_object *self,
613616
}
614617

615618
/*[clinic input]
619+
@critical_section
616620
_io.IncrementalNewlineDecoder.reset
617621
[clinic start generated code]*/
618622

619623
static PyObject *
620624
_io_IncrementalNewlineDecoder_reset_impl(nldecoder_object *self)
621-
/*[clinic end generated code: output=32fa40c7462aa8ff input=728678ddaea776df]*/
625+
/*[clinic end generated code: output=32fa40c7462aa8ff input=31bd8ae4e36cec83]*/
622626
{
623627
CHECK_INITIALIZED_DECODER(self);
624628

0 commit comments

Comments
 (0)