Skip to content

Plug tag_map leak in job_not_running that flaked test_mem_leak_in_event_listener - #70153

Open
dwoz wants to merge 1 commit into
saltstack:3008.xfrom
dwoz:dwoz/fix/eventlistener-tag-map-leak
Open

Plug tag_map leak in job_not_running that flaked test_mem_leak_in_event_listener#70153
dwoz wants to merge 1 commit into
saltstack:3008.xfrom
dwoz:dwoz/fix/eventlistener-tag-map-leak

Conversation

@dwoz

@dwoz dwoz commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • saltnado._disbatch_local spawns job_not_running as an IOLoop.spawn_callback coroutine. When the outer job's is_finished future completes first, job_not_running resolves the in-flight ping future via event.set_result(None) and returns — but never removes the future from event_listener.tag_map / timeout_map.
  • Because spawn_callback runs independently of the handler, the ping may be registered after the handler's on_finish -> clean_by_request has already emptied request_map, so the leaked entry only clears when the gather_job_timeout callback fires (10 s default, 30 s under the netapi test fixture).
  • test_mem_leak_in_event_listener allows only a 1 s poll window before asserting the maps are empty, so any leaked ping trips AssertionError: assert 2 == 0 — the exact flake caught on the 3008.x nightly Amazon Linux 2 integration zeromq 4 (run 32913591224 job 98023096704, first attempt failed / retry passed) and reproduced on merge-forward PR Merge forward 3007.x into 3008.x #70132.
  • Fix: after set_result(None), cancel the tornado timeout callback and call _timeout_future(...) to drop the entry from tag_map, matching the cleanup that clean_by_request would have performed had the request still been tracked.

Fixes #70152

Test plan

  • tests/pytests/integration/netapi/rest_tornado/test_minions_api_handler.py::test_mem_leak_in_event_listener passes on Amazon Linux 2 integration zeromq 4 without needing pytest-salt-factories retry
  • Netapi rest_tornado integration split otherwise unchanged

saltnado._disbatch_local spawns job_not_running as an
IOLoop.spawn_callback coroutine to ping saltutil.find_job while
the outer job is in flight.  When the outer job's is_finished
future completes first, job_not_running resolves the in-flight
ping future via event.set_result(None) and returns.

set_result() alone does not remove the future from
event_listener.tag_map or timeout_map -- the cleanup path in
_handle_event_socket_recv only fires when a matching event
arrives on the socket, and clean_by_request only fires from the
handler's on_finish.  Because spawn_callback runs independently
of the handler, the ping future may be registered *after*
on_finish -> clean_by_request has already emptied request_map,
so the leaked entry only clears when the gather_job_timeout
callback fires (10s default, 30s under the netapi test fixture).

The intermittent AssertionError in
test_mem_leak_in_event_listener catches this leak whenever the
1s poll window closes before the timeout callback runs -- the
race is triggered by ordinary event-loop scheduling variance,
which explains why the test flakes even on 3008.x nightlies
that then pass on retry.

Fix: after set_result(None), explicitly cancel the timeout
callback and call _timeout_future(...) to drop the entry from
tag_map, matching the cleanup that clean_by_request would have
performed had the request still been tracked.

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

Labels

test:full Run the full test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant