|
1 | 1 | import functools |
2 | 2 | import inspect |
3 | 3 | import os |
| 4 | +import select |
4 | 5 | import string |
5 | 6 | import sys |
6 | 7 | import tempfile |
@@ -1785,27 +1786,40 @@ def tearDown(self): |
1785 | 1786 | gc_collect() |
1786 | 1787 |
|
1787 | 1788 | @staticmethod |
1788 | | - def _drain_pty(master): |
1789 | | - # Read and discard whatever curses writes to the screen. |
1790 | | - try: |
1791 | | - while os.read(master, 1024): |
1792 | | - pass |
1793 | | - except OSError: |
1794 | | - pass |
| 1789 | + def _drain_pty(master, stop): |
| 1790 | + # Read and discard whatever curses writes to the screen, until asked to |
| 1791 | + # stop and nothing more is pending. poll() rather than a blocking |
| 1792 | + # read() so we can stop without closing the fd (closing it while this |
| 1793 | + # thread is blocked in read() hangs on macOS). |
| 1794 | + poller = select.poll() |
| 1795 | + poller.register(master, select.POLLIN) |
| 1796 | + while True: |
| 1797 | + if poller.poll(100): |
| 1798 | + try: |
| 1799 | + if not os.read(master, 1024): |
| 1800 | + break # EOF |
| 1801 | + except OSError: |
| 1802 | + break |
| 1803 | + elif stop.is_set(): |
| 1804 | + break |
1795 | 1805 |
|
1796 | 1806 | def make_pty(self): |
1797 | 1807 | master, slave = os.openpty() |
1798 | 1808 | # Nothing reads the master end, so writing to the slave and the |
1799 | 1809 | # tcdrain() in endwin() can block on macOS once the pty buffer fills; |
1800 | 1810 | # drain it from a background thread (endwin() releases the GIL). |
1801 | | - reader = threading.Thread(target=self._drain_pty, args=(master,), |
| 1811 | + stop = threading.Event() |
| 1812 | + reader = threading.Thread(target=self._drain_pty, args=(master, stop), |
1802 | 1813 | daemon=True) |
1803 | 1814 | reader.start() |
1804 | | - self.addCleanup(reader.join, SHORT_TIMEOUT) |
1805 | | - # Close the master first (cleanups run in reverse): on macOS, closing |
1806 | | - # the slave first blocks until its pending output drains. |
1807 | | - self.addCleanup(os.close, slave) |
| 1815 | + # Stop and join the reader before closing the fds: on macOS, closing |
| 1816 | + # either end while the reader is blocked in read() hangs. |
| 1817 | + def stop_reader(): |
| 1818 | + stop.set() |
| 1819 | + reader.join(SHORT_TIMEOUT) |
1808 | 1820 | self.addCleanup(os.close, master) |
| 1821 | + self.addCleanup(os.close, slave) |
| 1822 | + self.addCleanup(stop_reader) |
1809 | 1823 | return slave |
1810 | 1824 |
|
1811 | 1825 | def test_newterm(self): |
|
0 commit comments