Ensure child logging locks are not held; bpo-6721 & bpo-36533.
(self)
| 751 | @skip_if_asan_fork |
| 752 | @skip_if_tsan_fork |
| 753 | def test_post_fork_child_no_deadlock(self): |
| 754 | """Ensure child logging locks are not held; bpo-6721 & bpo-36533.""" |
| 755 | class _OurHandler(logging.Handler): |
| 756 | def __init__(self): |
| 757 | super().__init__() |
| 758 | self.sub_handler = logging.StreamHandler( |
| 759 | stream=open('/dev/null', 'wt', encoding='utf-8')) |
| 760 | |
| 761 | def emit(self, record): |
| 762 | with self.sub_handler.lock: |
| 763 | self.sub_handler.emit(record) |
| 764 | |
| 765 | self.assertEqual(len(logging._handlers), 0) |
| 766 | refed_h = _OurHandler() |
| 767 | self.addCleanup(refed_h.sub_handler.stream.close) |
| 768 | refed_h.name = 'because we need at least one for this test' |
| 769 | self.assertGreater(len(logging._handlers), 0) |
| 770 | self.assertGreater(len(logging._at_fork_reinit_lock_weakset), 1) |
| 771 | test_logger = logging.getLogger('test_post_fork_child_no_deadlock') |
| 772 | test_logger.addHandler(refed_h) |
| 773 | test_logger.setLevel(logging.DEBUG) |
| 774 | |
| 775 | locks_held__ready_to_fork = threading.Event() |
| 776 | fork_happened__release_locks_and_end_thread = threading.Event() |
| 777 | |
| 778 | def lock_holder_thread_fn(): |
| 779 | with logging._lock, refed_h.lock: |
| 780 | # Tell the main thread to do the fork. |
| 781 | locks_held__ready_to_fork.set() |
| 782 | |
| 783 | # If the deadlock bug exists, the fork will happen |
| 784 | # without dealing with the locks we hold, deadlocking |
| 785 | # the child. |
| 786 | |
| 787 | # Wait for a successful fork or an unreasonable amount of |
| 788 | # time before releasing our locks. To avoid a timing based |
| 789 | # test we'd need communication from os.fork() as to when it |
| 790 | # has actually happened. Given this is a regression test |
| 791 | # for a fixed issue, potentially less reliably detecting |
| 792 | # regression via timing is acceptable for simplicity. |
| 793 | # The test will always take at least this long. :( |
| 794 | fork_happened__release_locks_and_end_thread.wait(0.5) |
| 795 | |
| 796 | lock_holder_thread = threading.Thread( |
| 797 | target=lock_holder_thread_fn, |
| 798 | name='test_post_fork_child_no_deadlock lock holder') |
| 799 | lock_holder_thread.start() |
| 800 | |
| 801 | locks_held__ready_to_fork.wait() |
| 802 | pid = os.fork() |
| 803 | if pid == 0: |
| 804 | # Child process |
| 805 | try: |
| 806 | test_logger.info(r'Child process did not deadlock. \o/') |
| 807 | finally: |
| 808 | os._exit(0) |
| 809 | else: |
| 810 | # Parent process |
nothing calls this directly
no test coverage detected