mirror of
https://github.com/plexguide/Huntarr.io.git
synced 2026-02-20 07:44:17 -05:00
8.7 KiB
8.7 KiB
Instance ID Implementation – Code Review
Summary
Stable instance_id (format: app-YYMMDDHHMMxxx) is used for all DB keying so renaming an instance in the UI does not break tracking. Display name (instance_name) is used only for logs and UI.
1. Backward Compatibility
New installs
- No
instance_idin settings → first run generates one, saves to settings, runs migration (no-op when no rows exist). - All per-instance tables end up keyed by
instance_id.
Existing installs (legacy)
- Multi-instance: Settings have
instances[].namebut noinstance_id. On first load,get_configured_instances()generatesinstance_id, writes it tosettings["instances"][idx]["instance_id"], callssave_settings(), thenmigrate_instance_identifier(app_type, old_instance_name, new_instance_id). - Legacy single-instance (e.g. Radarr with no
instanceslist):instance_idis generated and stored atsettings["instance_id"]; migration runs for"Default"→legacy_id. - Idempotency: Migration does
UPDATE ... WHERE instance_name = old_name. After the first run there are no rows left withold_name, so a second run updates 0 rows. Safe to call multiple times.
Fallback when instance_id is missing
- Everywhere we use:
instance_key = instance_details.get("instance_id") or instance_details.get("instance_name", "Default"). - So old configs without
instance_idstill work (keyed by name until the next run assigns an id).
2. Database Safety
Migration (migrate_instance_identifier)
- Tables updated:
sleep_data_per_instance,hourly_caps_per_instance,reset_requests_per_instance,media_stats_per_instance(by columninstance_name), thenmigrate_instance_state_managementfor stateful + hunt_history. - Fix applied:
stateful_processed_idshas noupdated_atcolumn; migration only doesSET instance_name = ?for that table (noupdated_at). - Conflict: If the new id already has rows (shouldn’t happen on first assign),
migrate_instance_state_managementskips to avoid overwriting.
Schema
- All per-instance tables use a single column (e.g.
instance_name) to store the instance key; that value is nowinstance_idafter migration. No schema change required.
3. Consistency of instance_key vs instance_name
Background (background.py)
- Cycle / sleep / reset / cap / state: All use
instance_key(e.g.start_cycle,end_cycle,_has_pending_reset,set_cycle_activity,clear_cycle_activity,check_hourly_cap_exceeded,get_hourly_cap_status,initialize_instance_state_management,reset_instance_state_management,get_state_management_summary). - Sleep lookups:
_get_instances_due_and_sleepand_get_sleep_seconds_until_next_cycleuseinst.get("instance_id") or inst.get("instance_name", "Default"). - Responsive sleep reset check: Uses
inst.get("instance_id") or inst.get("instance_name", "Default"). - Logs and return values: Use
instance_name(display).
Apps (process_missing / process_upgrades)
- Sonarr: Receives
instance_name=instance_keyfrom background; all DB/history uses that (stable id). - Radarr, Lidarr, Readarr, Whisparr, Eros: Use
instance_key = app_settings.get("instance_id") or instance_nameand useinstance_keyfor:is_processed,add_processed_id,increment_stat/increment_stat_only,log_processed_media.
- Fixes applied in this review:
- Readarr upgrade:
is_processed("readarr", instance_key, ...)(wasinstance_name). - Readarr missing:
add_processed_id("readarr", instance_key, ...)(wasinstance_name). - Whisparr upgrade:
add_processed_id("whisparr", instance_key, ...)(wasinstance_name). - Lidarr missing:
is_processed("lidarr", instance_key, str(eid))for artist filter;log_processed_media(..., instance_key, "missing")(wasinstance_namewhere it affected DB).
- Readarr upgrade:
Cycle tracker (cycle_tracker.py)
- Uses
get_configured_instances()to get(instance_name, instance_id). - Response remains keyed by display name for the API.
pending_resetand DB merge useinstance_id;_cycle_activityis keyed byinstance_id(same as in background).
Stateful routes (stateful_routes.py)
_resolve_instance_id(app_type, instance_name)resolves display name →instance_idviaget_configured_instances().- Reset and summary use the resolved id for DB; messages still use display name where appropriate.
Web server (web_server.py)
- Reset request: resolves request
instance_nametoinstance_idviaget_configured_instances()beforecreate_reset_request(app_name, instance_identifier).
4. ID Generation and Concurrency
- Format:
app_type-YYMMDDHHMM+ 3 random alphanumeric; collision avoided by checkingexisting_ids(from current app instances). - Possible race: If two threads call
get_configured_instances()for the same app while an instance has no id, both could generate an id in the same second. Mitigation: same-second ids are unlikely to collide due to 3-char suffix; if they did, both would end up with the same id (no DB corruption). A stricter fix would be a short lock around generate+save per app (optional).
5. History and Rename Handling
- hunt_history:
instance_namecolumn now storesinstance_id(all callers passinstance_key). - handle_instance_rename: History is keyed by
instance_id, so display-name renames do not require updating history.HuntarrDatabase.handle_instance_rename()is implemented as a no-op (returns True) for API compatibility withhistory_manager; no DB updates.
6. Frontend / API Contract
- Cycle status API: Still returns instances keyed by display name;
pending_resetand next_cycle are resolved viainstance_idinternally. - Reset request: Request body/query can send display name; backend resolves to
instance_idbefore creating the reset. - Stateful summary/reset: Query params use display name; backend resolves to
instance_idfor DB.
No breaking change for existing frontends.
7. Checklist for Deployments
- New installs: get
instance_idon first run and persist. - Legacy single-instance: get default
instance_idand persist. - Migration: all relevant tables updated from name → id; idempotent; no
updated_atonstateful_processed_ids. - All apps use
instance_keyfor is_processed, add_processed_id, increment_stat, log_processed_media (Readarr, Whisparr, Lidarr fixes applied). - Sleep/reset/cap/state/cycle use
instance_keyeverywhere in background and cycle_tracker. - Stateful and reset APIs resolve display name → id where needed.
- No schema change; column names unchanged (value is now id).
- stats_manager: Hourly cap limit lookup supports
instance_id; get_stats and load_hourly_caps_for_api use instance_id for DB lookups and display name for API keys.
8. Final Review Fixes (stats_manager)
- ** _get_instance_hourly_cap_limit**: When
get_hourly_cap_statusis called withinstance_key(id), the limit lookup failed because it only matchedinst.get("name")andinst.get("instance_name"). Addedinst.get("instance_id") == instance_keyso limit is resolved correctly when key is an id. - get_stats: Was building
instance_namesfrom display names and looking upper_instance_caps.get(name),by_name.get(name)(DB is keyed by instance_id after migration). Fixed by usingget_configured_instances()to get (display_name, instance_id), then looking up stats/caps/lock byinstance_idand outputting withinstance_name: display_name. - load_hourly_caps_for_api: Same mismatch (output keyed by display name but DB keyed by id). Fixed by iterating configured instances and mapping
instance_id→ DB data,display_name→ output key.
9. Files Touched (Reference)
src/primary/utils/instance_id.py– ID generation.src/primary/utils/database.py– Migration (identifier + state management);stateful_processed_idsUPDATE fixed.src/primary/background.py– instance_key for all cycle/state/cap/sleep; sleep lookups by id.src/primary/cycle_tracker.py– get_configured_instances; response by display name, lookups by id.src/primary/stateful_routes.py– resolve to id for summary/reset.src/primary/web_server.py– resolve to id for reset request.src/primary/apps/{sonarr,radarr,lidarr,readarr,whisparr,eros}/__init__.py– assign and persist instance_id; include in returned instance dict.src/primary/apps/readarr/upgrade.py,readarr/missing.py,whisparr/upgrade.py,lidarr/missing.py– use instance_key for DB-related calls (fixes applied in this review).src/primary/stats_manager.py– _get_instance_hourly_cap_limit matches instance_id; get_stats and load_hourly_caps_for_api use instance_id for DB lookups, display name for API keys.