Skip to content

Commit 02fb3f7

Browse files
committed
PR comments
1 parent 2765531 commit 02fb3f7

3 files changed

Lines changed: 48 additions & 6 deletions

File tree

EasyReflectometryApp/Backends/Py/analysis.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,9 @@ def fittingStartStop(self) -> None:
123123

124124
def _start_threaded_fit(self) -> None:
125125
"""Start fitting in a background thread."""
126-
# Reset flags and prepare for fit
126+
# Reset flags and prepare for fit using proper encapsulation
127127
self._fitting_logic.reset_stop_flag()
128-
self._fitting_logic._running = True
129-
self._fitting_logic._finished = False
130-
self._fitting_logic._show_results_dialog = False
131-
self._fitting_logic._fit_error_message = None
128+
self._fitting_logic.prepare_for_threaded_fit()
132129
self.fittingChanged.emit()
133130

134131
# Prepare fit data for all experiments

EasyReflectometryApp/Backends/Py/logic/fitting.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,17 @@ def reset_stop_flag(self) -> None:
8888
self._stop_requested = False
8989
self._fit_cancelled = False
9090

91+
def prepare_for_threaded_fit(self) -> None:
92+
"""Prepare state for a new threaded fit.
93+
94+
This method sets the internal state flags to indicate a fit is starting.
95+
Call this before launching the background thread.
96+
"""
97+
self._running = True
98+
self._finished = False
99+
self._show_results_dialog = False
100+
self._fit_error_message = None
101+
91102
def prepare_threaded_fit(self, minimizers_logic: 'Minimizers') -> tuple:
92103
"""Prepare data for threaded fitting.
93104
@@ -112,6 +123,20 @@ def prepare_threaded_fit(self, minimizers_logic: 'Minimizers') -> tuple:
112123
# Prepare data arrays for all experiments
113124
x_data = [experiments[idx].x for idx in experiments]
114125
y_data = [experiments[idx].y for idx in experiments]
126+
127+
# Validate error values before computing weights to avoid division by zero
128+
import numpy as np
129+
130+
for idx in experiments:
131+
ye = experiments[idx].ye
132+
if np.any(ye == 0):
133+
exp_name = experiments[idx].name if hasattr(experiments[idx], 'name') else f'index {idx}'
134+
self._fit_error_message = f'Experiment {exp_name} has zero error values which would cause division by zero'
135+
self._running = False
136+
self._finished = True
137+
self._show_results_dialog = True
138+
return None, None, None, None, None
139+
115140
weights = [1.0 / experiments[idx].ye for idx in experiments]
116141

117142
# Method is optional in fit() - pass None to use minimizer's default

EasyReflectometryApp/Backends/Py/workers/fitter_worker.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@ def run(self) -> None:
9494
method = getattr(self._fitter, self._method_name)
9595
result = method(*self._args, **self._kwargs)
9696

97-
# Check if stop was requested during execution
97+
# NOTE: This check only catches stop requests that occurred AFTER the fit
98+
# completed but before we emit the result. It does NOT interrupt the fitting
99+
# algorithm mid-execution since lmfit/scipy don't support cancellation callbacks.
100+
# The effective cancellation window is only before the fit starts (checked above).
98101
if self._stop_requested:
99102
self.failed.emit('Fitting cancelled by user')
100103
return
@@ -119,9 +122,26 @@ def stop(self) -> None:
119122
This sets a flag that is checked during execution and also
120123
terminates the thread if it's still running. Call wait() after
121124
this to ensure proper thread cleanup.
125+
126+
.. warning::
127+
DANGEROUS: This method uses QThread.terminate() which is strongly
128+
discouraged by Qt documentation. It can:
129+
- Leave mutex locks held indefinitely causing deadlocks
130+
- Corrupt data structures mid-operation
131+
- Prevent proper cleanup of resources (especially numpy arrays, scipy internals)
132+
- Cause memory leaks and undefined behavior
133+
134+
The fitting libraries (lmfit, scipy) do not support graceful cancellation.
135+
The stop flag is only effective BEFORE the fit starts - once the fitting
136+
algorithm is running, it cannot be interrupted cleanly.
137+
138+
See THREAD_TERMINATION_WARNING.md for details on known issues and
139+
potential future improvements (e.g., using subprocess instead of QThread).
122140
"""
123141
self._stop_requested = True
124142
if self.isRunning():
143+
# WARNING: terminate() is dangerous but necessary since fitting
144+
# libraries don't support graceful cancellation. See docstring above.
125145
self.terminate()
126146
self.wait()
127147

0 commit comments

Comments
 (0)