Fix AI memory leaks #121
Reference in New Issue
Block a user
No description provided.
Delete Branch "fix/ai-leaks"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #120
Branch:
fix/ai-leaks→master· Closes:Fixes #120· Commits:5e329c77e,7469f31c7,f9e0ba963Coverage of issue #120
_load_identityinomemo.c:1434(not AI)_write_callbackvia_ai_request_threadconnection_create_stanza_idincmd_funcs.cai_session_create(worker never unref'd session)3 of 4 closed. Record 244 is OMEMO, shall belongs to a separate PR.
What's correct
auto_gchar) at cmd_funcs.c:8519 —g_strdup_printf-allocated string, scope-endg_freeis safe;cl_ev_send_ai_msguses the id only synchronously.local_provider_name/local_model/local_api_key→auto_gchar. Headers via_build_curl_headerscopy the bearer value, so releasing the local after that call is safe.response.data→g_steal_pointer(&response.data)intoauto_gchar response_databefore the if/else chain — all three branches now free identically.ai_session_unrefadded in both early-exits and at the end of_ai_request_thread;cmd_ai_startreleases its own ref afterwins_new_aibecausewin_create_ai(window.c:344) takes its own.Remaining items — all marked tolerable unless noted
Re-verified each against current HEAD; everything below is unchanged by this PR.
4.
_write_callbackreturnsrealsizeong_reallocfailure — tolerableai_client.c:66 — if
g_reallocreturns NULL the callback returnsrealsize, which (per libcurl semantics) says "all bytes written". curl would keep feeding more data into a buffer that didn't grow → silent data loss on OOM. Tolerable becauseg_reallocessentially never fails on modern systems before the 10 MB cap right above kicks in. Trivial fix though —return CURL_WRITEFUNC_ERROR;mirroring the cap path.5.
_load_identityGErrors leak — tolerable (this is record 244)omemo.c1434/1443/1451/1462 —g_set_errorallocates, but_load_identityreturns FALSE withoutg_clear_error(&error). Tolerable because it only fires when OMEMO identity load is already failing (broken keyfile), each leak is ~60 B, and it's outside AI scope. Belongs in a separate OMEMO PR.6. Detached worker threads at shutdown — tolerable
g_thread_unref(thread)immediately afterg_thread_new(1149, 1573) detaches. If profanity quits with a request in flight (60sCURLOPT_TIMEOUT), the thread is killed mid-flight and its locals are not released. Tolerable because the OS reclaims everything on process exit — this only shows up in valgrind output as noise. Out of scope for a leak-fix PR.7.
auto_charvsauto_gcharforconnection_create_stanza_id()— tolerable~70 places in the XMPP layer use
auto_char(callsfree) on ag_strdup_printf-allocated string. Tolerable on glibc becausefreeandg_freeare interchangeable there. Worth a separate sweep, not this PR.8.
_write_callbackrealloc is linear-quadratic — tolerableai_client.c:63 reallocs on every chunk. For a 1 MB response at ~16 KB chunks that's ~64 copies. Tolerable because the 10 MB cap puts a ceiling on it; only a perf concern under load.
9. Worker-thread
cons_showfallback in_aiwin_display_response— tolerableai_client.c:138 —
cons_showfrom a worker if the validated window is NULL. The surrounding globallock(profanity.h:46) is in practice the ncurses lock, so probably coordinated. Tolerable, but dropping the fallback (just log a warning) would be more defensive.10.
modelmay be a borrowed pointer — tolerablecmd_funcs.c:10945 —
ai_get_provider_default_modelreturnsconst gchar*borrowed fromprovider->default_model. Tolerable because all AI commands run on the main thread andai_session_createcopies viag_strdupbefore storing.11. TOCTOU between API-key check in
cmd_ai_startand the lookup inai_session_create— tolerableKey is fetched twice (cmd_funcs.c:10955, ai_client.c:1179). If rotated/removed between, session ends up empty-keyed. Tolerable: UX inconsistency, not a leak/crash.
12.
_find_json_fielddoes a barestrstr— tolerableai_client.c:898 — would match the field literal anywhere, including inside string values. Tolerable under well-formed JSON from real providers; could cause confusing parse failures with adversarial/unusual responses. A proper JSON parser is a follow-up issue.
13.
ai_session_switchdoes an avoidable strdup ofapi_key— tolerableai_client.c:1311 + 1314 —
session->api_key = g_strdup(api_key); g_free(api_key);despite already owningapi_key(callerg_steal_pointers into it). Tolerable: wasted alloc/free pair per/ai switch, not a bug.14. Provider hashtable lookup from worker threads is unlocked — tolerable (with caveat)
_ai_generic_request_thread(733) and_models_fetch_thread(1106, 1130) callai_get_provider→ unlockedg_hash_table_lookup. Refcounting protects struct lifetime but not the table read itself. Tolerable because/ai remove provideris a rare action that the user is unlikely to do mid-request, but a crash here is theoretically possible.15.
_aiwin_validateTOCTOU at shutdown — tolerableai_client.c:86-101 —
wins_destroydoesn't take the globallockbefore destroying windows. Worker could deref a freedaiwinbetween validation and use. Tolerable: shutdown-only race, OS cleans up after.16.
assert(aiwin->memcheck == PROFAIWIN_MEMCHECK)may be no-op underNDEBUG— tolerableIf release builds set
NDEBUG, the canary stops protecting in production. Tolerable as a debug-only safety net; turning it into a real conditional + abort would be a hardening pass, not a leak fix.Suggested next steps
local_providerleak in early-exits (+2 lines × 2 branches). Only blocker for merge.FixesforRefs #120and keep #120 open until item 5 (OMEMO) lands, or close #120 and file a follow-up OMEMO issue.