Skip to content

"loop argument must agree with lock" instantiating asyncio.Condition #89579

Description

@simonw
mannequin
BPO 45416
Nosy @asvetlov, @ambv, @serhiy-storchaka, @1st1, @achimnol, @simonw, @miss-islington, @uriyyo
PRs
  • bpo-45416: Fix use of asyncio.Condition() with explicit Lock objects #28850
  • [3.10] bpo-45416: Fix use of asyncio.Condition() with explicit Lock objects (GH-28850) #28856
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = <Date 2021-10-12.09:03:49.457>
    created_at = <Date 2021-10-08.20:30:58.548>
    labels = ['3.11', '3.10', 'expert-asyncio']
    title = '"loop argument must agree with lock" instantiating asyncio.Condition'
    updated_at = <Date 2021-10-12.09:03:49.456>
    user = 'https://github.com/simonw'

    bugs.python.org fields:

    activity = <Date 2021-10-12.09:03:49.456>
    actor = 'asvetlov'
    assignee = 'none'
    closed = True
    closed_date = <Date 2021-10-12.09:03:49.457>
    closer = 'asvetlov'
    components = ['asyncio']
    creation = <Date 2021-10-08.20:30:58.548>
    creator = 'simonw'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 45416
    keywords = ['patch']
    message_count = 7.0
    messages = ['403500', '403501', '403502', '403506', '403599', '403601', '403719']
    nosy_count = 8.0
    nosy_names = ['asvetlov', 'lukasz.langa', 'serhiy.storchaka', 'yselivanov', 'Joongi Kim', 'simonw', 'miss-islington', 'uriyyo']
    pr_nums = ['28850', '28856']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = None
    url = 'https://bugs.python.org/issue45416'
    versions = ['Python 3.10', 'Python 3.11']

    Activity

    1. simonw commented on Oct 8, 2021

      simonwmannequin
      MannequinAuthor

      In Python 3.10 it is not possible to instantiate an asyncio.Condition that wraps an asyncio.Lock without raising a "loop argument must agree with lock" exception.

      This code raises that exception:

          asyncio.Condition(asyncio.Lock())

      This worked in previous Python versions.

      Note that the error only occurs if an event loop is running. Here's a simple script that replicates the problem:

          import asyncio
      
          # This runs without an exception:
          print(asyncio.Condition(asyncio.Lock()))
      
          # This does not work:
          async def example():
              print(asyncio.Condition(asyncio.Lock()))
      
          # This raises "ValueError: loop argument must agree with lock":
          asyncio.run(example())
    2. simonw commented on Oct 8, 2021

      simonwmannequin
      MannequinAuthor

      I ran across this issue while trying to use the https://pypi.org/project/janus/ locking library with Python 3.10 - see my issue on their tracker here: aio-libs/janus#358

    3. simonw commented on Oct 8, 2021

      simonwmannequin
      MannequinAuthor

      It looks like the relevant test is here:

      def test_explicit_lock(self):
      lock = asyncio.Lock()
      cond = asyncio.Condition(lock)
      self.assertIs(cond._lock, lock)
      self.assertIs(cond._loop, lock._loop)

          def test_explicit_lock(self):
              lock = asyncio.Lock()
              cond = asyncio.Condition(lock)
      
              self.assertIs(cond._lock, lock)
              self.assertIs(cond._loop, lock._loop)

      But... that test doesn't appear to run inside an event loop, so it's not covering the behaviour described in this issue.

    4. ambv commented on Oct 8, 2021

      @ambv
      Contributor

      Issue confirmed. The problem is that Condition.__init__ compares its own loop with Lock's internal _loop attribute. That attribute is set to None unless the lock waited to be acquired.

      uriyyo, Andrew, Yury, since it's pretty likely that Lock objects will now have _loop set to None, I think the check for "loop agreement" in Condition is kind of useless. So I propose to simply remove it. It's the only such check in all of asyncio.

      If you insist on keeping the loop check, it should special-case _loop is None.

      Simon, thanks for your detailed report! As a backwards-compatible workaround till we get a fix in, your user code can do the following:

      >>> l = asyncio.Lock()
      >>> getattr(l, '_get_loop', lambda: None)()
      <_UnixSelectorEventLoop running=True closed=False debug=False>

      You can use such lock without issues now:

      >>> asyncio.Condition(l)
      <asyncio.locks.Condition object at 0x10c05bee0 [unlocked]>

      Alternatively, if the above disgusts you and you only want to trigger public APIs, you can do this dance:

      >>> l = asyncio.Lock()
      >>> await l.acquire()  # first acquire will just work
      True
      >>> try:
      ...   # second acquire will block so we time it out
      ...   await asyncio.wait_for(l.acquire(), 0.1)
      ... except asyncio.TimeoutError:
      ...   pass
      ...
      >>> l.release()

      Now the lock is fully initialized and we can use it:

      >> c = asyncio.Condition(l)

      Both workarounds should be compatible with Python 3.7+ asyncio.

    5. serhiy-storchaka commented on Oct 10, 2021

      @serhiy-storchaka
      Member

      New changeset 1a78924 by Joongi Kim in branch 'main':
      bpo-45416: Fix use of asyncio.Condition() with explicit Lock objects (GH-28850)
      1a78924

    6. miss-islington commented on Oct 10, 2021

      @miss-islington
      Contributor

      New changeset 164dddf by Miss Islington (bot) in branch '3.10':
      bpo-45416: Fix use of asyncio.Condition() with explicit Lock objects (GH-28850)
      164dddf

    7. asvetlov commented on Oct 12, 2021

      @asvetlov
      Contributor

      Thanks, guys!

    8. transferred this issue fromon Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions