Skip to content

Poll timeout/interrupt/memory-limit inside dive loops; fix pulling should_stop - #27

Merged
legraina merged 2 commits into
mainfrom
fix/dive-loop-stop-polling
Aug 3, 2026
Merged

Poll timeout/interrupt/memory-limit inside dive loops; fix pulling should_stop#27
legraina merged 2 commits into
mainfrom
fix/dive-loop-stop-polling

Conversation

@legraina

@legraina legraina commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Two fixes for pathological solves that can wedge a caller for minutes inside a single solve() call.

1. Poll should_stop() inside dive loops (7ca2273)

A dive is a DFS with backtracking: on pathological instances a single dive can enumerate exponentially many partial paths, while all per-iteration should_stop() checks sit between dives — so timeout_s and the external interrupt never fire. The dive loops in GreedyAlgorithm, TabuSearchAlgorithm, and ImprovingTabuSearch now poll should_stop() every 4096 steps.

Also fixes PullingDominanceAlgorithm::main_loop, which historically only checked the iteration budget: it now uses should_stop(i), which covers the budget plus timeout_s and the external interrupt, like the base DominanceAlgorithm.

2. Poll the memory limit inside dive loops (b5665b5)

Each dive extension allocates a label, so a pathological dive is an RSS runaway as well as a time sink — and only the dominance main loops consulted MemoryLimitHelper. The same 4096-step poll now also checks memory_limit_.is_exceeded(). The memory check stays out of should_stop() itself on purpose: reading process RSS is a real syscall, and should_stop() runs every iteration of the hot dominance loops (which sample memory on their own throttled memory_check_interval cadence).

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings August 3, 2026 15:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens several RCSPP algorithms against pathological “dive” behavior by adding periodic stop-condition polling inside deep inner loops, and fixes PullingDominanceAlgorithm to honor the same stop logic as the base dominance loop.

Changes:

  • Add periodic polling (every 4096 steps) for timeout / external interrupt / memory-limit during DFS-style dive loops in tabu-search variants and greedy backtracking.
  • Update PullingDominanceAlgorithm::main_loop() to use should_stop(i) instead of only checking the iteration budget.
  • Document the pathological long-run / OOM scenarios these guards address (ROADEF setB-01).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
cpp/rcspp/algorithm/tabu_search.hpp Adds periodic timeout/interrupt/memory polling inside dive_to_sink() to avoid wedging in a single dive.
cpp/rcspp/algorithm/pulling_dominance_algorithm.hpp Fixes override loop to use should_stop(i) so timeout/interrupt are respected.
cpp/rcspp/algorithm/improving_tabu_search.hpp Adds the same periodic polling inside dive_to_sink() for the improving tabu search.
cpp/rcspp/algorithm/greedy.hpp Adds periodic polling inside greedy backtracking/extension to avoid long uninterruptible runs.
Suppressed comments (1)

cpp/rcspp/algorithm/greedy.hpp:109

  • The 4096-step poll is currently only evaluated once per outer loop iteration, but the inner while (extend_label(...)) can perform an arbitrarily long greedy “dive” (many label allocations / extensions) without ever re-checking timeout/interrupt/memory. To actually make greedy dives interruptible, poll per extension attempt (e.g., inside the inner loop or by incrementing steps for each call to extend_label()).
                bool extended = false;
                while (extend_label(path_.back().first)) {
                    extended = true;  // successfully extended
                }
                if (extended) {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +98 to +104
size_t steps = 0;
while (!path_.empty()) {
if ((++steps & 0xFFFU) == 0 &&
(this->is_time_out() || this->is_interrupted() ||
this->memory_limit_.is_exceeded())) {
return;
}
Comment on lines 153 to +158
while (!this->path_.empty()) {
if ((++steps & 0xFFFU) == 0 &&
(this->is_time_out() || this->is_interrupted() ||
this->memory_limit_.is_exceeded())) {
return false;
}
Comment on lines +223 to +229
size_t steps = 0;
while (!this->path_.empty()) {
if ((++steps & 0xFFFU) == 0 &&
(this->is_time_out() || this->is_interrupted() ||
this->memory_limit_.is_exceeded())) {
return false;
}
@legraina
legraina force-pushed the fix/dive-loop-stop-polling branch from 7bc2997 to 741c4de Compare August 3, 2026 15:44
legraina and others added 2 commits August 3, 2026 22:55
dive_to_sink() in the tabu searches (and greedy extend) is a DFS with
backtracking; on pathological instances a single dive can enumerate
exponentially many partial paths while all per-iteration should_stop()
checks sit between dives, so timeout_s and the external interrupt never
fire and one solve can run unbounded.

- greedy.hpp, tabu_search.hpp, improving_tabu_search.hpp: poll
  should_stop() every 4096 dive steps.
- pulling_dominance_algorithm.hpp: main_loop now uses should_stop(i)
  (timeout + interrupt + budgets) like the base DominanceAlgorithm,
  instead of only the iteration budget.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each dive extension allocates a label, so a pathological dive is an RSS
runaway as well as a time sink, and only the dominance main loops
consulted MemoryLimitHelper. The every-4096-steps dive poll now also
checks is_exceeded().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@legraina
legraina force-pushed the fix/dive-loop-stop-polling branch from 741c4de to b5665b5 Compare August 3, 2026 20:57
@legraina
legraina merged commit fdc41b2 into main Aug 3, 2026
2 checks passed
@legraina
legraina deleted the fix/dive-loop-stop-polling branch August 3, 2026 21:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants