Skip to content

Fix resource leaks, race conditions, and overly broad exception handling#6

Merged
vladiant merged 5 commits intomainfrom
medium_fixes
Mar 29, 2026
Merged

Fix resource leaks, race conditions, and overly broad exception handling#6
vladiant merged 5 commits intomainfrom
medium_fixes

Conversation

@vladiant
Copy link
Copy Markdown
Collaborator

Summary

This MR addresses four bug fixes related to resource management, data consistency, and exception safety across the application and infrastructure layers.

Version bump: 1.2.01.2.1 (PATCH — bug fixes only)

Changes

1. ProcessPoolExecutor leak on shutdown

The module-level ProcessPoolExecutor in PillowImageProcessor was never shut down. Added shutdown_executor() and call it from the FastAPI lifespan teardown handler.

Files: pillow_processor.py, main.py

2. Race condition in retention sweeps

Concurrent calls to get_expired() could fetch the same rows, causing double-delete attempts on storage files. Added delete_expired_batch() to the ImageRepository port, implemented with SELECT … FOR UPDATE SKIP LOCKED + DELETE in a single transaction so concurrent sweeps never process the same rows.

Files: image_repository.py, postgres_image_repository.py, cached_image_repository.py, apply_retention.py

3. Missing rollback in ProcessImageUseCase

If thumbnail storage succeeded but the final metadata save failed, the thumbnail was orphaned on disk and the image was stuck in PROCESSING state. The final save() is now inside the try block, and the except handler deletes the stored thumbnail before marking the image as FAILED.

Files: process_image.py

4. Overly broad except Exception handlers

  • apply_retention.py and process_image.py: narrowed to except OSError for storage operations.
  • pipeline.py: replaced per-coroutine try/except with asyncio.gather(return_exceptions=True) so KeyboardInterrupt/CancelledError propagate naturally.

Files: apply_retention.py, process_image.py, pipeline.py

Test coverage

  • New test: test_process_image_cleans_up_thumbnail_on_save_failure
  • New test: test_delete_expired_batch_delegates_and_invalidates
  • Updated: test_retention_deletes_expired, test_retention_no_expired
  • All 48 tests pass. Ruff lint and mypy type-check clean.

Diff summary

14 files changed, 145 insertions(+), 25 deletions(-)

@vladiant vladiant merged commit e85173a into main Mar 29, 2026
4 checks passed
@vladiant vladiant deleted the medium_fixes branch March 29, 2026 19:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant