diff --git a/dronecan/node.py b/dronecan/node.py index 3e504dd..00e970a 100644 --- a/dronecan/node.py +++ b/dronecan/node.py @@ -95,12 +95,23 @@ def periodic(self, period_seconds, callback): :returns: EventHandle object. Call .remove() on it to cancel the event. """ priority = 0 + callback_running = [False] # Use list to allow modification in nested function def caller(scheduled_deadline): - # Event MUST be re-registered first in order to ensure that it can be cancelled from the callback + # Always reschedule first to maintain periodic timing scheduled_deadline += period_seconds event_holder[0] = self._scheduler.enterabs(scheduled_deadline, priority, caller, (scheduled_deadline,)) - callback() + + # Prevent callback overlap that can cause runaway scheduling + if callback_running[0]: + # Skip this execution if previous callback is still running + return + + callback_running[0] = True + try: + callback() + finally: + callback_running[0] = False first_deadline = self._scheduler.timefunc() + period_seconds event_holder = [self._scheduler.enterabs(first_deadline, priority, caller, (first_deadline,))] @@ -438,7 +449,9 @@ def execute_once(): while time.monotonic() < deadline: execute_once() else: - while True: + # Process available frames with a reasonable limit to prevent GUI blocking + max_frames_per_spin = 100 # Limit frames processed per spin(0) call + while count < max_frames_per_spin: frame = self._can_driver.receive(0) if frame: self._recv_frame(frame) diff --git a/dronecan/transport.py b/dronecan/transport.py index 56bd35e..66ee709 100644 --- a/dronecan/transport.py +++ b/dronecan/transport.py @@ -25,6 +25,9 @@ import dronecan import dronecan.dsdl as dsdl import dronecan.dsdl.common as common +from logging import getLogger + +logger = getLogger(__name__) try: @@ -893,9 +896,18 @@ def __init__(self): def receive_frame(self, frame): result = None key = frame.transfer_key + + # Clean up any stale transfers before processing new frames + self.remove_inactive_transfers(timeout=0.1) # Much shorter timeout + if key in self.active_transfers or frame.start_of_transfer: # If the first frame was received, restart this transfer from scratch if frame.start_of_transfer: + if key in self.active_transfers: + # Log when we're restarting an incomplete transfer (only for debugging) + # logger.debug('Restarting incomplete transfer for key %r (had %d frames)', + # key, len(self.active_transfers[key])) + pass self.active_transfers[key] = [] self.active_transfers[key].append(frame) @@ -911,7 +923,7 @@ def receive_frame(self, frame): def remove_inactive_transfers(self, timeout=1.0): t = time.monotonic() - transfer_keys = self.active_transfers.keys() + transfer_keys = list(self.active_transfers.keys()) # Create a copy to avoid iteration issues for key in transfer_keys: if t - self.active_transfer_timestamps[key] > timeout: del self.active_transfers[key]