Repository navigation
Conversation
…f the main loop On 2026-10-19 two nodes were shut down at 15:42:46 and the cluster read ACTIVE until 15:49:47, seven minutes with reduced redundancy. shutdown_storage_node flips the node to OFFLINE itself, so the monitor's set_node_offline (the path that refreshes the cluster status) never runs for it. The only remaining refresh is the monitor main loop, and that loop also ran the retention housekeeping, which read the whole events table and cleared one key per expired event. The cluster had just been upgraded from R26.2-PRE, which never pruned events, so the first sweeps deleted months of them while the status went unrefreshed. - shutdown_storage_node refreshes the cluster status once the node is OFFLINE (best effort; a failure there does not fail the shutdown). - Housekeeping runs on its own thread; the main loop only refreshes the status. - _delete_old_logs clears the cluster's expired events with one range clear over <cluster_uuid>/ .. <cluster_uuid>/<cutoff ms>, instead of a full-table read and per-event clears. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… leaking a mock monitor Review of the shutdown refresh. update_cluster_status is the monitor's full pass: requeue and re-admit sweeps, rebalancing flags, shared-placement and write-protection switches, and on a SUSPENDED cluster cluster_activate. A CLI `sn shutdown` must not start an activation or wait on one. The shutdown now calls apply_node_loss_status, which applies only the downgrades a node loss can cause (ACTIVE -> DEGRADED/SUSPENDED, DEGRADED -> SUSPENDED) with the same set_cluster_status the monitor makes for them. CI failure: test_secondary_promotion assigned a MagicMock to sys.modules["simplyblock_core.services.storage_node_monitor"] and never restored it. On Python 3.9 mock.patch resolves its target through package attributes, so the shutdown test patched the real module while the lazy import in shutdown_storage_node got the leaked mock, and the call went nowhere. 3.11+ resolves through sys.modules, which is why main passed. The test now uses patch.dict(sys.modules, ...), which restores the module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wmousa
force-pushed
the
fix/monitor-status-latency
branch
from
October 19, 2026 16:13
3f51afb to
8b7cab4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Main port of #1503 (R26.3). See that PR for the full write-up.
Problem
On 2026-10-19 a customer cluster, freshly upgraded from R26.2-PRE, had two nodes shut down at 15:42:46. It stayed ACTIVE until 15:49:47: seven minutes with reduced redundancy and no status change.
shutdown_storage_nodesets the node to OFFLINE itself, so the monitor'sset_node_offlinenever runs for it. That is the only per-node path that callsupdate_cluster_status, so the status waits for the monitor's main-loop tick.Change
shutdown_storage_node: once the node is OFFLINE, calls the newapply_node_loss_status(cluster_id)(monitor module). It applies only the downgrades a node loss can cause: ACTIVE → DEGRADED/SUSPENDED and DEGRADED → SUSPENDED. It uses the sameset_cluster_statusthe monitor makes for those transitions. It deliberately does not runupdate_cluster_status, the monitor's full pass, because on a SUSPENDED cluster that pass callscluster_activate, and a CLI shutdown must not start an activation or wait on one. Recovery transitions stay with the monitor. This is best effort: a failure is logged with a traceback and the shutdown still succeeds._start_housekeeping()inmain(). The main loop only refreshes the status._delete_old_logs(cluster_id): removes one cluster's expired events with a singleclear_range(<cluster>/, <cluster>/<cutoff ms>). Event keys are<cluster_uuid>/<date ms>/<uuid>, so those events form one contiguous key range.Differences from #1503
logger.exception, so that hunk keeps main's version.test_monitor_thread_supervision.py: those tests count every threadmain()creates. They now patch_start_housekeepinginstead of_run_periodic_housekeeping, so their per-node thread counts are unchanged._housekeeping_loop.Integration CI failure (fixed)
test_cluster_status_is_refreshed_once_offlinefailed on R26.3 (py3.9) with 0 refreshes recorded.test_secondary_promotion.py::test_escalates_unreachable_primaryassigned aMagicMocktosys.modules["simplyblock_core.services.storage_node_monitor"]and never restored it.mock.patchresolves its target through package attributes, so the test patched the real module while the lazy import inshutdown_storage_nodegot the leaked mock. On 3.11+,patchresolves throughsys.modules, which is why main passed.patch.dict(sys.modules, ...), which restores the real module.Tests
ruff: clean.mypy: 2 errors, both inspdk_http_proxy_server.py(asyncio.open_unix_connectionon Windows). This PR doesn't touch that file.spdk_proxy_unit,lblk_persistent_names, …).test_shutdown_no_suspension.pylocally with three extra FDB reads stubbed (get_active_lvol_migration,cancel_pending_node_restart_tasks,cancel_node_tasks): 17/17 pass. CI covers this tier.🤖 Generated with Claude Code