Skip to content

Conversation

@note35
Copy link
Contributor

@note35 note35 commented Dec 10, 2025

The fix is aligned with bug reporter's observation.

Test with

  1. ./python -m test test_interpreters test_struct (This one triggers OP's issue.)
  2. Repro script by Victor.
  3. ./python -m test test_interpreters test_concurrent_futures.test_interpreter_pool (This one triggers the forever loop after the initial fix.)

Copy link
Member

@ZeroIntensity ZeroIntensity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs a news entry, please add one. You can use blurb-it or the blurb command-line tool.

@note35 note35 changed the title gh-142414: Add register_unbound in _crossinterp.py and initialize it in _queues.py gh-142414: Align UNBOUND in Lib/concurrent/interpreters/_queues.py and Lib/concurrent/interpreters/_crossinterp.py. Dec 10, 2025
@note35 note35 requested a review from ZeroIntensity December 10, 2025 15:29
@note35 note35 changed the title gh-142414: Align UNBOUND in Lib/concurrent/interpreters/_queues.py and Lib/concurrent/interpreters/_crossinterp.py. gh-142414: Unify UNBOUND in Lib/concurrent/interpreters/_queues.py and Lib/concurrent/interpreters/_crossinterp.py. Dec 10, 2025
Copy link
Member

@ZeroIntensity ZeroIntensity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would also be good to add a dedicated test for this, similar to Victor's small repro in the original issue. This bug only occurs under some special circumstances in the current test suite, which isn't ideal.

@note35
Copy link
Contributor Author

note35 commented Dec 11, 2025

I also created another PR to remove singleton - #142553

We don't need to make singleton picklabe based on the current tests.
Comment on lines +430 to +431
except queues.QueueEmpty:
pass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happened here?

Copy link
Contributor Author

@note35 note35 Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

./python -m test test_interpreters test_concurrent_futures.test_interpreter_pool 
Using random seed: 1867170309
0:00:00 load avg: 0.27 Run 2 tests sequentially in a single process
0:00:00 load avg: 0.27 [1/2] test_interpreters
0:00:24 load avg: 0.48 [1/2] test_interpreters passed
0:00:24 load avg: 0.48 [2/2] test_concurrent_futures.test_interpreter_pool
... (running forever without catching queues.QueueEmpty)
def test_blocking_with_limited_workers(self):
    ready = queues.create()
    blocker = queues.create()

    def run(taskid, ready, blocker):
        ready.put_nowait(taskid)
        blocker.get()

    numtasks = 10
    futures = []
    with self.executor_type(4) as executor:
        for i in range(numtasks):
            fut = executor.submit(run, i, ready, blocker)
            futures.append(fut)
        
        pending = numtasks
        while pending > 0:
            done = 0
            for _ in range(pending):
                try:
                    ready.get(timeout=1)
                    # https://github.com/python/cpython/blob/2a820e2b9cf9e470d9f5342019fca3fe7f4ed7bc/Lib/concurrent/interpreters/_queues.py#L261C20-L261C30

                    # test_interpreters test_concurrent_futures.test_interpreter_pool 
                    # Before: queue.get() throws interpreters.QueueEmpty
                    # After: queue.get() throws queues.QueueEmpty

                    # test_concurrent_futures.test_interpreter_pool 
                    # Before: queue.get() throws interpreters.QueueEmpty.
                    # After: queue.get() throws interpreters.QueueEmpty
                except interpreters.QueueEmpty:
                    pass                    
                except queues.QueueEmpty:
                    pass                    
                else:
                    # If the error is not caught, the loop is running forever.
                    done += 1
            pending -= done
            
            for _ in range(done):
                blocker.put_nowait(None)

Why does running test_interpreters + test_concurrent_futures.test_interpreter_pool make the queue.get() throws queues.QueueEmpty after having the same UNBOUND object in _queues.py and _crossinterp.py?

FWIK, cpython/Lib/concurrent/interpreters/init.py loads queues.QueueEmpty as interpreters.QueueEmpty, and before this PR, queue.get() is expected to throw interpreters.QueueEmpty,

For some reasons, queue.get() throws _queues.QueueEmpty after running test_interpreters. This may be another thing to explore further (I think it depends on how the sub-interpreter is implemented. Do you have any knowledge about this?

Given this is a test fix, maybe it's okay to have a further dive deep separately? I think this so far can be reproduced only during a combination of test_interpreters + test_concurrent_futures.test_interpreter_pool. (which is likely rare and not reported?) I'd also like to add a TODO gh-XXX, but we don't yet have enough context to make an issue...)

@note35 note35 requested a review from ZeroIntensity December 12, 2025 08:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants