@@ -117,15 +117,38 @@ fileio_dealloc_warn(PyObject *op, PyObject *source)
117117 Py_RETURN_NONE ;
118118}
119119
120+ /* Atomic accessors for self->fd.
121+ * Relaxed ordering is sufficient: the OS syscall that follows provides the
122+ * real barrier, and we only need to prevent data-race UB on the int-sized field itself. */
123+ static inline int
124+ fileio_get_fd (fileio * self )
125+ {
126+ return _Py_atomic_load_int_relaxed (& self -> fd );
127+ }
128+
129+ static inline void
130+ fileio_set_fd (fileio * self , int fd )
131+ {
132+ _Py_atomic_store_int_relaxed (& self -> fd , fd );
133+ }
134+
135+ /* Atomically replace self->fd with -1 and return the old value.
136+ * Used by internal_close so that two concurrent calls to close cannot
137+ * both observe fd >= 0 and both attempt to close the same descriptor. */
138+ static inline int
139+ fileio_exchange_fd (fileio * self )
140+ {
141+ return _Py_atomic_exchange_int (& self -> fd , -1 );
142+ }
143+
120144/* Returns 0 on success, -1 with exception set on failure. */
121145static int
122146internal_close (fileio * self )
123147{
124148 int err = 0 ;
125149 int save_errno = 0 ;
126- if (self -> fd >= 0 ) {
127- int fd = self -> fd ;
128- self -> fd = -1 ;
150+ int fd = fileio_exchange_fd (self );
151+ if (fd >= 0 ) {
129152 /* fd is accessible and someone else may have closed it */
130153 Py_BEGIN_ALLOW_THREADS
131154 _Py_BEGIN_SUPPRESS_IPH
@@ -167,7 +190,7 @@ _io_FileIO_close_impl(fileio *self, PyTypeObject *cls)
167190 res = PyObject_CallMethodOneArg ((PyObject * )state -> PyRawIOBase_Type ,
168191 & _Py_ID (close ), (PyObject * )self );
169192 if (!self -> closefd ) {
170- self -> fd = -1 ;
193+ fileio_set_fd ( self , -1 ) ;
171194 return res ;
172195 }
173196
@@ -609,9 +632,10 @@ static PyObject *
609632_io_FileIO_fileno_impl (fileio * self )
610633/*[clinic end generated code: output=a9626ce5398ece90 input=0b9b2de67335ada3]*/
611634{
612- if (self -> fd < 0 )
635+ int fd = fileio_get_fd (self );
636+ if (fd < 0 )
613637 return err_closed ();
614- return PyLong_FromLong ((long ) self -> fd );
638+ return PyLong_FromLong ((long ) fd );
615639}
616640
617641/*[clinic input]
@@ -624,7 +648,7 @@ static PyObject *
624648_io_FileIO_readable_impl (fileio * self )
625649/*[clinic end generated code: output=640744a6150fe9ba input=a3fdfed6eea721c5]*/
626650{
627- if (self -> fd < 0 )
651+ if (fileio_get_fd ( self ) < 0 )
628652 return err_closed ();
629653 return PyBool_FromLong ((long ) self -> readable );
630654}
@@ -639,7 +663,7 @@ static PyObject *
639663_io_FileIO_writable_impl (fileio * self )
640664/*[clinic end generated code: output=96cefc5446e89977 input=c204a808ca2e1748]*/
641665{
642- if (self -> fd < 0 )
666+ if (fileio_get_fd ( self ) < 0 )
643667 return err_closed ();
644668 return PyBool_FromLong ((long ) self -> writable );
645669}
@@ -654,7 +678,7 @@ static PyObject *
654678_io_FileIO_seekable_impl (fileio * self )
655679/*[clinic end generated code: output=47909ca0a42e9287 input=c8e5554d2fd63c7f]*/
656680{
657- if (self -> fd < 0 )
681+ if (fileio_get_fd ( self ) < 0 )
658682 return err_closed ();
659683 if (self -> seekable < 0 ) {
660684 /* portable_lseek() sets the seekable attribute */
@@ -686,14 +710,16 @@ _io_FileIO_readinto_impl(fileio *self, PyTypeObject *cls, Py_buffer *buffer)
686710 Py_ssize_t n ;
687711 int err ;
688712
689- if (self -> fd < 0 )
713+ int fd ;
714+ fd = fileio_get_fd (self );
715+ if (fd < 0 )
690716 return err_closed ();
691717 if (!self -> readable ) {
692718 _PyIO_State * state = get_io_state_by_cls (cls );
693719 return err_mode (state , "reading" );
694720 }
695721
696- n = _Py_read (self -> fd , buffer -> buf , buffer -> len );
722+ n = _Py_read (fd , buffer -> buf , buffer -> len );
697723 /* copy errno because PyBuffer_Release() can indirectly modify it */
698724 err = errno ;
699725
@@ -754,7 +780,9 @@ _io_FileIO_readall_impl(fileio *self, PyTypeObject *cls)
754780 Py_ssize_t n ;
755781 size_t bufsize ;
756782
757- if (self -> fd < 0 ) {
783+ int fd ;
784+ fd = fileio_get_fd (self );
785+ if (fd < 0 ) {
758786 return err_closed ();
759787 }
760788 if (!self -> readable ) {
@@ -795,9 +823,9 @@ _io_FileIO_readall_impl(fileio *self, PyTypeObject *cls)
795823 Py_BEGIN_ALLOW_THREADS
796824 _Py_BEGIN_SUPPRESS_IPH
797825#ifdef MS_WINDOWS
798- pos = _lseeki64 (self -> fd , 0L , SEEK_CUR );
826+ pos = _lseeki64 (fd , 0L , SEEK_CUR );
799827#else
800- pos = lseek (self -> fd , 0L , SEEK_CUR );
828+ pos = lseek (fd , 0L , SEEK_CUR );
801829#endif
802830 _Py_END_SUPPRESS_IPH
803831 Py_END_ALLOW_THREADS
@@ -830,7 +858,7 @@ _io_FileIO_readall_impl(fileio *self, PyTypeObject *cls)
830858 }
831859 }
832860
833- n = _Py_read (self -> fd ,
861+ n = _Py_read (fd ,
834862 (char * )PyBytesWriter_GetData (writer ) + bytes_read ,
835863 bufsize - bytes_read );
836864
@@ -875,7 +903,9 @@ static PyObject *
875903_io_FileIO_read_impl (fileio * self , PyTypeObject * cls , Py_ssize_t size )
876904/*[clinic end generated code: output=bbd749c7c224143e input=c7baa3b440af9337]*/
877905{
878- if (self -> fd < 0 )
906+ int fd ;
907+ fd = fileio_get_fd (self );
908+ if (fd < 0 )
879909 return err_closed ();
880910 if (!self -> readable ) {
881911 _PyIO_State * state = get_io_state_by_cls (cls );
@@ -895,7 +925,7 @@ _io_FileIO_read_impl(fileio *self, PyTypeObject *cls, Py_ssize_t size)
895925 }
896926 char * ptr = PyBytesWriter_GetData (writer );
897927
898- Py_ssize_t n = _Py_read (self -> fd , ptr , size );
928+ Py_ssize_t n = _Py_read (fd , ptr , size );
899929 if (n == -1 ) {
900930 // copy errno because PyBytesWriter_Discard() can indirectly modify it
901931 int err = errno ;
@@ -930,14 +960,16 @@ _io_FileIO_write_impl(fileio *self, PyTypeObject *cls, Py_buffer *b)
930960 Py_ssize_t n ;
931961 int err ;
932962
933- if (self -> fd < 0 )
963+ int fd ;
964+ fd = fileio_get_fd (self );
965+ if (fd < 0 )
934966 return err_closed ();
935967 if (!self -> writable ) {
936968 _PyIO_State * state = get_io_state_by_cls (cls );
937969 return err_mode (state , "writing" );
938970 }
939971
940- n = _Py_write (self -> fd , b -> buf , b -> len );
972+ n = _Py_write (fd , b -> buf , b -> len );
941973 /* copy errno because PyBuffer_Release() can indirectly modify it */
942974 err = errno ;
943975
@@ -959,7 +991,8 @@ static PyObject *
959991portable_lseek (fileio * self , PyObject * posobj , int whence , bool suppress_pipe_error )
960992{
961993 Py_off_t pos , res ;
962- int fd = self -> fd ;
994+ int fd ;
995+ fd = fileio_get_fd (self );
963996
964997#ifdef SEEK_SET
965998 /* Turn 0, 1, 2 into SEEK_{SET,CUR,END} */
@@ -1040,7 +1073,7 @@ static PyObject *
10401073_io_FileIO_seek_impl (fileio * self , PyObject * pos , int whence )
10411074/*[clinic end generated code: output=c976acdf054e6655 input=f165a1b4f5d494ad]*/
10421075{
1043- if (self -> fd < 0 )
1076+ if (fileio_get_fd ( self ) < 0 )
10441077 return err_closed ();
10451078
10461079 return portable_lseek (self , pos , whence , false);
@@ -1058,7 +1091,7 @@ static PyObject *
10581091_io_FileIO_tell_impl (fileio * self )
10591092/*[clinic end generated code: output=ffe2147058809d0b input=807e24ead4cec2f9]*/
10601093{
1061- if (self -> fd < 0 )
1094+ if (fileio_get_fd ( self ) < 0 )
10621095 return err_closed ();
10631096
10641097 return portable_lseek (self , NULL , 1 , false);
@@ -1086,7 +1119,7 @@ _io_FileIO_truncate_impl(fileio *self, PyTypeObject *cls, PyObject *posobj)
10861119 int ret ;
10871120 int fd ;
10881121
1089- fd = self -> fd ;
1122+ fd = fileio_get_fd ( self ) ;
10901123 if (fd < 0 )
10911124 return err_closed ();
10921125 if (!self -> writable ) {
@@ -1181,7 +1214,8 @@ fileio_repr(PyObject *op)
11811214 fileio * self = PyFileIO_CAST (op );
11821215 const char * type_name = Py_TYPE (self )-> tp_name ;
11831216
1184- if (self -> fd < 0 ) {
1217+ int fd = fileio_get_fd (self );
1218+ if (fd < 0 ) {
11851219 return PyUnicode_FromFormat ("<%.100s [closed]>" , type_name );
11861220 }
11871221
@@ -1193,7 +1227,7 @@ fileio_repr(PyObject *op)
11931227 if (nameobj == NULL ) {
11941228 res = PyUnicode_FromFormat (
11951229 "<%.100s fd=%d mode='%s' closefd=%s>" ,
1196- type_name , self -> fd , mode_string (self ), self -> closefd ? "True" : "False" );
1230+ type_name , fd , mode_string (self ), self -> closefd ? "True" : "False" );
11971231 }
11981232 else {
11991233 int status = Py_ReprEnter ((PyObject * )self );
@@ -1225,11 +1259,13 @@ _io_FileIO_isatty_impl(fileio *self)
12251259{
12261260 long res ;
12271261
1228- if (self -> fd < 0 )
1262+ int fd ;
1263+ fd = fileio_get_fd (self );
1264+ if (fd < 0 )
12291265 return err_closed ();
12301266 Py_BEGIN_ALLOW_THREADS
12311267 _Py_BEGIN_SUPPRESS_IPH
1232- res = isatty (self -> fd );
1268+ res = isatty (fd );
12331269 _Py_END_SUPPRESS_IPH
12341270 Py_END_ALLOW_THREADS
12351271 return PyBool_FromLong (res );
@@ -1281,7 +1317,7 @@ static PyObject *
12811317fileio_get_closed (PyObject * op , void * closure )
12821318{
12831319 fileio * self = PyFileIO_CAST (op );
1284- return PyBool_FromLong ((long )(self -> fd < 0 ));
1320+ return PyBool_FromLong ((long )(fileio_get_fd ( self ) < 0 ));
12851321}
12861322
12871323static PyObject *
0 commit comments