Summary
Summary
CustomThread extends threading.Thread to capture and return the result of a background thread's target function via a custom join(). However, if the target raises an exception, the exception is silently swallowed — self._return stays None and the exception is lost forever. The calling code receives None as if the thread completed successfully, making it impossible to distinguish a clean None return from a thread crash.
This is especially dangerous for model solver threads, where a silent failure gives users false confidence that a run completed — while results are never actually produced.
Affected File
API/Classes/Base/CustomThreadClass.py
Current Behaviour
class CustomThread(Thread):
def run(self):
if self._target is not None:
# ❌ If _target raises, exception is silently swallowed here
self._return = self._target(*self._args, **self._kwargs)
def join(self):
Thread.join(self)
# ❌ Returns None whether thread succeeded or crashed
return self._return
If the solver subprocess raises a FileNotFoundError, RuntimeError, or any other exception:
self._return is never assigned → stays None
- The exception is lost — it does not propagate to the main thread
join() returns None to the caller, which looks identical to a valid empty result
- No error is logged, no user feedback is shown
Expected behavior
Expected Behaviour
- If a background thread raises an exception, that exception must propagate to the caller when
join() is called
join() should re-raise the exception so the route handler can catch it and return a proper error response to the frontend
- No silent failures — the exception must never be discarded
Proposed Fix
import sys
class CustomThread(Thread):
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, Verbose=None):
Thread.__init__(self, group, target, name, args, kwargs)
self._return = None
self._exc_info = None # stores exception if thread crashes
def run(self):
if self._target is not None:
try:
self._return = self._target(*self._args, **self._kwargs)
except Exception:
self._exc_info = sys.exc_info() # capture full traceback
def join(self):
Thread.join(self)
if self._exc_info is not None:
# re-raise the original exception with its original traceback
raise self._exc_info[1].with_traceback(self._exc_info[2])
return self._return
Reproduction steps
Steps to Reproduce
from API.Classes.Base.CustomThreadClass import CustomThread
def failing_task():
raise RuntimeError("Solver failed: file not found")
t = CustomThread(target=failing_task)
t.start()
result = t.join()
# Bug: result is None, no exception is raised
# Expected: RuntimeError should propagate here
print(result) # prints None — silent failure
Environment
Environment
| Field |
Value |
| Affected file |
API/Classes/Base/CustomThreadClass.py |
| Python |
3.10+ |
| Impact |
All background solver runs using CustomThread |
Acceptance Criteria
Labels
bug · correctness · threading · GSoC-2026
Logs or screenshots

Summary
Summary
CustomThreadextendsthreading.Threadto capture and return the result of a background thread's target function via a customjoin(). However, if the target raises an exception, the exception is silently swallowed —self._returnstaysNoneand the exception is lost forever. The calling code receivesNoneas if the thread completed successfully, making it impossible to distinguish a cleanNonereturn from a thread crash.This is especially dangerous for model solver threads, where a silent failure gives users false confidence that a run completed — while results are never actually produced.
Affected File
API/Classes/Base/CustomThreadClass.pyCurrent Behaviour
If the solver subprocess raises a
FileNotFoundError,RuntimeError, or any other exception:self._returnis never assigned → staysNonejoin()returnsNoneto the caller, which looks identical to a valid empty resultExpected behavior
Expected Behaviour
join()is calledjoin()should re-raise the exception so the route handler can catch it and return a proper error response to the frontendProposed Fix
Reproduction steps
Steps to Reproduce
Environment
Environment
API/Classes/Base/CustomThreadClass.pyCustomThreadAcceptance Criteria
CustomThreadtarget propagate to the caller onjoin()join()still returns the target's return value on successLabels
bug·correctness·threading·GSoC-2026Logs or screenshots