Skip to content

Commit 69c6b43

Browse files
authored
gh-138013: Split SignalsTest from test_io.test_general (#139079)
Increase parallelism by splitting out `SignalsTest` from test_general. `SignalsTest` takes 24.2 seconds on my dev machine when fully enabled making it the largest part of `test_io`. Code move done via copy/paste then tweak imports. After splitting `test_io.test_general` is down to 10.1 seconds on my dev box with all parts enabled.
1 parent 67636f7 commit 69c6b43

File tree

2 files changed

+280
-271
lines changed

2 files changed

+280
-271
lines changed

Lib/test/test_io/test_general.py

Lines changed: 0 additions & 271 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import os
1111
import pickle
1212
import random
13-
import signal
1413
import sys
1514
import textwrap
1615
import threading
@@ -39,9 +38,6 @@ def _default_chunk_size():
3938
with open(__file__, "r", encoding="latin-1") as f:
4039
return f._CHUNK_SIZE
4140

42-
requires_alarm = unittest.skipUnless(
43-
hasattr(signal, "alarm"), "test requires signal.alarm()"
44-
)
4541

4642

4743
class BadIndex:
@@ -4468,273 +4464,6 @@ class PyMiscIOTest(MiscIOTest, PyTestCase):
44684464
not_exported = "valid_seek_flags",
44694465

44704466

4471-
@unittest.skipIf(os.name == 'nt', 'POSIX signals required for this test.')
4472-
class SignalsTest:
4473-
4474-
def setUp(self):
4475-
self.oldalrm = signal.signal(signal.SIGALRM, self.alarm_interrupt)
4476-
4477-
def tearDown(self):
4478-
signal.signal(signal.SIGALRM, self.oldalrm)
4479-
4480-
def alarm_interrupt(self, sig, frame):
4481-
1/0
4482-
4483-
def check_interrupted_write(self, item, bytes, **fdopen_kwargs):
4484-
"""Check that a partial write, when it gets interrupted, properly
4485-
invokes the signal handler, and bubbles up the exception raised
4486-
in the latter."""
4487-
4488-
# XXX This test has three flaws that appear when objects are
4489-
# XXX not reference counted.
4490-
4491-
# - if wio.write() happens to trigger a garbage collection,
4492-
# the signal exception may be raised when some __del__
4493-
# method is running; it will not reach the assertRaises()
4494-
# call.
4495-
4496-
# - more subtle, if the wio object is not destroyed at once
4497-
# and survives this function, the next opened file is likely
4498-
# to have the same fileno (since the file descriptor was
4499-
# actively closed). When wio.__del__ is finally called, it
4500-
# will close the other's test file... To trigger this with
4501-
# CPython, try adding "global wio" in this function.
4502-
4503-
# - This happens only for streams created by the _pyio module,
4504-
# because a wio.close() that fails still consider that the
4505-
# file needs to be closed again. You can try adding an
4506-
# "assert wio.closed" at the end of the function.
4507-
4508-
# Fortunately, a little gc.collect() seems to be enough to
4509-
# work around all these issues.
4510-
support.gc_collect() # For PyPy or other GCs.
4511-
4512-
read_results = []
4513-
def _read():
4514-
s = os.read(r, 1)
4515-
read_results.append(s)
4516-
4517-
t = threading.Thread(target=_read)
4518-
t.daemon = True
4519-
r, w = os.pipe()
4520-
fdopen_kwargs["closefd"] = False
4521-
large_data = item * (support.PIPE_MAX_SIZE // len(item) + 1)
4522-
try:
4523-
wio = self.io.open(w, **fdopen_kwargs)
4524-
if hasattr(signal, 'pthread_sigmask'):
4525-
# create the thread with SIGALRM signal blocked
4526-
signal.pthread_sigmask(signal.SIG_BLOCK, [signal.SIGALRM])
4527-
t.start()
4528-
signal.pthread_sigmask(signal.SIG_UNBLOCK, [signal.SIGALRM])
4529-
else:
4530-
t.start()
4531-
4532-
# Fill the pipe enough that the write will be blocking.
4533-
# It will be interrupted by the timer armed above. Since the
4534-
# other thread has read one byte, the low-level write will
4535-
# return with a successful (partial) result rather than an EINTR.
4536-
# The buffered IO layer must check for pending signal
4537-
# handlers, which in this case will invoke alarm_interrupt().
4538-
signal.alarm(1)
4539-
try:
4540-
self.assertRaises(ZeroDivisionError, wio.write, large_data)
4541-
finally:
4542-
signal.alarm(0)
4543-
t.join()
4544-
# We got one byte, get another one and check that it isn't a
4545-
# repeat of the first one.
4546-
read_results.append(os.read(r, 1))
4547-
self.assertEqual(read_results, [bytes[0:1], bytes[1:2]])
4548-
finally:
4549-
os.close(w)
4550-
os.close(r)
4551-
# This is deliberate. If we didn't close the file descriptor
4552-
# before closing wio, wio would try to flush its internal
4553-
# buffer, and block again.
4554-
try:
4555-
wio.close()
4556-
except OSError as e:
4557-
if e.errno != errno.EBADF:
4558-
raise
4559-
4560-
@requires_alarm
4561-
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
4562-
def test_interrupted_write_unbuffered(self):
4563-
self.check_interrupted_write(b"xy", b"xy", mode="wb", buffering=0)
4564-
4565-
@requires_alarm
4566-
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
4567-
def test_interrupted_write_buffered(self):
4568-
self.check_interrupted_write(b"xy", b"xy", mode="wb")
4569-
4570-
@requires_alarm
4571-
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
4572-
def test_interrupted_write_text(self):
4573-
self.check_interrupted_write("xy", b"xy", mode="w", encoding="ascii")
4574-
4575-
@support.no_tracing
4576-
def check_reentrant_write(self, data, **fdopen_kwargs):
4577-
def on_alarm(*args):
4578-
# Will be called reentrantly from the same thread
4579-
wio.write(data)
4580-
1/0
4581-
signal.signal(signal.SIGALRM, on_alarm)
4582-
r, w = os.pipe()
4583-
wio = self.io.open(w, **fdopen_kwargs)
4584-
try:
4585-
signal.alarm(1)
4586-
# Either the reentrant call to wio.write() fails with RuntimeError,
4587-
# or the signal handler raises ZeroDivisionError.
4588-
with self.assertRaises((ZeroDivisionError, RuntimeError)) as cm:
4589-
while 1:
4590-
for i in range(100):
4591-
wio.write(data)
4592-
wio.flush()
4593-
# Make sure the buffer doesn't fill up and block further writes
4594-
os.read(r, len(data) * 100)
4595-
exc = cm.exception
4596-
if isinstance(exc, RuntimeError):
4597-
self.assertStartsWith(str(exc), "reentrant call")
4598-
finally:
4599-
signal.alarm(0)
4600-
wio.close()
4601-
os.close(r)
4602-
4603-
@requires_alarm
4604-
def test_reentrant_write_buffered(self):
4605-
self.check_reentrant_write(b"xy", mode="wb")
4606-
4607-
@requires_alarm
4608-
def test_reentrant_write_text(self):
4609-
self.check_reentrant_write("xy", mode="w", encoding="ascii")
4610-
4611-
def check_interrupted_read_retry(self, decode, **fdopen_kwargs):
4612-
"""Check that a buffered read, when it gets interrupted (either
4613-
returning a partial result or EINTR), properly invokes the signal
4614-
handler and retries if the latter returned successfully."""
4615-
r, w = os.pipe()
4616-
fdopen_kwargs["closefd"] = False
4617-
def alarm_handler(sig, frame):
4618-
os.write(w, b"bar")
4619-
signal.signal(signal.SIGALRM, alarm_handler)
4620-
try:
4621-
rio = self.io.open(r, **fdopen_kwargs)
4622-
os.write(w, b"foo")
4623-
signal.alarm(1)
4624-
# Expected behaviour:
4625-
# - first raw read() returns partial b"foo"
4626-
# - second raw read() returns EINTR
4627-
# - third raw read() returns b"bar"
4628-
self.assertEqual(decode(rio.read(6)), "foobar")
4629-
finally:
4630-
signal.alarm(0)
4631-
rio.close()
4632-
os.close(w)
4633-
os.close(r)
4634-
4635-
@requires_alarm
4636-
@support.requires_resource('walltime')
4637-
def test_interrupted_read_retry_buffered(self):
4638-
self.check_interrupted_read_retry(lambda x: x.decode('latin1'),
4639-
mode="rb")
4640-
4641-
@requires_alarm
4642-
@support.requires_resource('walltime')
4643-
def test_interrupted_read_retry_text(self):
4644-
self.check_interrupted_read_retry(lambda x: x,
4645-
mode="r", encoding="latin1")
4646-
4647-
def check_interrupted_write_retry(self, item, **fdopen_kwargs):
4648-
"""Check that a buffered write, when it gets interrupted (either
4649-
returning a partial result or EINTR), properly invokes the signal
4650-
handler and retries if the latter returned successfully."""
4651-
select = import_helper.import_module("select")
4652-
4653-
# A quantity that exceeds the buffer size of an anonymous pipe's
4654-
# write end.
4655-
N = support.PIPE_MAX_SIZE
4656-
r, w = os.pipe()
4657-
fdopen_kwargs["closefd"] = False
4658-
4659-
# We need a separate thread to read from the pipe and allow the
4660-
# write() to finish. This thread is started after the SIGALRM is
4661-
# received (forcing a first EINTR in write()).
4662-
read_results = []
4663-
write_finished = False
4664-
error = None
4665-
def _read():
4666-
try:
4667-
while not write_finished:
4668-
while r in select.select([r], [], [], 1.0)[0]:
4669-
s = os.read(r, 1024)
4670-
read_results.append(s)
4671-
except BaseException as exc:
4672-
nonlocal error
4673-
error = exc
4674-
t = threading.Thread(target=_read)
4675-
t.daemon = True
4676-
def alarm1(sig, frame):
4677-
signal.signal(signal.SIGALRM, alarm2)
4678-
signal.alarm(1)
4679-
def alarm2(sig, frame):
4680-
t.start()
4681-
4682-
large_data = item * N
4683-
signal.signal(signal.SIGALRM, alarm1)
4684-
try:
4685-
wio = self.io.open(w, **fdopen_kwargs)
4686-
signal.alarm(1)
4687-
# Expected behaviour:
4688-
# - first raw write() is partial (because of the limited pipe buffer
4689-
# and the first alarm)
4690-
# - second raw write() returns EINTR (because of the second alarm)
4691-
# - subsequent write()s are successful (either partial or complete)
4692-
written = wio.write(large_data)
4693-
self.assertEqual(N, written)
4694-
4695-
wio.flush()
4696-
write_finished = True
4697-
t.join()
4698-
4699-
self.assertIsNone(error)
4700-
self.assertEqual(N, sum(len(x) for x in read_results))
4701-
finally:
4702-
signal.alarm(0)
4703-
write_finished = True
4704-
os.close(w)
4705-
os.close(r)
4706-
# This is deliberate. If we didn't close the file descriptor
4707-
# before closing wio, wio would try to flush its internal
4708-
# buffer, and could block (in case of failure).
4709-
try:
4710-
wio.close()
4711-
except OSError as e:
4712-
if e.errno != errno.EBADF:
4713-
raise
4714-
4715-
@requires_alarm
4716-
@support.requires_resource('walltime')
4717-
def test_interrupted_write_retry_buffered(self):
4718-
self.check_interrupted_write_retry(b"x", mode="wb")
4719-
4720-
@requires_alarm
4721-
@support.requires_resource('walltime')
4722-
def test_interrupted_write_retry_text(self):
4723-
self.check_interrupted_write_retry("x", mode="w", encoding="latin1")
4724-
4725-
4726-
class CSignalsTest(SignalsTest, CTestCase):
4727-
pass
4728-
4729-
class PySignalsTest(SignalsTest, PyTestCase):
4730-
pass
4731-
4732-
# Handling reentrancy issues would slow down _pyio even more, so the
4733-
# tests are disabled.
4734-
test_reentrant_write_buffered = None
4735-
test_reentrant_write_text = None
4736-
4737-
47384467
class ProtocolsTest(unittest.TestCase):
47394468
class MyReader:
47404469
def read(self, sz=-1):

0 commit comments

Comments
 (0)