mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	[3.14] gh-134908: Protect textiowrapper_iternext with critical section (gh-134910) (gh-135039)
				
					
				
			The `textiowrapper_iternext` function called `_textiowrapper_writeflush`, but did not
use a critical section, making it racy in free-threaded builds.
(cherry picked from commit 44fb7c361c)
Co-authored-by: Duane Griffin <duaneg@dghda.com>
			
			
This commit is contained in:
		
							parent
							
								
									7ac461883d
								
							
						
					
					
						commit
						428b0ca114
					
				
					 3 changed files with 46 additions and 1 deletions
				
			
		| 
						 | 
				
			
			@ -1062,6 +1062,37 @@ def flush(self):
 | 
			
		|||
        # Silence destructor error
 | 
			
		||||
        R.flush = lambda self: None
 | 
			
		||||
 | 
			
		||||
    @threading_helper.requires_working_threading()
 | 
			
		||||
    def test_write_readline_races(self):
 | 
			
		||||
        # gh-134908: Concurrent iteration over a file caused races
 | 
			
		||||
        thread_count = 2
 | 
			
		||||
        write_count = 100
 | 
			
		||||
        read_count = 100
 | 
			
		||||
 | 
			
		||||
        def writer(file, barrier):
 | 
			
		||||
            barrier.wait()
 | 
			
		||||
            for _ in range(write_count):
 | 
			
		||||
                file.write("x")
 | 
			
		||||
 | 
			
		||||
        def reader(file, barrier):
 | 
			
		||||
            barrier.wait()
 | 
			
		||||
            for _ in range(read_count):
 | 
			
		||||
                for line in file:
 | 
			
		||||
                    self.assertEqual(line, "")
 | 
			
		||||
 | 
			
		||||
        with self.open(os_helper.TESTFN, "w+") as f:
 | 
			
		||||
            barrier = threading.Barrier(thread_count + 1)
 | 
			
		||||
            reader = threading.Thread(target=reader, args=(f, barrier))
 | 
			
		||||
            writers = [threading.Thread(target=writer, args=(f, barrier))
 | 
			
		||||
                       for _ in range(thread_count)]
 | 
			
		||||
            with threading_helper.catch_threading_exception() as cm:
 | 
			
		||||
                with threading_helper.start_threads(writers + [reader]):
 | 
			
		||||
                    pass
 | 
			
		||||
                self.assertIsNone(cm.exc_type)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(os.stat(os_helper.TESTFN).st_size,
 | 
			
		||||
                         write_count * thread_count)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CIOTest(IOTest):
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
Fix crash when iterating over lines in a text file on the :term:`free threaded <free threading>` build.
 | 
			
		||||
| 
						 | 
				
			
			@ -1578,6 +1578,8 @@ _io_TextIOWrapper_detach_impl(textio *self)
 | 
			
		|||
static int
 | 
			
		||||
_textiowrapper_writeflush(textio *self)
 | 
			
		||||
{
 | 
			
		||||
    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
 | 
			
		||||
 | 
			
		||||
    if (self->pending_bytes == NULL)
 | 
			
		||||
        return 0;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3173,8 +3175,9 @@ _io_TextIOWrapper_close_impl(textio *self)
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
static PyObject *
 | 
			
		||||
textiowrapper_iternext(PyObject *op)
 | 
			
		||||
textiowrapper_iternext_lock_held(PyObject *op)
 | 
			
		||||
{
 | 
			
		||||
    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
 | 
			
		||||
    PyObject *line;
 | 
			
		||||
    textio *self = textio_CAST(op);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3210,6 +3213,16 @@ textiowrapper_iternext(PyObject *op)
 | 
			
		|||
    return line;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static PyObject *
 | 
			
		||||
textiowrapper_iternext(PyObject *op)
 | 
			
		||||
{
 | 
			
		||||
    PyObject *result;
 | 
			
		||||
    Py_BEGIN_CRITICAL_SECTION(op);
 | 
			
		||||
    result = textiowrapper_iternext_lock_held(op);
 | 
			
		||||
    Py_END_CRITICAL_SECTION();
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*[clinic input]
 | 
			
		||||
@critical_section
 | 
			
		||||
@getter
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue