Skip to content

Commit 7011c14

Browse files
Bwoocetridge
authored andcommitted
Fix scheduler callback overlap and improve transfer management
- Fix periodic scheduler callback overlap preventing NodeStatus flooding - Add GUI responsiveness protection by limiting frames per spin cycle - Improve transfer reassembly with proactive cleanup and shorter timeouts - Fix dictionary iteration bug in transfer cleanup These changes resolve NodeStatus flooding and GUI freezing issues while improving frame decode reliability.
1 parent 5d69bc3 commit 7011c14

File tree

2 files changed

+20
-7
lines changed

2 files changed

+20
-7
lines changed

dronecan/node.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,18 +98,17 @@ def periodic(self, period_seconds, callback):
9898
callback_running = [False] # Use list to allow modification in nested function
9999

100100
def caller(scheduled_deadline):
101+
# Always reschedule first to maintain periodic timing
102+
scheduled_deadline += period_seconds
103+
event_holder[0] = self._scheduler.enterabs(scheduled_deadline, priority, caller, (scheduled_deadline,))
104+
101105
# Prevent callback overlap that can cause runaway scheduling
102106
if callback_running[0]:
103107
# Skip this execution if previous callback is still running
104-
scheduled_deadline += period_seconds
105-
event_holder[0] = self._scheduler.enterabs(scheduled_deadline, priority, caller, (scheduled_deadline,))
106108
return
107109

108110
callback_running[0] = True
109111
try:
110-
# Event MUST be re-registered first in order to ensure that it can be cancelled from the callback
111-
scheduled_deadline += period_seconds
112-
event_holder[0] = self._scheduler.enterabs(scheduled_deadline, priority, caller, (scheduled_deadline,))
113112
callback()
114113
finally:
115114
callback_running[0] = False
@@ -450,7 +449,9 @@ def execute_once():
450449
while time.monotonic() < deadline:
451450
execute_once()
452451
else:
453-
while True:
452+
# Process available frames with a reasonable limit to prevent GUI blocking
453+
max_frames_per_spin = 100 # Limit frames processed per spin(0) call
454+
while count < max_frames_per_spin:
454455
frame = self._can_driver.receive(0)
455456
if frame:
456457
self._recv_frame(frame)

dronecan/transport.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
import dronecan
2626
import dronecan.dsdl as dsdl
2727
import dronecan.dsdl.common as common
28+
from logging import getLogger
29+
30+
logger = getLogger(__name__)
2831

2932

3033
try:
@@ -893,9 +896,18 @@ def __init__(self):
893896
def receive_frame(self, frame):
894897
result = None
895898
key = frame.transfer_key
899+
900+
# Clean up any stale transfers before processing new frames
901+
self.remove_inactive_transfers(timeout=0.1) # Much shorter timeout
902+
896903
if key in self.active_transfers or frame.start_of_transfer:
897904
# If the first frame was received, restart this transfer from scratch
898905
if frame.start_of_transfer:
906+
if key in self.active_transfers:
907+
# Log when we're restarting an incomplete transfer (only for debugging)
908+
# logger.debug('Restarting incomplete transfer for key %r (had %d frames)',
909+
# key, len(self.active_transfers[key]))
910+
pass
899911
self.active_transfers[key] = []
900912

901913
self.active_transfers[key].append(frame)
@@ -911,7 +923,7 @@ def receive_frame(self, frame):
911923

912924
def remove_inactive_transfers(self, timeout=1.0):
913925
t = time.monotonic()
914-
transfer_keys = self.active_transfers.keys()
926+
transfer_keys = list(self.active_transfers.keys()) # Create a copy to avoid iteration issues
915927
for key in transfer_keys:
916928
if t - self.active_transfer_timestamps[key] > timeout:
917929
del self.active_transfers[key]

0 commit comments

Comments
 (0)