@@ -2623,7 +2623,8 @@ def alarm_interrupt(self, sig, frame):
26232623 @unittest .skipUnless (threading , 'Threading required for this test.' )
26242624 def check_interrupted_write (self , item , bytes , ** fdopen_kwargs ):
26252625 """Check that a partial write, when it gets interrupted, properly
2626- invokes the signal handler."""
2626+ invokes the signal handler, and bubbles up the exception raised
2627+ in the latter."""
26272628 read_results = []
26282629 def _read ():
26292630 s = os .read (r , 1 )
@@ -2703,6 +2704,98 @@ def test_reentrant_write_buffered(self):
27032704 def test_reentrant_write_text (self ):
27042705 self .check_reentrant_write ("xy" , mode = "w" , encoding = "ascii" )
27052706
2707+ def check_interrupted_read_retry (self , decode , ** fdopen_kwargs ):
2708+ """Check that a buffered read, when it gets interrupted (either
2709+ returning a partial result or EINTR), properly invokes the signal
2710+ handler and retries if the latter returned successfully."""
2711+ r , w = os .pipe ()
2712+ fdopen_kwargs ["closefd" ] = False
2713+ def alarm_handler (sig , frame ):
2714+ os .write (w , b"bar" )
2715+ signal .signal (signal .SIGALRM , alarm_handler )
2716+ try :
2717+ rio = self .io .open (r , ** fdopen_kwargs )
2718+ os .write (w , b"foo" )
2719+ signal .alarm (1 )
2720+ # Expected behaviour:
2721+ # - first raw read() returns partial b"foo"
2722+ # - second raw read() returns EINTR
2723+ # - third raw read() returns b"bar"
2724+ self .assertEqual (decode (rio .read (6 )), "foobar" )
2725+ finally :
2726+ rio .close ()
2727+ os .close (w )
2728+ os .close (r )
2729+
2730+ def test_interrupterd_read_retry_buffered (self ):
2731+ self .check_interrupted_read_retry (lambda x : x .decode ('latin1' ),
2732+ mode = "rb" )
2733+
2734+ def test_interrupterd_read_retry_text (self ):
2735+ self .check_interrupted_read_retry (lambda x : x ,
2736+ mode = "r" )
2737+
2738+ @unittest .skipUnless (threading , 'Threading required for this test.' )
2739+ def check_interrupted_write_retry (self , item , ** fdopen_kwargs ):
2740+ """Check that a buffered write, when it gets interrupted (either
2741+ returning a partial result or EINTR), properly invokes the signal
2742+ handler and retries if the latter returned successfully."""
2743+ select = support .import_module ("select" )
2744+ # A quantity that exceeds the buffer size of an anonymous pipe's
2745+ # write end.
2746+ N = 1024 * 1024
2747+ r , w = os .pipe ()
2748+ fdopen_kwargs ["closefd" ] = False
2749+ # We need a separate thread to read from the pipe and allow the
2750+ # write() to finish. This thread is started after the SIGALRM is
2751+ # received (forcing a first EINTR in write()).
2752+ read_results = []
2753+ write_finished = False
2754+ def _read ():
2755+ while not write_finished :
2756+ while r in select .select ([r ], [], [], 1.0 )[0 ]:
2757+ s = os .read (r , 1024 )
2758+ read_results .append (s )
2759+ t = threading .Thread (target = _read )
2760+ t .daemon = True
2761+ def alarm1 (sig , frame ):
2762+ signal .signal (signal .SIGALRM , alarm2 )
2763+ signal .alarm (1 )
2764+ def alarm2 (sig , frame ):
2765+ t .start ()
2766+ signal .signal (signal .SIGALRM , alarm1 )
2767+ try :
2768+ wio = self .io .open (w , ** fdopen_kwargs )
2769+ signal .alarm (1 )
2770+ # Expected behaviour:
2771+ # - first raw write() is partial (because of the limited pipe buffer
2772+ # and the first alarm)
2773+ # - second raw write() returns EINTR (because of the second alarm)
2774+ # - subsequent write()s are successful (either partial or complete)
2775+ self .assertEqual (N , wio .write (item * N ))
2776+ wio .flush ()
2777+ write_finished = True
2778+ t .join ()
2779+ self .assertEqual (N , sum (len (x ) for x in read_results ))
2780+ finally :
2781+ write_finished = True
2782+ os .close (w )
2783+ os .close (r )
2784+ # This is deliberate. If we didn't close the file descriptor
2785+ # before closing wio, wio would try to flush its internal
2786+ # buffer, and could block (in case of failure).
2787+ try :
2788+ wio .close ()
2789+ except IOError as e :
2790+ if e .errno != errno .EBADF :
2791+ raise
2792+
2793+ def test_interrupterd_write_retry_buffered (self ):
2794+ self .check_interrupted_write_retry (b"x" , mode = "wb" )
2795+
2796+ def test_interrupterd_write_retry_text (self ):
2797+ self .check_interrupted_write_retry ("x" , mode = "w" , encoding = "latin1" )
2798+
27062799
27072800class CSignalsTest (SignalsTest ):
27082801 io = io
0 commit comments