spacr.qt.resource_cleanup¶
Free what spaCR owns, and nothing else.
Four buttons in Preferences — clear RAM, clear VRAM, clear CPU, check disk space — and the two performance modes that press them for you. This module is what they call.
The rule, and it is a refusal rather than a preference¶
“Free as many resources as possible” must never reach anything spaCR does not own. This machine runs other people’s work: a segmentation that starts four seconds sooner is not worth somebody’s eight-hour training run, and a tool that decides otherwise on the user’s behalf is not a tool anybody can leave running.
So, concretely, and asserted in tests/qt/test_resource_cleanup.py by
reading this source file:
No process is ever killed. Not by name, not by “python processes using a lot of memory”, not spaCR’s own children. There is no
os.kill, nosignal, nosubprocess, noProcess.terminate(), noQThread.terminate()anywhere in this module or reachable from it. The Qt layer removedQThread.terminate()outright; a stubborn thread is parked byspacr.qt.bridge.drain_thread(), and that is the strongest thing any of this may do.Nothing here needs root, and nothing here touches the operating system’s own memory. No
drop_caches, nosysctl, noswapoff. The page cache belongs to the kernel and dropping it would slow down the whole machine, spaCR included — it is not a free win, it is a transfer from everybody to nobody.A run in flight is never disturbed. No queued job is dropped, no worker is cancelled, no thread pool is emptied of work that has not started yet.
spacr.qt.bridge.registry()is read here and never cancelled.
Reporting¶
Every number is measured before and after by the same function, and
Reclaim carries both endpoints so a caller can show the subtraction
rather than an estimate. A cleanup that freed nothing reports that it freed
nothing; there is no reassuring dialog. See Reclaim.summary().
Classes¶
Observable result of applying the user's live-cache policy. |
|
One filesystem, and what it is holding. |
|
Every drive the current project touches. Read-only, always. |
|
The measured result of one cleanup. Not the intent — the outcome. |
Functions¶
|
Retire spaCR's own idle workers and lower its thread counts. |
|
Drop spaCR's own caches, measured by RSS before and after. |
|
Return torch's reserved-but-unused CUDA blocks to the driver. |
|
What |
|
The title of |
|
Bytes the CUDA caching allocator holds for this process, or |
|
Free space on every drive the project touches. Reads; frees nothing. |
|
|
|
Install the bounded periodic policy sweep on the live QApplication. |
|
Connect the pre-run cleanup to the run registry. Idempotent. |
|
This process's resident set size in bytes, or |
|
Folders the current project actually touches, most relevant first. |
|
Entry point for |
|
The cleanup the mode asks for at launch. |
|
The cleanup Extra Performance runs immediately before a module run. |
|
One paragraph saying what |
|
Apply idle age, one global byte ceiling, and the free-memory floor. |
Module Contents¶
- class spacr.qt.resource_cleanup.BudgetSweep[source]¶
Observable result of applying the user’s live-cache policy.
before_mb/after_mbare sums of the cache owners’ measured entry sizes, not process RSS. That makes the accounting stable and attributable: shared pages and Python’s allocator cannot make an evicted entry appear to have grown. RSS remains the measurement reported byReclaimfor the explicit Clear RAM action.
- class spacr.qt.resource_cleanup.DiskEntry[source]¶
One filesystem, and what it is holding.
- Parameters:
path – the folder that was measured; it stands for the whole filesystem it lives on.
total – the filesystem’s size, in bytes.
used – bytes in use.
free – bytes free.
- class spacr.qt.resource_cleanup.DiskReport[source]¶
Every drive the current project touches. Read-only, always.
- class spacr.qt.resource_cleanup.Reclaim[source]¶
The measured result of one cleanup. Not the intent — the outcome.
- Parameters:
action – one of
ACTIONS.before – the measurement taken before anything ran, in bytes.
after – the same measurement, taken after.
details – what was actually done, one short phrase each.
note – the honest caveat, if there is one. A cleanup that could do nothing in the current state says so here.
measured –
Falsewhen there was no measurement to take at all (no CUDA device, no way to read RSS), which is different from a measurement that came back zero.
- spacr.qt.resource_cleanup.clear_cpu(*, target_threads: int | None = None) Reclaim[source]¶
Retire spaCR’s own idle workers and lower its thread counts.
Reads
spacr.qt.bridge.registry()to say what is still running; it never cancels it. A parked thread — one that would not stop when its owner went away — is released here only once it has actually exited;spacr.qt.bridge.prune_parked_threads()is the whole mechanism and it waits rather than terminating, becauseQThread.terminate()on a thread running Python leaves either a held GIL or a corrupt heap.
- spacr.qt.resource_cleanup.clear_ram(*, aggressive: bool = False) Reclaim[source]¶
Drop spaCR’s own caches, measured by RSS before and after.
- Parameters:
aggressive – also drop the caches that are expensive to rebuild (thumbnails, icon pixmaps). The mild form keeps them, because a cleanup that costs the next screen a second of redrawing is not a cleanup, it is a stutter with good intentions.
- Returns:
a
Reclaim.freedisbefore - after, fromprocess_rss()— not the size of what was dropped, which would be a guess about an allocator nobody here controls.
- spacr.qt.resource_cleanup.clear_vram(*, release_models: bool = True) Reclaim[source]¶
Return torch’s reserved-but-unused CUDA blocks to the driver.
- Parameters:
release_models – also run
MODEL_RELEASERS. SetFalseimmediately before a run — seerun_pre_run_cleanup()for why releasing a model spaCR is about to reload is a slowdown wearing an optimisation’s clothes.
This cannot reclaim another process’s VRAM. Nothing can: CUDA memory belongs to the context that allocated it, and the only way to take it back would be to kill that process, which this module does not do to anybody. Nor does it touch tensors a running job still holds —
empty_cache()frees blocks the allocator is caching, never live ones, which is exactly why it is safe to call while work is in flight.
- spacr.qt.resource_cleanup.confirmation_text(action: str) str[source]¶
What
actionwill actually do, in words, before it does it.The long form, for the confirmation the user is asked to agree to. A bulleted list is right there: they are about to authorise it, and the bullets are what they are authorising.
- Parameters:
action – one of
ACTIONS; any other value raisesKeyError.
- spacr.qt.resource_cleanup.confirmation_title(action: str) str[source]¶
The title of
action’s confirmation dialog.- Parameters:
action – one of
ACTIONS; any other value raisesKeyError.
- spacr.qt.resource_cleanup.cuda_reserved() int | None[source]¶
Bytes the CUDA caching allocator holds for this process, or
None.Nonefor “there is nothing to ask”: no torch, no CUDA build, no device, or a CUDA context this process has never initialised. Asking would create that context — several hundred MB of VRAM — which is a strange thing for a button called “clear VRAM” to do.
- spacr.qt.resource_cleanup.disk_report(paths: Sequence[str] | None = None) DiskReport[source]¶
Free space on every drive the project touches. Reads; frees nothing.
Deduplicated by device id, so a project folder and a home directory on the same disk are one line rather than two identical ones — that duplication is what makes a disk readout stop being read.
RUN THIS ON A WORKER.
os.statandshutil.disk_usageare the calls that wake a sleeping automount, and no cache can answer them without inventing the numbers, so the Qt caller hands this whole function to aJobRunner(spacr.qt.preferences._start_disk_report()). There the wait is unbounded on purpose: a worker is allowed to wait for a mount to wake up, and the drive table is complete.CALLED ON THE GUI THREAD ANYWAY, it will not freeze it. The whole reading is then bounded by
_GUI_DISK_BUDGET_S, and a folder that misses the budget is counted in the note exactly like one that could not be read — because within the time the interface had, it could not be. That is a line missing from a report, against an application that has stopped.
- spacr.qt.resource_cleanup.human_bytes(count: int) str[source]¶
1536->"1.5 KB". Two significant places, never a fake one.
- spacr.qt.resource_cleanup.install_budget_sweep() bool[source]¶
Install the bounded periodic policy sweep on the live QApplication.
- spacr.qt.resource_cleanup.install_run_hook() bool[source]¶
Connect the pre-run cleanup to the run registry. Idempotent.
Nothing in
bridge.pyknows this exists: the registry already emits when a job is registered, and a mode that is not Extra Performance turns the slot into a dictionary lookup and a return.
- spacr.qt.resource_cleanup.process_rss() int[source]¶
This process’s resident set size in bytes, or
0if unknowable.psutilwhen it is installed,/proc/self/statmwhen it is not. Zero means “could not measure”, and a cleanup that could not measure says so rather than reporting a freed figure it made up.
- spacr.qt.resource_cleanup.project_paths() List[str][source]¶
Folders the current project actually touches, most relevant first.
The source folders the user last pointed each module at, plus the two places spaCR writes regardless of where the data lives: the home directory (settings, logs, model downloads) and the temp directory. Only paths that exist are returned.
Called on a worker this is exact. Called on the GUI thread it answers from cache for the remembered folders rather than stat-ing them; see
_is_a_folder()for the twenty seconds that bought.
- spacr.qt.resource_cleanup.register() bool[source]¶
Entry point for
spacr.qt.SELF_REGISTERING_MODULES.Installs the pre-run hook and performs the launch cleanup the mode asks for — once per process, so the test suite calling the launch sequence forty times does not collect forty times.
- spacr.qt.resource_cleanup.run_launch_cleanup() List[Reclaim][source]¶
The cleanup the mode asks for at launch.
Extra Performance — everything, models included: nothing is running yet, so nothing can be taken out from under a job.
Performance — RAM and VRAM, gently.
Balanced — nothing at all. Returns
[]without measuring anything, because a “cleanup” that measures is still a pause.
- spacr.qt.resource_cleanup.run_pre_run_cleanup(app_key: str = '') List[Reclaim][source]¶
The cleanup Extra Performance runs immediately before a module run.
Only Extra Performance does this, and it is deliberately not the same cleanup as at launch:
release_models=False. Releasing a model the run is about to reload is a slowdown dressed as an optimisation — the reclaim is temporary, the reload is seconds of disk and PCIe, and the peak memory is the same either way.empty_cache()still runs, because returning reserved but unused blocks is exactly what a run about to allocate wants.It does not run at all while another run is in flight. The caches this drops are the ones a running job is reading, and a cleanup that competes with the work it is supposed to be helping is worse than no cleanup.
It does not touch the CPU: lowering thread counts a moment before a run that wants those threads would slow down the very run it precedes.
- spacr.qt.resource_cleanup.summary_text(action: str) str[source]¶
One paragraph saying what
actiondoes.- Parameters:
action –
'ram','vram','cpu'or'disk'.- Returns:
the short form for a hover, falling back to the long form so a new action is never left with no help at all.
- spacr.qt.resource_cleanup.sweep_memory_budget(*, now: float | None = None, idle_minutes: float | None = None, ceiling_mb: float | None = None, headroom_short: bool | None = None, max_entries: int = BUDGET_SWEEP_MAX_ENTRIES, owners=None) BudgetSweep[source]¶
Apply idle age, one global byte ceiling, and the free-memory floor.
- Parameters:
now – epoch seconds; explicit so a controlled-clock test can drive real cache entries.
idle_minutes – override for tests; otherwise the live preference.
ceiling_mb – global RAM-cache ceiling; otherwise the preference.
headroom_short – controlled pressure state for tests. When omitted,
spacr.qt.memory_budget.headroom_is_short()is measured before and after each pressure eviction, stopping as soon as the floor is restored.max_entries – hard bound on evictions in this call.
owners – optional owner sequence for an isolated test; production discovers all already-loaded registered caches.
- Returns:
measured, attributable accounting for the pass.
In-use entries are excluded before either policy is evaluated. Their bytes still count against the one process-wide ceiling, so a pinned 100 MB Figure leaves 100 MB less room for evictable thumbnails; applying a full ceiling independently to every cache would multiply the user’s setting by the number of open screens.
Nested helpers¶
- _collect_budget_entries._drop(dropper=dropper, key=key)¶
Drop this entry’s cached value. Key bound as a default argument.
Bound rather than closed over: closing over the loop variable would give every entry the LAST key, so one eviction would drop the wrong thing and report success.
spacr/qt/resource_cleanup.py:507
- _readings_within_the_budget.measure(path: str) None¶
Read one folder on this helper thread and record the answer.
- Parameters:
path – the folder to measure. Nothing is returned: the answer goes into
answers, which the waiting thread reads.
spacr/qt/resource_cleanup.py:1267
- project_paths._add(value, *, remembered: bool = True) None¶
Add one path, ignoring blanks and duplicates.
rememberedis what makes a path dangerous: it means the user chose it and it can therefore be on a sleeping mount. The home and temp directories are not remembered – spaCR reads them from the environment and every start-up has already stat-ed them – so they keep the direct check and are never missing from a first report.spacr/qt/resource_cleanup.py:1175