|
14 | 14 | from test import support |
15 | 15 | from test.support import import_helper |
16 | 16 | from test.support import os_helper |
| 17 | +from test.support import threading_helper |
17 | 18 | from test.support import warnings_helper |
18 | 19 | from test.support import force_not_colorized |
19 | 20 | from test.support.script_helper import assert_python_ok, assert_python_failure |
@@ -1561,6 +1562,105 @@ def test_release_lock_no_lock(self): |
1561 | 1562 | ): |
1562 | 1563 | c_warnings._release_lock() |
1563 | 1564 |
|
| 1565 | + @support.cpython_only |
| 1566 | + @unittest.skipUnless(support.has_fork_support, 'requires working os.fork') |
| 1567 | + @threading_helper.requires_working_threading() |
| 1568 | + def test_fork_other_thread_holds_lock(self): |
| 1569 | + code = textwrap.dedent(''' |
| 1570 | + import _warnings |
| 1571 | + import os |
| 1572 | + import threading |
| 1573 | + import warnings |
| 1574 | + from test import support |
| 1575 | +
|
| 1576 | + parent_pid = os.getpid() |
| 1577 | + read_fd, write_fd = os.pipe() |
| 1578 | + local = threading.local() |
| 1579 | + ready = threading.Event() |
| 1580 | + release = threading.Event() |
| 1581 | + warnings.simplefilter('ignore', DeprecationWarning) |
| 1582 | + warnings.simplefilter('ignore', ResourceWarning) |
| 1583 | +
|
| 1584 | + class WarnOnDelete: |
| 1585 | + def __del__(self): |
| 1586 | + if os.getpid() != parent_pid: |
| 1587 | + # Even an ignored warning must acquire the lock. |
| 1588 | + warnings.warn('child cleanup', ResourceWarning) |
| 1589 | + os.write(write_fd, b'finalized') |
| 1590 | +
|
| 1591 | + def worker(): |
| 1592 | + local.obj = WarnOnDelete() |
| 1593 | + _warnings._acquire_lock() |
| 1594 | + _warnings._acquire_lock() |
| 1595 | + try: |
| 1596 | + ready.set() |
| 1597 | + release.wait() |
| 1598 | + finally: |
| 1599 | + _warnings._release_lock() |
| 1600 | + _warnings._release_lock() |
| 1601 | +
|
| 1602 | + thread = threading.Thread(target=worker) |
| 1603 | + thread.start() |
| 1604 | + # Release the worker before fork's parent-side warning attempts |
| 1605 | + # to acquire the warnings lock. |
| 1606 | + os.register_at_fork(after_in_parent=release.set) |
| 1607 | + try: |
| 1608 | + assert ready.wait(support.LONG_TIMEOUT) |
| 1609 | + pid = os.fork() |
| 1610 | + if pid == 0: |
| 1611 | + # The finalizer must run before os.fork() returns. |
| 1612 | + os._exit(0) |
| 1613 | + support.wait_process(pid, exitcode=0) |
| 1614 | + finally: |
| 1615 | + release.set() |
| 1616 | + thread.join() |
| 1617 | + os.close(write_fd) |
| 1618 | + assert os.read(read_fd, 100) == b'finalized' |
| 1619 | + os.close(read_fd) |
| 1620 | + ''') |
| 1621 | + assert_python_ok('-c', code) |
| 1622 | + |
| 1623 | + @support.cpython_only |
| 1624 | + @unittest.skipUnless(support.has_fork_support, 'requires working os.fork') |
| 1625 | + def test_fork_current_thread_holds_lock(self): |
| 1626 | + code = textwrap.dedent(''' |
| 1627 | + import _warnings |
| 1628 | + import os |
| 1629 | + import warnings |
| 1630 | + from test import support |
| 1631 | +
|
| 1632 | + warnings.simplefilter('ignore') |
| 1633 | + for _ in range(3): |
| 1634 | + _warnings._acquire_lock() |
| 1635 | + pid = os.fork() |
| 1636 | + if pid == 0: |
| 1637 | + # Ownership must also survive a second fork in the child. |
| 1638 | + child_pid = os.fork() |
| 1639 | + if child_pid != 0: |
| 1640 | + support.wait_process(child_pid, exitcode=0) |
| 1641 | + try: |
| 1642 | + # A warning must not change the inherited recursion depth. |
| 1643 | + warnings.warn('after fork') |
| 1644 | + for _ in range(3): |
| 1645 | + _warnings._release_lock() |
| 1646 | + try: |
| 1647 | + _warnings._release_lock() |
| 1648 | + except RuntimeError: |
| 1649 | + pass |
| 1650 | + else: |
| 1651 | + raise AssertionError('unexpected recursion depth') |
| 1652 | + _warnings._acquire_lock() |
| 1653 | + _warnings._release_lock() |
| 1654 | + except BaseException: |
| 1655 | + if pid == 0: |
| 1656 | + os._exit(1) |
| 1657 | + raise |
| 1658 | + if pid == 0: |
| 1659 | + os._exit(0) |
| 1660 | + support.wait_process(pid, exitcode=0) |
| 1661 | + ''') |
| 1662 | + assert_python_ok('-c', code) |
| 1663 | + |
1564 | 1664 |
|
1565 | 1665 | class _DeprecatedTest(BaseTest, unittest.TestCase): |
1566 | 1666 |
|
|
0 commit comments