Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bluez: Create a bluez manager instance per-event-loop #1034

Merged
merged 1 commit into from
Sep 27, 2022

Conversation

projectgus
Copy link
Contributor

@projectgus projectgus commented Sep 27, 2022

Alternative to #1031. Avoids "got Future attached to a different loop" if Bleak instances with BlueZ backend are constructed in context of more than one event loop (for example, in default configuration of pytest-asyncio).

Also enables parallel access to Bleak instances from multiple event loops, provided the same Bleak object is not shared between two event loops. Each backend manager instance establishes an independent dbus connection with a unique dbus address.

Some quick code that demonstrates using Bleak in parallel from multiple threads (and therefore multiple event loops):

import asyncio
import logging
from logging import getLogger, getLevelName, Formatter, StreamHandler
import threading
import time

from bleak import BleakScanner

log = getLogger()
log.setLevel(getLevelName('INFO'))
log_formatter = Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s [%(threadName)s] ")
console_handler = StreamHandler()
console_handler.setFormatter(log_formatter)
log.addHandler(console_handler)

def scanner_callback(device, adv_data):
    log.info(f"Callback Loop {id(asyncio.get_running_loop())} device {device}")

async def scan_coro():
    log.info(f"Scanning Loop {id(asyncio.get_running_loop())}")
    async with BleakScanner(scanner_callback):
        await asyncio.sleep(2)
    log.info("Coro done")

def run_scanner():
    log.info("Running thread")
    asyncio.run(scan_coro(), debug=True)
    log.info("Thread done")

threads = [ threading.Thread(target=run_scanner),
            threading.Thread(target=run_scanner),
            threading.Thread(target=run_scanner), ]

for t in threads:
    t.start()
    # note need to stagger starting scanning as BlueZ seems to drop responses to StartDiscovery if they happen at
    # the same time (seems to be a BlueZ limitation or bug, not anything Bleak is doing).
    time.sleep(0.4) 

[t.join() for t in threads]

There is also a simple pytest-asyncio case in PR #1031 that fails without this patch and passes with this patch.

Copy link
Collaborator

@dlech dlech left a comment

Choose a reason for hiding this comment

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

Looks good. Just a few suggestions.

bleak/backends/bluezdbus/manager.py Outdated Show resolved Hide resolved
bleak/backends/bluezdbus/manager.py Outdated Show resolved Hide resolved
bleak/backends/bluezdbus/manager.py Outdated Show resolved Hide resolved
Avoids "got Future <Future pending> attached to a different loop" if Bleak
instances are constructed in context of more than one event loop.
@projectgus
Copy link
Contributor Author

@dlech Thanks for the quick and thorough review! Updated accordingly.

@dlech dlech merged commit 433a811 into hbldh:develop Sep 27, 2022
@dlech
Copy link
Collaborator

dlech commented Sep 27, 2022

Thanks!

@projectgus projectgus deleted the bugfix/bluez_manager_per_loop branch September 27, 2022 03:03
@dlech dlech mentioned this pull request Oct 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants