ble-reticulum/test_monitoring.py

100 lines
3.2 KiB
Python
Raw Permalink Normal View History

fix(ble): Fix D-Bus disconnect monitoring with ObjectManager and polling fallback The original D-Bus monitoring implementation (from peripheral disconnect fix) wasn't receiving signals due to improper low-level API usage. This commit replaces it with two reliable solutions: Solution A: High-Level ObjectManager API - Uses proper D-Bus proxy interface with automatic signal subscription - Discovers and subscribes to all BlueZ devices (existing + new) - PropertiesChanged callbacks properly integrated with asyncio event loop - Signals now correctly delivered when centrals disconnect Solution B: Timeout-Based Polling Fallback - Polls BlueZ device state every 30 seconds as safety net - Detects stale connections missed by D-Bus signals - Uses sync dbus-python for simplicity and reliability - Guaranteed cleanup within 30s even if signals fail Implementation: - Replaced _monitor_device_disconnections() with ObjectManager-based approach - Added _poll_stale_connections() as polling fallback - Both threads run concurrently for dual-layer monitoring - Cleanup is idempotent (both detecting same disconnect is safe) Testing: - Added test_dbus_disconnect_monitoring.py (10 test cases) - Added test_stale_connection_polling.py (8 test cases) - Added 2 integration tests to test_peripheral_disconnect_cleanup.py - All tests mock D-Bus libraries, no real D-Bus required - Manual validation script (test_monitoring.py) verified locally Impact: - Peripheral disconnects now detected within ~1s (D-Bus) or 30s max (polling) - Prevents "max peers (7) reached" blocking after multiple disconnect cycles - System can handle unlimited connect/disconnect cycles without memory leaks Reference: DBUS_MONITORING_FIX.md for complete analysis and troubleshooting 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 20:10:44 -05:00
#!/usr/bin/env python3
"""
Quick test script to verify D-Bus monitoring threads start correctly.
"""
import sys
import time
import threading
# Add src to path
sys.path.insert(0, 'src')
from RNS.Interfaces.linux_bluetooth_driver import BluezeroGATTServer
print("=" * 60)
print("Testing D-Bus Monitoring Thread Startup")
print("=" * 60)
# Create a mock driver with minimal attributes needed
class MockDriver:
def __init__(self):
self._peers = {}
self._peers_lock = threading.RLock()
def _log(self, msg, level="INFO"):
print(f"[{level}] {msg}")
def _handle_peripheral_disconnected(self, address):
print(f"[MOCK] Peripheral disconnected callback: {address}")
# Create GATT server instance
driver = MockDriver()
gatt_server = BluezeroGATTServer(
driver=driver,
adapter_index=0,
service_uuid="00000000-0000-0000-0000-000000000000",
rx_char_uuid="00000000-0000-0000-0000-000000000001",
tx_char_uuid="00000000-0000-0000-0000-000000000002",
identity_char_uuid="00000000-0000-0000-0000-000000000003"
)
# Set identity (required before start)
gatt_server.identity_bytes = b'0' * 16
print("\nAttempting to start monitoring threads (without full GATT server)...")
print("This will test if the threads can be created and started.\n")
# Manually start just the monitoring threads
print("[TEST] Starting D-Bus disconnect monitoring thread...")
try:
gatt_server.disconnect_monitor_thread = threading.Thread(
target=gatt_server._monitor_device_disconnections,
daemon=True,
name="test-dbus-monitor"
)
gatt_server.disconnect_monitor_thread.start()
print("[TEST] ✓ D-Bus monitoring thread started")
except Exception as e:
print(f"[TEST] ✗ Failed to start D-Bus monitoring thread: {e}")
import traceback
traceback.print_exc()
print("\n[TEST] Starting stale connection polling thread...")
try:
gatt_server.stale_poll_thread = threading.Thread(
target=gatt_server._poll_stale_connections,
daemon=True,
name="test-stale-poller"
)
gatt_server.stale_poll_thread.start()
print("[TEST] ✓ Stale polling thread started")
except Exception as e:
print(f"[TEST] ✗ Failed to start stale polling thread: {e}")
import traceback
traceback.print_exc()
print("\n[TEST] Waiting 5 seconds to observe thread behavior...")
print("[TEST] Check stderr output above for [GATT-MONITOR] and [STALE-POLL] messages")
time.sleep(5)
print("\n[TEST] Stopping threads...")
gatt_server.stop_event.set()
# Wait for threads to exit
if gatt_server.disconnect_monitor_thread and gatt_server.disconnect_monitor_thread.is_alive():
gatt_server.disconnect_monitor_thread.join(timeout=3.0)
if not gatt_server.disconnect_monitor_thread.is_alive():
print("[TEST] ✓ D-Bus monitoring thread stopped cleanly")
else:
print("[TEST] ✗ D-Bus monitoring thread did not stop")
if gatt_server.stale_poll_thread and gatt_server.stale_poll_thread.is_alive():
gatt_server.stale_poll_thread.join(timeout=3.0)
if not gatt_server.stale_poll_thread.is_alive():
print("[TEST] ✓ Stale polling thread stopped cleanly")
else:
print("[TEST] ✗ Stale polling thread did not stop")
print("\n" + "=" * 60)
print("Test complete!")
print("=" * 60)