perf: periodic full GC to bound RSS (plexapi cyclic Element trees) (#802)
measured the 'resource hungry' / lockup issue: browsing every page grows RSS ~300MB -> 1.8GB and it stays. it's not a leak — it's deferred cyclic collection. plexapi parses Plex responses into XML Element trees whose nodes reference each other in cycles; Python's generational GC leaves them in gen2 and sweeps it rarely, so ~227k Element objects pile up. forcing gc.collect() reclaimed ~700MB instantly (1.8GB -> 1.1GB live), confirming. add a daemon that runs a full gc.collect() every 60s so the cyclic garbage is reclaimed on a cadence instead of climbing into lock-up. full collect is ~tens of ms; once a minute is negligible. this is the root of the reporter's 2GB + ramonskie's spike too.
This commit is contained in:
parent
de9e78cf95
commit
f383a62c4f
1 changed files with 20 additions and 0 deletions
|
|
@ -38549,6 +38549,26 @@ def start_runtime_services():
|
|||
# Initialize app start time for uptime tracking
|
||||
app.start_time = time.time()
|
||||
|
||||
# Periodic full garbage collection (#802 / resource usage). plexapi builds large XML
|
||||
# Element trees whose nodes reference each other in cycles; Python's generational GC
|
||||
# parks those in gen2 and only sweeps it rarely, so they accumulate and RSS climbs
|
||||
# (measured: ~300MB fresh -> 1.8GB after browsing every page, ~700MB of it reclaimable
|
||||
# cyclic garbage that a full gc.collect() frees instantly). A scheduled full collect
|
||||
# reclaims them on a cadence so RSS stays bounded instead of climbing into lock-up.
|
||||
# A full collect on this heap is ~tens of ms; once a minute is negligible CPU.
|
||||
def _periodic_gc(interval=60):
|
||||
import gc
|
||||
while True:
|
||||
time.sleep(interval)
|
||||
try:
|
||||
n = gc.collect()
|
||||
if n:
|
||||
logger.debug("periodic gc.collect() reclaimed %d cyclic objects", n)
|
||||
except Exception: # noqa: S110 — best-effort housekeeping, never crash the app
|
||||
pass
|
||||
threading.Thread(target=_periodic_gc, daemon=True, name='periodic-gc').start()
|
||||
logger.info("Periodic GC sweeper started (full collect every 60s)")
|
||||
|
||||
# Register action handlers and start automation engine
|
||||
_register_automation_handlers()
|
||||
if automation_engine:
|
||||
|
|
|
|||
Loading…
Reference in a new issue