Skip to content

fix(client): serialize create_onvif_service to avoid leaking sessions#294

Draft
bluetoothbot wants to merge 2 commits into
openvideolibs:asyncfrom
bluetoothbot:koan/serialize-service-create
Draft

fix(client): serialize create_onvif_service to avoid leaking sessions#294
bluetoothbot wants to merge 2 commits into
openvideolibs:asyncfrom
bluetoothbot:koan/serialize-service-create

Conversation

@bluetoothbot

@bluetoothbot bluetoothbot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What

Serialize ONVIFCamera.create_onvif_service so concurrent calls for the same binding build exactly one service instead of leaking an aiohttp session.

Why

create_onvif_service checks self.services, then awaits service.setup() before storing the result. Two coroutines creating the same service concurrently (e.g. create_media_service() fired in parallel during device setup) both miss the cache, both construct an ONVIFService — each of which opens an aiohttp ClientSession — and the second store overwrites the first. The loser is never placed in self.services, so close() never reaches it: an orphaned socket leaks for the lifetime of the camera object.

Resource-safety fix — untracked aiohttp session per concurrent duplicate create.

How

  • Added a lazily-created per-camera asyncio.Lock (_services_lock).
  • Moved the check-recreate-store block inside async with self._services_lock, with the cache re-checked after the lock is held so the second caller returns the winner's instance.
  • Mirrors the existing double-checked _get_shared_sqlite_cache pattern already in this module. Lazy init is race-free: no await between the None check and the assignment.

Testing

  • New test_concurrent_create_onvif_service_builds_one_service drives two concurrent creates and asserts one service is built and both callers get the same instance. Committed first so it fails against the unpatched code (2 services built), passes after the fix.
  • Full suite green: 256 passed, ruff clean.

Quality Report

Changes: 87 files changed, 9619 insertions(+), 1474 deletions(-)

Code scan: 20 issue(s) found

  • README.rst:61 — debug print statement
  • README.rst:111 — debug print statement
  • examples/events.py:20 — debug print statement
  • examples/events.py:24 — debug print statement
  • examples/events.py:25 — debug print statement
  • examples/events.py:27 — debug print statement
  • examples/events.py:29 — debug print statement
  • examples/events.py:62 — debug print statement
  • examples/events.py:74 — debug print statement
  • examples/events.py:84 — debug print statement

Tests: failed (FAILED)

Branch hygiene: clean

Generated by Kōan

Two concurrent create_onvif_service calls for the same binding both miss
the services cache and each build an ONVIFService (opening an aiohttp
session); the second store overwrites the first, orphaning the loser's
session. This test fails against the current code (two services built).
Concurrent create_onvif_service calls for the same binding both missed
the services cache and each built an ONVIFService (each opening an aiohttp
session); the later store overwrote the earlier one, orphaning the loser's
session forever. Guard the check-create-store with a lazily-created
per-camera asyncio.Lock and re-check the cache after acquiring it, matching
the existing shared-SqliteCache double-checked pattern.
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