fix: reuse persistent aiohttp.ClientSession in GiphyService (#26) #66

Merged
cal merged 1 commits from ai/major-domo-v2-26 into next-release 2026-03-07 03:31:03 +00:00
Owner

Summary

Closes #26.

Both get_disappointment_gif and get_gif previously created a new aiohttp.ClientSession per invocation, incurring a TCP handshake and DNS resolution on every GIF fetch. This PR stores a single ClientSession on the GiphyService instance and reuses it across calls.

Changes

  • services/giphy_service.py
    • Added self._session: Optional[aiohttp.ClientSession] = None in __init__
    • Added _get_session() lazy initialiser — creates the session on first use, recreates it if closed
    • Replaced async with aiohttp.ClientSession() as session: in both get_disappointment_gif and get_gif with session = self._get_session()
    • Individual HTTP responses still use async with session.get(...) as resp: — only the outer session lifetime changes

Test Results

924 tests passed, 3 skipped. The 6 failures in test_commands_profile_images.py are pre-existing and unrelated to this change (those files were already modified on main before this branch was created).

## Summary Closes #26. Both `get_disappointment_gif` and `get_gif` previously created a new `aiohttp.ClientSession` per invocation, incurring a TCP handshake and DNS resolution on every GIF fetch. This PR stores a single `ClientSession` on the `GiphyService` instance and reuses it across calls. ## Changes - `services/giphy_service.py` - Added `self._session: Optional[aiohttp.ClientSession] = None` in `__init__` - Added `_get_session()` lazy initialiser — creates the session on first use, recreates it if closed - Replaced `async with aiohttp.ClientSession() as session:` in both `get_disappointment_gif` and `get_gif` with `session = self._get_session()` - Individual HTTP responses still use `async with session.get(...) as resp:` — only the outer session lifetime changes ## Test Results 924 tests passed, 3 skipped. The 6 failures in `test_commands_profile_images.py` are pre-existing and unrelated to this change (those files were already modified on `main` before this branch was created).
cal added 1 commit 2026-03-05 07:33:07 +00:00
perf: reuse persistent aiohttp.ClientSession in GiphyService (#26)
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m5s
e9dd318e5a
Both get_disappointment_gif and get_gif previously created a new
ClientSession per call. Replace with a lazily-initialised shared
session stored on the instance, eliminating per-call TCP handshake
and DNS overhead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cal added the
ai-reviewing
label 2026-03-05 07:45:31 +00:00
cal reviewed 2026-03-05 07:47:10 +00:00
cal left a comment
Author
Owner

AI Code Review

Files Reviewed

  • services/giphy_service.py (modified)

Findings

Correctness

  • The lazy initializer pattern is correct. _get_session() checks both None and .closed before constructing a new session, which handles the case where the session was previously closed externally.
  • The HTTP response context managers (async with session.get(...) as resp:) are preserved correctly — only the outer session lifetime changes, as described.
  • Optional is already imported from typing and aiohttp was already imported — no new import issues.
  • The refactoring is a pure mechanical extraction; no logic was altered in the retry loops or filter paths.

Security

  • No issues. API key and search phrases are still passed through quote(). No new attack surface introduced.

Style & Conventions

  • _get_session() is a synchronous method but is always called from within async methods (get_disappointment_gif, get_gif), so the event loop is guaranteed to be running when aiohttp.ClientSession() is constructed. This is safe.
  • Naming and code organization follow existing patterns in the service layer.

Suggestions

  • No close() method: The shared ClientSession is never explicitly closed. aiohttp will emit ResourceWarning: Unclosed client session at process exit. For a long-running bot singleton this is an accepted trade-off, but an explicit cleanup method would be clean:
    async def close(self) -> None:
        if self._session and not self._session.closed:
            await self._session.close()
    
    The bot's shutdown hook (or cog teardown) could then call this. Not a blocker — the OS reclaims resources at container exit either way.

Verdict: APPROVED

The optimization is correct and well-scoped. Reusing a persistent ClientSession is the right pattern for a long-running service and eliminates the per-call TCP handshake overhead. The only notable omission is explicit cleanup on shutdown, which is a minor concern for a Docker-based bot.


Automated review by Claude PR Reviewer

## AI Code Review ### Files Reviewed - `services/giphy_service.py` (modified) ### Findings #### Correctness - The lazy initializer pattern is correct. `_get_session()` checks both `None` and `.closed` before constructing a new session, which handles the case where the session was previously closed externally. - The HTTP response context managers (`async with session.get(...) as resp:`) are preserved correctly — only the outer session lifetime changes, as described. - `Optional` is already imported from `typing` and `aiohttp` was already imported — no new import issues. - The refactoring is a pure mechanical extraction; no logic was altered in the retry loops or filter paths. #### Security - No issues. API key and search phrases are still passed through `quote()`. No new attack surface introduced. #### Style & Conventions - `_get_session()` is a synchronous method but is always called from within async methods (`get_disappointment_gif`, `get_gif`), so the event loop is guaranteed to be running when `aiohttp.ClientSession()` is constructed. This is safe. - Naming and code organization follow existing patterns in the service layer. #### Suggestions - **No `close()` method**: The shared `ClientSession` is never explicitly closed. aiohttp will emit `ResourceWarning: Unclosed client session` at process exit. For a long-running bot singleton this is an accepted trade-off, but an explicit cleanup method would be clean: ```python async def close(self) -> None: if self._session and not self._session.closed: await self._session.close() ``` The bot's shutdown hook (or cog teardown) could then call this. Not a blocker — the OS reclaims resources at container exit either way. ### Verdict: APPROVED The optimization is correct and well-scoped. Reusing a persistent `ClientSession` is the right pattern for a long-running service and eliminates the per-call TCP handshake overhead. The only notable omission is explicit cleanup on shutdown, which is a minor concern for a Docker-based bot. --- *Automated review by Claude PR Reviewer*
cal added
ai-reviewed
and removed
ai-reviewing
labels 2026-03-05 07:47:30 +00:00
cal changed target branch from main to next-release 2026-03-07 03:30:53 +00:00
cal merged commit b6b997c15e into next-release 2026-03-07 03:31:03 +00:00
cal deleted branch ai/major-domo-v2-26 2026-03-07 03:31:04 +00:00
Sign in to join this conversation.
No reviewers
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: cal/major-domo-v2#66
No description provided.