Skip to content

Fix list modification during iteration in wait_for_threads_to_finish#1498

Open
juandiego-bmu wants to merge 3 commits intoOWASP:masterfrom
juandiego-bmu:fix-unsafe-list-iteration
Open

Fix list modification during iteration in wait_for_threads_to_finish#1498
juandiego-bmu wants to merge 3 commits intoOWASP:masterfrom
juandiego-bmu:fix-unsafe-list-iteration

Conversation

@juandiego-bmu
Copy link
Copy Markdown

Proposed change

Replace threads.remove(thread) inside for thread in threads loop with a list comprehension slice assignment in wait_for_threads_to_finish() (nettacker/core/utils/common.py).

Removing elements during iteration shifts the iterator and can skip threads that just finished. Using threads[:] = [t for t in threads if t.is_alive()] rebuilds the list safely.

Linked issue: #1465

Type of change

  • Bugfix (non-breaking change which fixes an issue)

Checklist

  • I've followed the contributing guidelines
  • I have digitally signed all my commits in this PR
  • I've run make pre-commit and confirm it didn't generate any warnings/changes
  • I've run make test, I confirm all tests passed locally
  • I've added/updated any relevant documentation in the docs/ folder
  • I've linked this PR with an open issue
  • I've tested and verified that my code works as intended and resolves the issue as described
  • I have attached screenshots demonstrating my code works as intended
  • I've checked all other open PRs to avoid submitting duplicate work
  • I confirm that the code and comments in this PR are not direct unreviewed outputs of AI
  • I confirm that I am the Sole Responsible Author for every line of code, comment, and design decision

Recreated from #1471 with signed commits and proper template as requested by @securestep9.

Replace threads.remove(thread) inside for loop with a list
comprehension slice assignment. Removing elements during iteration
shifts the iterator and can skip threads that just finished.

Fixes OWASP#1465
Copilot AI review requested due to automatic review settings April 3, 2026 14:52
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Improved thread/process waiting to avoid unsafe in-place mutation and ensure consistent removal of completed workers.
  • Tests

    • Added tests verifying completed threads are removed in-place, early exit behavior with a maximum wait, and that the function returns success when all workers finish.

Walkthrough

Replaced in-loop removal of non-alive threads with an atomic filtered reassignment in wait_for_threads_to_finish, added a detailed docstring, and introduced four unit tests that validate early exit, in-place list clearing, and removal of dead threads.

Changes

Cohort / File(s) Summary
Thread management & docs
nettacker/core/utils/common.py
Rewrote thread-list cleanup to threads[:] = [t for t in threads if t.is_alive()] (avoids mutating list during iteration); added an expanded docstring describing behavior, maximum semantics, and KeyboardInterrupt handling.
Tests for wait_for_threads_to_finish
tests/core/utils/test_common.py
Added four unit tests (using threading, time, unittest.mock.MagicMock) that assert immediate return when all threads are dead, complete removal of dead threads, early exit with maximum, and in-place clearing of the input list as threads finish.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing unsafe list modification during iteration in wait_for_threads_to_finish.
Description check ✅ Passed The description clearly explains the problem (removing elements during iteration skips threads) and the solution (using slice assignment), directly relating to the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a concurrency bug in wait_for_threads_to_finish() where removing elements from the threads list during iteration could skip completed threads, causing delayed cleanup and inaccurate maximum checks.

Changes:

  • Replace in-loop threads.remove(thread) with a safe in-place list rebuild (threads[:] = [...]) to avoid iterator skipping.

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

Comment on lines 58 to 62
def wait_for_threads_to_finish(threads, maximum=None, terminable=False, sub_process=False):
while threads:
try:
for thread in threads:
if not thread.is_alive():
threads.remove(thread)
threads[:] = [t for t in threads if t.is_alive()]
if maximum and len(threads) < maximum:
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The bugfix changes thread cleanup semantics; there’s currently no regression test coverage for wait_for_threads_to_finish, even though tests/core/utils/test_common.py exists for other common.py utilities. Please add a unit test that exercises the skipped-element scenario (e.g., multiple completed threads adjacent in the list) and validates maximum behavior (breaking once len(threads) < maximum).

Copilot uses AI. Check for mistakes.
@securestep9 securestep9 added the bug label Apr 9, 2026
@securestep9
Copy link
Copy Markdown
Collaborator

@juandiego-bmu can you please add a unit test (see Copilot comments)

Address review feedback: add docstring and unit tests covering
dead-thread cleanup, the maximum parameter, and in-place list
mutation -- confirming the fix prevents skipping elements.
@juandiego-bmu
Copy link
Copy Markdown
Author

Done! Pushed a commit with:

  • Docstring for wait_for_threads_to_finish documenting all parameters and return values
  • 4 unit tests covering: already-dead threads, removal of all dead threads without skipping (the core bug), early exit with maximum parameter, and in-place list mutation after real threads finish

All tests pass locally.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/core/utils/test_common.py (1)

118-132: maximum test should assert early-exit semantics explicitly.

Right now it only checks True, so it can still pass if the function waits for full completion.

🔧 Suggested test tightening
 def test_wait_for_threads_to_finish_with_maximum():
     """Should break early when thread count drops below maximum."""
-
-    def short_task():
-        time.sleep(0.02)
-
-    threads = [threading.Thread(target=short_task) for _ in range(3)]
+    release = threading.Event()
+
+    def short_task():
+        time.sleep(0.01)
+
+    def blocking_task():
+        release.wait(timeout=1)
+
+    threads = [
+        threading.Thread(target=short_task),
+        threading.Thread(target=short_task),
+        threading.Thread(target=blocking_task),
+        threading.Thread(target=blocking_task),
+    ]
     for t in threads:
         t.start()
     result = common_utils.wait_for_threads_to_finish(threads, maximum=3)
     assert result is True
+    assert len(threads) == 2
+    assert all(t.is_alive() for t in threads)
     # Clean up
+    release.set()
     for t in threads:
         t.join(timeout=1)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/core/utils/test_common.py` around lines 118 - 132, The test
test_wait_for_threads_to_finish_with_maximum currently only asserts True, which
can pass even if the helper waited for all threads; modify it to assert
early-exit by making short_task take longer (e.g., time.sleep(0.2)), call
common_utils.wait_for_threads_to_finish(threads, maximum=3, timeout=0.1) (or use
a small timeout/poll) and then assert result is True AND at least one thread in
threads is still alive (e.g., any(t.is_alive() for t in threads)), then perform
cleanup joins; reference symbols: test_wait_for_threads_to_finish_with_maximum,
short_task, threads, common_utils.wait_for_threads_to_finish.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/core/utils/test_common.py`:
- Around line 118-132: The test test_wait_for_threads_to_finish_with_maximum
currently only asserts True, which can pass even if the helper waited for all
threads; modify it to assert early-exit by making short_task take longer (e.g.,
time.sleep(0.2)), call common_utils.wait_for_threads_to_finish(threads,
maximum=3, timeout=0.1) (or use a small timeout/poll) and then assert result is
True AND at least one thread in threads is still alive (e.g., any(t.is_alive()
for t in threads)), then perform cleanup joins; reference symbols:
test_wait_for_threads_to_finish_with_maximum, short_task, threads,
common_utils.wait_for_threads_to_finish.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3ec2cdca-bd6e-4312-b598-42d431965955

📥 Commits

Reviewing files that changed from the base of the PR and between a98e261 and f23836c.

📒 Files selected for processing (2)
  • nettacker/core/utils/common.py
  • tests/core/utils/test_common.py

Copy link
Copy Markdown
Contributor

@pUrGe12 pUrGe12 left a comment

Choose a reason for hiding this comment

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

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants