import pytest """Tests for reclaiming the virtiofs mount's file handles. These guard a mechanism that exists only because of an environment defect: the mount's daemon holds a host descriptor per inode the guest looks up, and runs out. The properties that matter are that reclaim is *actually verified* rather than assumed, and that the expensive fallback is used only when it must be. """ from suvi import vfs def test_drop_caches_reports_failure_without_root(monkeypatch): """Must return False rather than raise, so the fallback can run.""" def refuse(*args, **kwargs): raise PermissionError(13, "Permission denied") monkeypatch.setattr("builtins.open", refuse) assert vfs.drop_caches() is False def test_reclaimable_kb_reads_the_slab(): assert vfs.reclaimable_kb() > 0 # real /proc/meminfo def test_reclaimable_kb_survives_a_broken_meminfo(monkeypatch): monkeypatch.setattr("builtins.open", lambda *a, **k: (_ for _ in ()).throw(OSError())) assert vfs.reclaimable_kb() == 0 def test_release_prefers_drop_caches(monkeypatch): """The fallback allocates tens of GiB; it must not run when drop_caches works.""" monkeypatch.setattr(vfs, "drop_caches", lambda: True) def explode(*args, **kwargs): raise AssertionError("allocated memory despite drop_caches succeeding") monkeypatch.setattr(vfs, "bytearray", explode, raising=False) assert vfs.release_handles() is True def test_fallback_reports_failure_when_nothing_is_reclaimed(monkeypatch): monkeypatch.setattr(vfs, "drop_caches", lambda: False) monkeypatch.setattr(vfs, "reclaimable_kb", lambda: 1000) assert vfs.release_handles(budget_gib=1, floor_kb=0) is False def test_release_gives_up_when_the_slab_cannot_be_read(monkeypatch): monkeypatch.setattr(vfs, "drop_caches", lambda: False) monkeypatch.setattr(vfs, "reclaimable_kb", lambda: 0) assert vfs.release_handles() is False # --------------------------------------------------------------- memory bounding def test_available_gib_reads_meminfo(): assert vfs.available_gib() > 0 def test_pressure_stops_at_the_reserve_not_a_fraction(monkeypatch): """Capping at a fraction of available memory reclaims nothing. The kernel only frees slab under genuine pressure, so leaving 40% headroom means it never triggers: an earlier version allocated 14 GiB of a 23 GiB allowance and freed zero, costing the machine the memory for no benefit. Pressure must run down to a fixed reserve instead. """ monkeypatch.setattr(vfs, "drop_caches", lambda: False) monkeypatch.setattr(vfs, "reclaimable_kb", lambda: 1000) # never yields available = [20.0] monkeypatch.setattr(vfs, "available_gib", lambda: available[0]) allocated = [] real = bytearray def fake(n): allocated.append(n) available[0] -= 1.0 # each GiB claimed reduces what is available return real(1) monkeypatch.setattr(vfs, "bytearray", fake, raising=False) assert vfs.release_handles(reserve_gib=2.0, floor_kb=0) is False # Pushed from 20 GiB down to the 2 GiB reserve, not stopped at 60%. assert 17 <= len(allocated) <= 19, f"allocated {len(allocated)} GiB" def test_pressure_stops_as_soon_as_the_slab_yields(monkeypatch): monkeypatch.setattr(vfs, "drop_caches", lambda: False) readings = iter([1000, 1000, 1000, 400]) monkeypatch.setattr(vfs, "reclaimable_kb", lambda: next(readings)) monkeypatch.setattr(vfs, "available_gib", lambda: 20.0) allocated = [] real = bytearray monkeypatch.setattr( vfs, "bytearray", lambda n: allocated.append(n) or real(1), raising=False ) assert vfs.release_handles(floor_kb=0) is True assert len(allocated) == 2, "kept allocating after the kernel gave ground" # ------------------------------------------------------------------- Reliever def test_reliever_fires_on_the_interval(monkeypatch): calls = [] monkeypatch.setattr(vfs, "release_handles", lambda *a, **k: calls.append(1) or True) reliever = vfs.Reliever(interval=10) for _ in range(9): reliever.tick() assert calls == [] reliever.tick() assert len(calls) == 1 def test_reliever_keeps_firing_across_a_long_run(monkeypatch): calls = [] monkeypatch.setattr(vfs, "release_handles", lambda *a, **k: calls.append(1) or True) reliever = vfs.Reliever(interval=10) for _ in range(100): reliever.tick() assert len(calls) == 10, "stopped reclaiming partway through a long traversal" assert reliever.releases == 10 def test_reliever_counts_batched_items(monkeypatch): """Callers processing six bands per timestamp tick by six, not one.""" calls = [] monkeypatch.setattr(vfs, "release_handles", lambda *a, **k: calls.append(1) or True) reliever = vfs.Reliever(interval=10) reliever.tick(6) reliever.tick(6) assert len(calls) == 1 assert reliever.seen == 12 def test_reliever_can_be_disabled(monkeypatch): def explode(*a, **k): raise AssertionError("reclaimed while disabled") monkeypatch.setattr(vfs, "release_handles", explode) reliever = vfs.Reliever(interval=1, enabled=False) for _ in range(50): reliever.tick() assert reliever.releases == 0 def test_reliever_reclaims_when_the_run_ends(monkeypatch): """Reclaiming only during a run leaves the machine loaded once it finishes. The inodes a traversal cached stay pinned -- and with them the host's file handles -- until something else forces reclaim. That is why the system was still saturated with nothing of ours running. """ calls = [] monkeypatch.setattr(vfs, "release_handles", lambda *a, **k: calls.append(1) or True) reliever = vfs.Reliever(interval=1000, on_exit=False) reliever.tick(5) assert calls == [], "should not have reclaimed mid-run yet" reliever.finish() assert len(calls) == 1 def test_finish_is_idempotent(monkeypatch): calls = [] monkeypatch.setattr(vfs, "release_handles", lambda *a, **k: calls.append(1) or True) reliever = vfs.Reliever(interval=1000, on_exit=False) reliever.tick() reliever.finish() reliever.finish() reliever.finish() assert len(calls) == 1 def test_finish_does_nothing_if_no_work_happened(monkeypatch): def explode(*a, **k): raise AssertionError("reclaimed despite processing nothing") monkeypatch.setattr(vfs, "release_handles", explode) vfs.Reliever(on_exit=False).finish() def test_reliever_works_as_a_context_manager(monkeypatch): calls = [] monkeypatch.setattr(vfs, "release_handles", lambda *a, **k: calls.append(1) or True) with vfs.Reliever(interval=1000, on_exit=False) as reliever: reliever.tick(3) assert calls == [] assert len(calls) == 1 def test_context_manager_reclaims_even_when_the_body_raises(monkeypatch): calls = [] monkeypatch.setattr(vfs, "release_handles", lambda *a, **k: calls.append(1) or True) with pytest.raises(ValueError): with vfs.Reliever(interval=1000, on_exit=False) as reliever: reliever.tick() raise ValueError("boom") assert len(calls) == 1, "a failed job must still hand its handles back" def test_no_pressure_is_applied_when_the_slab_is_already_small(monkeypatch): """Below the floor the slab is live processes' working set, not archive inodes. Squeezing the machine to shave a few hundred megabytes off it costs far more than it gains -- and during a long job this runs every RELIEF_INTERVAL items. """ monkeypatch.setattr(vfs, "drop_caches", lambda: False) monkeypatch.setattr(vfs, "reclaimable_kb", lambda: 500 * 1024) # 0.5 GiB def explode(*a, **k): raise AssertionError("applied memory pressure below the floor") monkeypatch.setattr(vfs, "bytearray", explode, raising=False) assert vfs.release_handles() is True