From 49192f3dff0599b01f013f896fcc7ee8cdb97d28 Mon Sep 17 00:00:00 2001 From: Elias Bonnici Date: Tue, 7 Jan 2025 15:25:44 +0100 Subject: [PATCH] Handle empty queue after `K_CF_RUN_LOOP_RUN_HANDLED_SOURCE` Occassionally CFRunLoopInMode() returns K_CF_RUN_LOOP_RUN_HANDLED_SOURCE even though the _hid_read_callback() has not been invoked, resulting in reading from an empty queue. We can handle this by re-running the run loop with a retry mechanism. --- fido2/hid/macos.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/fido2/hid/macos.py b/fido2/hid/macos.py index a0bb156f..d609ad93 100644 --- a/fido2/hid/macos.py +++ b/fido2/hid/macos.py @@ -253,14 +253,28 @@ def _dev_read_thread(hid_device): hid_device.handle, REMOVAL_CALLBACK, ctypes.py_object(hid_device) ) - # Run the run loop - run_loop_run_result = cf.CFRunLoopRunInMode( - K_CF_RUNLOOP_DEFAULT_MODE, 4, True # Timeout in seconds - ) # Return after source handled - - # log any unexpected run loop exit - if run_loop_run_result != K_CF_RUN_LOOP_RUN_HANDLED_SOURCE: - logger.error("Unexpected run loop exit code: %d", run_loop_run_result) + max_retries = 2 # Maximum number of run loop retries + retries = 0 + + while retries < max_retries: + # Run the run loop + run_loop_run_result = cf.CFRunLoopRunInMode( + K_CF_RUNLOOP_DEFAULT_MODE, 4, True # Timeout in seconds + ) # Return after source handled + + received_data = not hid_device.read_queue.empty() + if run_loop_run_result == K_CF_RUN_LOOP_RUN_HANDLED_SOURCE: + if received_data: + # Return when data has been received + break + else: + # Retry running the run loop if data has not been received yet + logger.debug("Read queue empty after HANDLE_SOURCE, attempting retry") + retries += 1 + else: + # log any unexpected run loop exit + logger.error("Unexpected run loop exit code: %d", run_loop_run_result) + break # Unschedule from run loop iokit.IOHIDDeviceUnscheduleFromRunLoop(