Skip to content

Commit dc9effa

Browse files
committed
[app] Fix thread safety issues when stopping scan
Unlike previously, we're now changing the scanner object in the ScanWindow from the scan thread, which we need to synchronize with the main UI thread. Note that some properties are still accessed without locks, but these are only informational (e.g. the name of the video being processed).
1 parent 801f326 commit dc9effa

1 file changed

Lines changed: 35 additions & 41 deletions

File tree

dvr_scan/app/scan_window.py

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,9 @@ def __init__(
5151
self._total_videos = len(settings)
5252
self._multi_video_mode = self._total_videos > 1
5353

54-
# Initialize scanner for the first video
55-
self._scanner = init_scanner(self._settings_list[0])
56-
self._scanner.set_callbacks(
57-
scan_started=self._on_scan_started,
58-
processed_frame=self._on_processed_frame,
59-
)
54+
# Scanner is created in _do_scan for each video
55+
self._scanner = None
56+
self._scanner_lock = threading.Lock()
6057

6158
# Determine if we should open folder on completion
6259
first_settings = self._settings_list[0]
@@ -102,8 +99,8 @@ def __init__(
10299
current_row += 1
103100

104101
# Current video name label
105-
video_name = Path(self._settings_list[0].get("input")[0]).name
106-
self._current_video_label = tk.Label(self._root, text=f"Current: {video_name}")
102+
self._current_video_name = Path(self._settings_list[0].get("input")[0]).name
103+
self._current_video_label = tk.Label(self._root, text=f"{self._current_video_name}")
107104
self._current_video_label.grid(row=current_row, column=0, columnspan=2, sticky=tk.NSEW)
108105
current_row += 1
109106
else:
@@ -192,8 +189,7 @@ def _update(self):
192189
logger.critical(f"error during scan:\n{formatted_exception}")
193190
messagebox.showerror(
194191
"Scan Error",
195-
"Error during scanning. See log messages for more info."
196-
f"\nSummary: {self._scan_exception}",
192+
f"Error during scan: {self._scan_exception}\n\nSee log messages for more info.",
197193
parent=self._root,
198194
)
199195
self._destroy()
@@ -204,12 +200,11 @@ def _update(self):
204200
# Update final state
205201
if self._multi_video_mode:
206202
self._video_progress.set(self._total_videos)
207-
self._video_progress_label["text"] = (
208-
f"Completed {self._total_videos} of {self._total_videos} videos"
209-
)
210-
self._events_label["text"] = f"Total Events Found: {self._total_events}"
203+
self._video_progress_label["text"] = f"Scanned {self._total_videos} Videos"
204+
self._events_label["text"] = f"Total Events: {self._total_events}"
205+
self._current_video_label.config(text=f"{self._current_video_name}")
211206
else:
212-
self._events_label["text"] = f"Events Found: {self._num_events}"
207+
self._events_label["text"] = f"Total Events: {self._num_events}"
213208

214209
self._progress.set(self._expected_num_frames)
215210
self._elapsed_label["text"] = f"Elapsed: {self._elapsed}"
@@ -243,11 +238,9 @@ def _update(self):
243238
self._video_progress_label["text"] = (
244239
f"Video {self._current_video_index + 1} of {self._total_videos}"
245240
)
246-
self._events_label["text"] = (
247-
f"Events Found: {self._num_events} (Total: {self._total_events})"
248-
)
241+
self._events_label["text"] = f"Events: {self._num_events} (Total: {self._total_events})"
249242
else:
250-
self._events_label["text"] = f"Events Found: {self._num_events}"
243+
self._events_label["text"] = f"Events: {self._num_events}"
251244

252245
self._elapsed_label["text"] = f"Elapsed: {self._elapsed}"
253246
self._remaining_label["text"] = f"Time Remaining:\n{self._remaining}"
@@ -271,8 +264,13 @@ def _destroy(self):
271264
logger.debug("root destroyed")
272265

273266
def prompt_stop(self):
274-
if self._scanner.is_stopped() and self._scan_finished.is_set():
275-
return
267+
with self._scanner_lock:
268+
if (
269+
self._scanner is not None
270+
and self._scanner.is_stopped()
271+
and self._scan_finished.is_set()
272+
):
273+
return
276274
if messagebox.askyesno(
277275
title="Stop scan?",
278276
message="Are you sure you want to stop the current scan?",
@@ -282,10 +280,12 @@ def prompt_stop(self):
282280
self._destroy()
283281

284282
def stop(self):
285-
self._stopped = True
286-
if not self._scanner.is_stopped():
287-
logger.debug("stopping scan thread")
288-
self._scanner.stop()
283+
with self._scanner_lock:
284+
self._stopped = True
285+
if self._scanner is not None and not self._scanner.is_stopped():
286+
logger.debug("stopping scan thread")
287+
self._scanner.stop()
288+
if self._scan_thread.is_alive():
289289
self._scan_thread.join()
290290

291291
def show(self):
@@ -328,31 +328,25 @@ def _do_scan(self):
328328

329329
try:
330330
for i, settings in enumerate(self._settings_list):
331-
if self._stopped:
332-
break
333-
334331
self._current_video_index = i
335332

336333
# Update video label in multi-video mode
337334
if self._multi_video_mode and self._current_video_label:
338-
video_name = Path(settings.get("input")[0]).name
339-
# Schedule UI update on main thread
340-
self._root.after(
341-
0,
342-
lambda n=video_name: self._current_video_label.config(text=f"Current: {n}"),
343-
)
335+
self._current_video_name = Path(settings.get("input")[0]).name
344336

345-
# Create new scanner for each video (except the first which is already created)
346-
if i > 0:
337+
# Create scanner for this video
338+
with self._scanner_lock:
339+
if self._stopped:
340+
break
347341
self._scanner = init_scanner(settings)
348342
self._scanner.set_callbacks(
349343
scan_started=self._on_scan_started,
350344
processed_frame=self._on_processed_frame,
351345
)
352-
# Reset per-video state
353-
self._frames_processed = 0
354-
self._num_events = 0
355-
self._progress.set(0)
346+
# Reset per-video state
347+
self._frames_processed = 0
348+
self._num_events = 0
349+
self._progress.set(0)
356350

357351
logger.info(
358352
f"Scanning video {i + 1} of {self._total_videos}: {settings.get('input')[0]}"
@@ -364,7 +358,7 @@ def _do_scan(self):
364358
self._total_events += len(result.event_list)
365359
self._total_frames_processed = total_frames
366360

367-
logger.info(f"Video {i + 1} complete: {len(result.event_list)} events found")
361+
logger.info(f"Finished scanning video: {len(result.event_list)} events found")
368362

369363
except Exception as ex:
370364
self._scan_exception = ex

0 commit comments

Comments
 (0)