Skip to content

Commit 263ff52

Browse files
committed
Forget consumed task results; fix report file-list rendering (v2.6.52)
Maintenance task results were never forgotten and expire only after 24h: a full-library run left 1M+ result keys in the noeviction Redis, slowing every broker/backend operation ~100x in the final stretch (heartbeats minutes apart, misleading '0 active tasks' and 'ETA: 0m'). Both maintenance loops now forget each result after consuming it and on abandonment, bounding Redis to in-flight tasks only. Cleanup and file-changes reports store file lists (objects) in the directories field; the report-details modal rendered them as '[object Object]' rows. It now shows labeled, escaped file lists (Orphaned Files / Changed Files) capped at 100 entries. Sub-minute cleanup ETA reads '<1m' instead of '0m'.
1 parent 1eac544 commit 263ff52

5 files changed

Lines changed: 53 additions & 5 deletions

File tree

CHANGELOG.MD

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0).
77

8+
## [2.6.52] - 2026-06-11
9+
10+
### Fixed
11+
12+
- **Maintenance runs no longer flood Redis with a million result keys.** Task results were never forgotten and expire only after 24h, so a full-library cleanup or integrity run accumulated 1M+ result keys in the noeviction Redis - observed live as every broker/backend operation slowing ~100x once roughly 1M files had been processed (heartbeats minutes apart in the final stretch, "0 active tasks" snapshots, misleading "ETA: 0m"). Both maintenance loops now forget each result after consuming it (and on abandonment), bounding Redis to in-flight tasks only (~5,000 keys instead of 1.18M).
13+
- **Scan report details no longer render "[object Object]" rows.** Cleanup and file-changes reports store file lists (objects with file_path/change_type) in the directories field; the report modal now renders those as labeled file lists (Orphaned Files / Changed Files, capped at 100 entries) instead of joining raw objects.
14+
- Cleanup ETA under one minute reads "<1m" instead of "0m".
15+
816
## [2.6.51] - 2026-06-11
917

1018
### Fixed

docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ services:
6363

6464
# PixelProbe application
6565
pixelprobe:
66-
image: ttlequals0/pixelprobe:${PIXELPROBE_VERSION:-2.6.51}
66+
image: ttlequals0/pixelprobe:${PIXELPROBE_VERSION:-2.6.52}
6767
container_name: pixelprobe-app
6868
environment:
6969
# Security
@@ -131,7 +131,7 @@ services:
131131

132132
# P1 Celery Worker for distributed task processing
133133
celery-worker:
134-
image: ttlequals0/pixelprobe:${PIXELPROBE_VERSION:-2.6.51}
134+
image: ttlequals0/pixelprobe:${PIXELPROBE_VERSION:-2.6.52}
135135
container_name: pixelprobe-celery-worker
136136
command: python celery_worker.py
137137
environment:

pixelprobe/services/maintenance_service.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@
4545
CLEANUP_TASK_TIMEOUT_SECS = env_int('CLEANUP_TASK_TIMEOUT_SECS', 600, floor=60)
4646

4747

48+
def forget_task_result(task):
49+
"""Delete a consumed result from the backend (best-effort).
50+
51+
Results otherwise live for result_expires (24h): a full-library run leaves
52+
1M+ result keys in Redis, driving the noeviction store toward its memory
53+
cap - observed live as every broker/backend op slowing ~100x once ~1M
54+
files had been processed.
55+
"""
56+
try:
57+
task.forget()
58+
except Exception:
59+
pass
60+
61+
4862
def abandon_if_stuck(task_info, timeout_secs, label):
4963
"""Revoke and drop a dispatched task whose result never arrived.
5064
@@ -62,6 +76,7 @@ def abandon_if_stuck(task_info, timeout_secs, label):
6276
task.revoke(terminate=False)
6377
except Exception:
6478
pass
79+
forget_task_result(task)
6580
return True
6681

6782

@@ -451,7 +466,12 @@ def write_cleanup_progress():
451466
eta_seconds = avg_time_per_file * (total_files - total_files_processed)
452467
eta_hours = int(eta_seconds // 3600)
453468
eta_minutes = int((eta_seconds % 3600) // 60)
454-
eta_str = f"{eta_hours}h {eta_minutes}m" if eta_hours > 0 else f"{eta_minutes}m"
469+
if eta_hours > 0:
470+
eta_str = f"{eta_hours}h {eta_minutes}m"
471+
elif eta_minutes > 0:
472+
eta_str = f"{eta_minutes}m"
473+
else:
474+
eta_str = "<1m"
455475
else:
456476
eta_str = "calculating..."
457477

@@ -529,6 +549,8 @@ def write_cleanup_progress():
529549
except Exception as e:
530550
logger.error(f"Error processing existence check result: {e}")
531551
total_files_processed += 1
552+
finally:
553+
forget_task_result(task)
532554
elif abandon_if_stuck(task_info, CLEANUP_TASK_TIMEOUT_SECS, 'existence-check'):
533555
# Abandoned work counts as unverifiable - NEVER deleted
534556
files_abandoned += 1
@@ -1007,6 +1029,8 @@ def write_progress_snapshot(set_heartbeat: bool):
10071029
})
10081030
except Exception as e:
10091031
logger.error(f"Error getting task result: {e}")
1032+
finally:
1033+
forget_task_result(task)
10101034
elif abandon_if_stuck(task_info, INTEGRITY_TASK_TIMEOUT_SECS, 'integrity'):
10111035
files_abandoned += 1
10121036
else:

pixelprobe/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Default version - this is the single source of truth
55

66

7-
_DEFAULT_VERSION = '2.6.51'
7+
_DEFAULT_VERSION = '2.6.52'
88

99

1010
# Allow override via environment variable for CI/CD, but default to the hardcoded version

static/js/app.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3385,7 +3385,23 @@ class PixelProbeApp {
33853385
detailsHtml += `<tr><th>Duration:</th><td>${report.duration_formatted || 'N/A'}</td></tr>`;
33863386

33873387
if (report.directories_scanned && Array.isArray(report.directories_scanned) && report.directories_scanned.length > 0) {
3388-
detailsHtml += `<tr><th>Directories:</th><td>${report.directories_scanned.join('<br>')}</td></tr>`;
3388+
// Cleanup and file-changes reports store file lists (objects with
3389+
// file_path/change_type) in this field; scan reports store paths
3390+
const label = report.scan_type === 'cleanup' ? 'Orphaned Files:'
3391+
: report.scan_type === 'file_changes' ? 'Changed Files:'
3392+
: 'Directories:';
3393+
const maxEntries = 100;
3394+
const entries = report.directories_scanned.slice(0, maxEntries).map(entry => {
3395+
if (entry && typeof entry === 'object') {
3396+
const path = entry.file_path || JSON.stringify(entry);
3397+
return escapeHtml(entry.change_type ? `${path} (${entry.change_type})` : path);
3398+
}
3399+
return escapeHtml(String(entry));
3400+
});
3401+
if (report.directories_scanned.length > maxEntries) {
3402+
entries.push(`... and ${report.directories_scanned.length - maxEntries} more`);
3403+
}
3404+
detailsHtml += `<tr><th>${label}</th><td>${entries.join('<br>')}</td></tr>`;
33893405
}
33903406

33913407
detailsHtml += '</table>';

0 commit comments

Comments
 (0)