Skip to content

BaseProvider.close() is a no-op, leaking an httpx.Client on every model switch #172

Description

@abhishekKokadwar

Description

BaseProvider.close() does nothing, but BaseProvider.__init__ creates an httpx.Client:

https://github.com/sugarlabs/sugar-ai/blob/main/app/providers/base.py

def close(self) -> None:
    """Release provider resources."""
    pass

RAGAgent.set_model() calls old_provider.close() when swapping providers, expecting the previous one to release its resources:

def set_model(self, provider: BaseProvider) -> None:
    old_provider = self.provider
    self.provider = provider
    self.model_name = provider.get_model_name()
    if old_provider is not provider:
        try:
            old_provider.close()
        except Exception as e:
            logger.warning("Failed to close previous provider: %s", e)

Because close() is a no-op, every model switch leaks a client along with its connection pool and open sockets. With the model-switching endpoint (#121) this accumulates over the life of the process.

Affected providers

Provider Creates httpx.Client Overrides close() Leaks
BaseProvider yes no yes
GeminiProvider yes no yes
OllamaProvider yes yes no
HuggingFaceProvider no (local model) no n/a

OllamaProvider is unaffected only because it happens to override close() correctly.

Reproduction

from app.providers.base import BaseProvider

p = BaseProvider(model_name="gpt-4o-mini", api_key="test-key")
print(p._client.is_closed)   # False
p.close()
print(p._client.is_closed)   # False  <-- still open, should be True

Expected

close() releases the HTTP client, so p._client.is_closed is True afterwards.

Note on the fix

The fix belongs in BaseProvider so all providers inherit it, rather than in each subclass. It needs a guard rather than a bare self._client.close(): HuggingFaceProvider subclasses BaseProvider but loads a local model and never calls super().__init__(), so it has no _client, and an unguarded close raises AttributeError there.

I have a fix with tests ready in #171.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions