Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions ok/task/TaskExecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class TaskExecutor:
pause_end_time: float
_last_frame_time: float
wait_until_timeout: float
frame_stall_timeout: float
device_manager: object
feature_set: object
wait_until_settle_time: float
Expand Down Expand Up @@ -54,7 +55,8 @@ def __init__(self, device_manager,
wait_until_timeout=10, wait_until_settle_time=-1,
exit_event=None, feature_set=None,
ocr_lib=None,
config_folder=None, debug=False, global_config=None, ocr_target_height=0, config=None):
config_folder=None, debug=False, global_config=None, ocr_target_height=0, config=None,
frame_stall_timeout=60):
self._frame = None
device_manager.executor = self
self.pause_start = time.time()
Expand All @@ -73,6 +75,7 @@ def __init__(self, device_manager,
self.feature_set = feature_set
self.wait_until_settle_time = wait_until_settle_time
self.wait_scene_timeout = wait_until_timeout
self.frame_stall_timeout = frame_stall_timeout
self.exit_event = exit_event
self.debug_mode = False
self.debug = debug
Expand Down Expand Up @@ -283,8 +286,16 @@ def frame(self):
if self.exit_event.is_set():
logger.info("frame Exit event set. Exiting early.")
sys.exit(0)
if self._frame is None:
start = time.time()
while self._frame is None:
self.next_frame()
if self._frame is None:
elapsed = time.time() - start
if elapsed > self.frame_stall_timeout:
raise CaptureException(
f'Unable to capture frame for {self.frame_stall_timeout} seconds')
logger.warning(
f'no frame for {elapsed:.0f}s, retrying capture (timeout {self.frame_stall_timeout}s)')
return self._frame

def check_enabled(self, check_pause=True):
Expand Down Expand Up @@ -565,7 +576,7 @@ def execute(self):
task.info_set(QCoreApplication.tr('app', 'Error'), error)
logger.error(f"{name} exception stopped", e)
if self._frame is not None:
communicate.screenshot.emit(self.frame, name, True, None)
communicate.screenshot.emit(self._frame, name, True, None)
self.current_task = None
communicate.task.emit(None)
self.destroy()
Expand Down
51 changes: 51 additions & 0 deletions tests/test_task_executor_frame.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import threading
import time
import unittest

from ok.task.TaskExecutor import TaskExecutor
from ok.task.exceptions import CaptureException


class TestFrameStall(unittest.TestCase):
def make_executor(self):
executor = TaskExecutor.__new__(TaskExecutor)
executor.exit_event = threading.Event()
executor.paused = False
executor.debug_mode = False
executor._frame = None
executor.frame_stall_timeout = 0.1
return executor

def test_frame_raises_capture_exception_when_capture_stalls(self):
executor = self.make_executor()

def stalled_next_frame(time_out=6):
time.sleep(0.02)
return None

executor.next_frame = stalled_next_frame
with self.assertRaises(CaptureException):
_ = executor.frame

def test_frame_returns_frame_when_capture_recovers(self):
executor = self.make_executor()
calls = []

def recovering_next_frame(time_out=6):
calls.append(1)
if len(calls) >= 2:
executor._frame = 'frame'
return executor._frame

executor.next_frame = recovering_next_frame
self.assertEqual('frame', executor.frame)
self.assertGreaterEqual(len(calls), 2)

def test_frame_returns_existing_frame_without_capture(self):
executor = self.make_executor()
executor._frame = 'frame'
self.assertEqual('frame', executor.frame)


if __name__ == '__main__':
unittest.main()