Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
707ce82c
Commit
707ce82c
authored
Feb 25, 2011
by
Antoine Pitrou
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Issue #10956: Buffered I/O classes retry reading or writing after a signal
has arrived and the handler returned successfully.
parent
7e1b9af0
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
173 additions
and
7 deletions
+173
-7
Lib/_pyio.py
Lib/_pyio.py
+27
-4
Lib/test/test_io.py
Lib/test/test_io.py
+94
-1
Misc/NEWS
Misc/NEWS
+3
-0
Modules/_io/bufferedio.c
Modules/_io/bufferedio.c
+49
-2
No files found.
Lib/_pyio.py
View file @
707ce82c
...
...
@@ -14,6 +14,7 @@ except ImportError:
import
io
from
io
import
(
__all__
,
SEEK_SET
,
SEEK_CUR
,
SEEK_END
)
from
errno
import
EINTR
# open() uses st_blksize whenever we can
DEFAULT_BUFFER_SIZE
=
8
*
1024
# bytes
...
...
@@ -943,7 +944,12 @@ class BufferedReader(_BufferedIOMixin):
current_size
=
0
while
True
:
# Read until EOF or until read() would block.
try
:
chunk
=
self
.
raw
.
read
()
except
IOError
as
e
:
if
e
.
errno
!=
EINTR
:
raise
continue
if
chunk
in
empty_values
:
nodata_val
=
chunk
break
...
...
@@ -962,7 +968,12 @@ class BufferedReader(_BufferedIOMixin):
chunks
=
[
buf
[
pos
:]]
wanted
=
max
(
self
.
buffer_size
,
n
)
while
avail
<
n
:
try
:
chunk
=
self
.
raw
.
read
(
wanted
)
except
IOError
as
e
:
if
e
.
errno
!=
EINTR
:
raise
continue
if
chunk
in
empty_values
:
nodata_val
=
chunk
break
...
...
@@ -991,7 +1002,14 @@ class BufferedReader(_BufferedIOMixin):
have
=
len
(
self
.
_read_buf
)
-
self
.
_read_pos
if
have
<
want
or
have
<=
0
:
to_read
=
self
.
buffer_size
-
have
while
True
:
try
:
current
=
self
.
raw
.
read
(
to_read
)
except
IOError
as
e
:
if
e
.
errno
!=
EINTR
:
raise
continue
break
if
current
:
self
.
_read_buf
=
self
.
_read_buf
[
self
.
_read_pos
:]
+
current
self
.
_read_pos
=
0
...
...
@@ -1098,7 +1116,12 @@ class BufferedWriter(_BufferedIOMixin):
written
=
0
try
:
while
self
.
_write_buf
:
try
:
n
=
self
.
raw
.
write
(
self
.
_write_buf
)
except
IOError
as
e
:
if
e
.
errno
!=
EINTR
:
raise
continue
if
n
>
len
(
self
.
_write_buf
)
or
n
<
0
:
raise
IOError
(
"write() returned incorrect number of bytes"
)
del
self
.
_write_buf
[:
n
]
...
...
Lib/test/test_io.py
View file @
707ce82c
...
...
@@ -2623,7 +2623,8 @@ class SignalsTest(unittest.TestCase):
@
unittest
.
skipUnless
(
threading
,
'Threading required for this test.'
)
def
check_interrupted_write
(
self
,
item
,
bytes
,
**
fdopen_kwargs
):
"""Check that a partial write, when it gets interrupted, properly
invokes the signal handler."""
invokes the signal handler, and bubbles up the exception raised
in the latter."""
read_results
=
[]
def
_read
():
s
=
os
.
read
(
r
,
1
)
...
...
@@ -2703,6 +2704,98 @@ class SignalsTest(unittest.TestCase):
def
test_reentrant_write_text
(
self
):
self
.
check_reentrant_write
(
"xy"
,
mode
=
"w"
,
encoding
=
"ascii"
)
def
check_interrupted_read_retry
(
self
,
decode
,
**
fdopen_kwargs
):
"""Check that a buffered read, when it gets interrupted (either
returning a partial result or EINTR), properly invokes the signal
handler and retries if the latter returned successfully."""
r
,
w
=
os
.
pipe
()
fdopen_kwargs
[
"closefd"
]
=
False
def
alarm_handler
(
sig
,
frame
):
os
.
write
(
w
,
b"bar"
)
signal
.
signal
(
signal
.
SIGALRM
,
alarm_handler
)
try
:
rio
=
self
.
io
.
open
(
r
,
**
fdopen_kwargs
)
os
.
write
(
w
,
b"foo"
)
signal
.
alarm
(
1
)
# Expected behaviour:
# - first raw read() returns partial b"foo"
# - second raw read() returns EINTR
# - third raw read() returns b"bar"
self
.
assertEqual
(
decode
(
rio
.
read
(
6
)),
"foobar"
)
finally
:
rio
.
close
()
os
.
close
(
w
)
os
.
close
(
r
)
def
test_interrupterd_read_retry_buffered
(
self
):
self
.
check_interrupted_read_retry
(
lambda
x
:
x
.
decode
(
'latin1'
),
mode
=
"rb"
)
def
test_interrupterd_read_retry_text
(
self
):
self
.
check_interrupted_read_retry
(
lambda
x
:
x
,
mode
=
"r"
)
@
unittest
.
skipUnless
(
threading
,
'Threading required for this test.'
)
def
check_interrupted_write_retry
(
self
,
item
,
**
fdopen_kwargs
):
"""Check that a buffered write, when it gets interrupted (either
returning a partial result or EINTR), properly invokes the signal
handler and retries if the latter returned successfully."""
select
=
support
.
import_module
(
"select"
)
# A quantity that exceeds the buffer size of an anonymous pipe's
# write end.
N
=
1024
*
1024
r
,
w
=
os
.
pipe
()
fdopen_kwargs
[
"closefd"
]
=
False
# We need a separate thread to read from the pipe and allow the
# write() to finish. This thread is started after the SIGALRM is
# received (forcing a first EINTR in write()).
read_results
=
[]
write_finished
=
False
def
_read
():
while
not
write_finished
:
while
r
in
select
.
select
([
r
],
[],
[],
1.0
)[
0
]:
s
=
os
.
read
(
r
,
1024
)
read_results
.
append
(
s
)
t
=
threading
.
Thread
(
target
=
_read
)
t
.
daemon
=
True
def
alarm1
(
sig
,
frame
):
signal
.
signal
(
signal
.
SIGALRM
,
alarm2
)
signal
.
alarm
(
1
)
def
alarm2
(
sig
,
frame
):
t
.
start
()
signal
.
signal
(
signal
.
SIGALRM
,
alarm1
)
try
:
wio
=
self
.
io
.
open
(
w
,
**
fdopen_kwargs
)
signal
.
alarm
(
1
)
# Expected behaviour:
# - first raw write() is partial (because of the limited pipe buffer
# and the first alarm)
# - second raw write() returns EINTR (because of the second alarm)
# - subsequent write()s are successful (either partial or complete)
self
.
assertEqual
(
N
,
wio
.
write
(
item
*
N
))
wio
.
flush
()
write_finished
=
True
t
.
join
()
self
.
assertEqual
(
N
,
sum
(
len
(
x
)
for
x
in
read_results
))
finally
:
write_finished
=
True
os
.
close
(
w
)
os
.
close
(
r
)
# This is deliberate. If we didn't close the file descriptor
# before closing wio, wio would try to flush its internal
# buffer, and could block (in case of failure).
try
:
wio
.
close
()
except
IOError
as
e
:
if
e
.
errno
!=
errno
.
EBADF
:
raise
def
test_interrupterd_write_retry_buffered
(
self
):
self
.
check_interrupted_write_retry
(
b"x"
,
mode
=
"wb"
)
def
test_interrupterd_write_retry_text
(
self
):
self
.
check_interrupted_write_retry
(
"x"
,
mode
=
"w"
,
encoding
=
"latin1"
)
class
CSignalsTest
(
SignalsTest
):
io
=
io
...
...
Misc/NEWS
View file @
707ce82c
...
...
@@ -35,6 +35,9 @@ Core and Builtins
Library
-------
- Issue #10956: Buffered I/O classes retry reading or writing after a signal
has arrived and the handler returned successfully.
- Issue #10784: New os.getpriority() and os.setpriority() functions.
- Issue #11114: Fix catastrophic performance of tell() on text files (up
...
...
Modules/_io/bufferedio.c
View file @
707ce82c
...
...
@@ -714,6 +714,39 @@ _buffered_init(buffered *self)
return
0
;
}
/* Return 1 if an EnvironmentError with errno == EINTR is set (and then
clears the error indicator), 0 otherwise.
Should only be called when PyErr_Occurred() is true.
*/
static
int
_trap_eintr
(
void
)
{
static
PyObject
*
eintr_int
=
NULL
;
PyObject
*
typ
,
*
val
,
*
tb
;
PyEnvironmentErrorObject
*
env_err
;
if
(
eintr_int
==
NULL
)
{
eintr_int
=
PyLong_FromLong
(
EINTR
);
assert
(
eintr_int
!=
NULL
);
}
if
(
!
PyErr_ExceptionMatches
(
PyExc_EnvironmentError
))
return
0
;
PyErr_Fetch
(
&
typ
,
&
val
,
&
tb
);
PyErr_NormalizeException
(
&
typ
,
&
val
,
&
tb
);
env_err
=
(
PyEnvironmentErrorObject
*
)
val
;
assert
(
env_err
!=
NULL
);
if
(
env_err
->
myerrno
!=
NULL
&&
PyObject_RichCompareBool
(
env_err
->
myerrno
,
eintr_int
,
Py_EQ
)
>
0
)
{
Py_DECREF
(
typ
);
Py_DECREF
(
val
);
Py_XDECREF
(
tb
);
return
1
;
}
/* This silences any error set by PyObject_RichCompareBool() */
PyErr_Restore
(
typ
,
val
,
tb
);
return
0
;
}
/*
* Shared methods and wrappers
*/
...
...
@@ -1269,7 +1302,14 @@ _bufferedreader_raw_read(buffered *self, char *start, Py_ssize_t len)
memobj
=
PyMemoryView_FromBuffer
(
&
buf
);
if
(
memobj
==
NULL
)
return
-
1
;
/* NOTE: PyErr_SetFromErrno() calls PyErr_CheckSignals() when EINTR
occurs so we needn't do it ourselves.
We then retry reading, ignoring the signal if no handler has
raised (see issue #10956).
*/
do
{
res
=
PyObject_CallMethodObjArgs
(
self
->
raw
,
_PyIO_str_readinto
,
memobj
,
NULL
);
}
while
(
res
==
NULL
&&
_trap_eintr
());
Py_DECREF
(
memobj
);
if
(
res
==
NULL
)
return
-
1
;
...
...
@@ -1678,7 +1718,14 @@ _bufferedwriter_raw_write(buffered *self, char *start, Py_ssize_t len)
memobj
=
PyMemoryView_FromBuffer
(
&
buf
);
if
(
memobj
==
NULL
)
return
-
1
;
/* NOTE: PyErr_SetFromErrno() calls PyErr_CheckSignals() when EINTR
occurs so we needn't do it ourselves.
We then retry writing, ignoring the signal if no handler has
raised (see issue #10956).
*/
do
{
res
=
PyObject_CallMethodObjArgs
(
self
->
raw
,
_PyIO_str_write
,
memobj
,
NULL
);
}
while
(
res
==
NULL
&&
_trap_eintr
());
Py_DECREF
(
memobj
);
if
(
res
==
NULL
)
return
-
1
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment