Cancel-token protocol¶
PATHOS uses a lightweight cooperative cancellation primitive. Every algorithm
checks space._cancel_requested()
inside its main loop and returns its best-so-far cleanly when the token is set
— no exceptions raised, no work discarded.
This single mechanism powers two user-visible features:
solver(timeout=…)returns the best incumbent on expiry instead ofnot_found. Metaheuristics in particular become naturally anytime — aGeneticAlgorithm.solve()interrupted at second 8 of 10 returns the best individual seen in those 8 seconds.- Meta-algorithms like
AnytimeAStarcompose other algorithms and use the token to skip remaining phases when the budget is exhausted. See Modes & Anytime delivery.
How the Solver sets the token¶
When Solver.solve() is called with a timeout:
- Primary
SIGALRM(ITIMER_REAL) is armed attimeoutseconds. - When it fires, the handler sets the cancel token —
space._request_cancel(). The currently-running algorithm sees the flag at its next check point and returnsSearchResult(best_seen, …, found=True, …)cleanly. - Watchdog backstop: 2 seconds after the primary signal, a secondary
SIGVTALRM(ITIMER_VIRTUAL) fires. Its handler raisesTimeoutError, whichSolver.solve()catches and converts toSearchResult.not_found. This handles algorithms that don't check the token (currently:IDA*,Backtracking,ForwardChecking,MinConflicts).
sequenceDiagram
participant U as User
participant S as Solver
participant A as Algorithm
participant K as CancelToken
U->>S: solver(timeout=10).solve()
S->>S: arm ITIMER_REAL @ 10s
S->>A: solve()
Note over A: runs main loop
Note over S: 10s elapses
S->>K: request_cancel()
A->>K: is_set()? → True
A->>S: SearchResult(best_seen, found=True)
S->>U: SearchResult
If the algorithm doesn't check (e.g. IDA*):
sequenceDiagram
participant U as User
participant S as Solver
participant A as Algorithm (non-cooperating)
participant K as CancelToken
U->>S: solver(timeout=10).solve()
S->>S: arm ITIMER_REAL @ 10s
S->>A: solve()
Note over S: 10s elapses
S->>K: request_cancel()
Note over A: keeps running, ignores token
Note over S: arm ITIMER_VIRTUAL @ +2s grace
Note over S: 2s elapses
S->>A: raise TimeoutError
A-->>S: TimeoutError propagates
S->>U: SearchResult.not_found
Algorithms that cooperate (v1)¶
All of these check the token at the top of their main loop and return best-so-far on cancel:
- Metaheuristics:
HillClimbing,TabuSearch,LocalBeamSearch,SimulatedAnnealing,GeneticAlgorithm,DifferentialEvolution,ParticleSwarm - Path-search:
BFS,DFS,IDDFS,UCS(returnnot_found— no meaningful partial path possible) - Informed:
AStar,WeightedAStar,GreedyBestFirst,BidirectionalAStar(returnnot_found) - Adversarial:
Minimax,AlphaBeta,Negamaxcheck the token at the top of their recursion (returning a(nan, None)sentinel that collapses toSearchResult.not_foundat the root);MCTSchecks it at the top of its iteration loop and returns best-so-far from the partial tree.
Algorithms that use the watchdog backstop¶
These have recursive shape that doesn't compose cleanly with a top-of-loop
check — they rely on the 2s SIGVTALRM watchdog grace:
IDAstar(iterative deepening, recursive)Backtracking,ForwardChecking,MinConflicts(CSP recursion)
Using the token directly¶
The token is a primitive — you can set it from outside the solver if you need to cancel a running search from another thread or signal handler:
import threading
from pathos import Space
space = Space().initial(...).timeout(60)
# ... configure decorators ...
def watch_external_condition():
while running:
if user_pressed_stop():
space._request_cancel()
return
threading.Thread(target=watch_external_condition, daemon=True).start()
result = space.solver().solve()
# If user_pressed_stop fired, result has the best incumbent so far.
Single-threaded by default
The SIGALRM/SIGVTALRM machinery only works on the main thread on
Unix. Multi-threaded use of space._request_cancel() from a worker
thread is fine, but the timer itself can only be set from main.
API reference¶
CancelToken
¶
Cooperative cancellation signal.
Set by the Solver when the wall-clock deadline approaches; read by Algorithm.solve() loops at well-defined check points. Algorithms that observe a set token are expected to return their best-so-far cleanly (not raise).