Skip to content

fix(smithy-http): auto-close aiohttp session to avoid unclosed warnings#711

Open
Alan4506 wants to merge 1 commit into
smithy-lang:developfrom
Alan4506:fix/aiohttp-unclosed-upstream
Open

fix(smithy-http): auto-close aiohttp session to avoid unclosed warnings#711
Alan4506 wants to merge 1 commit into
smithy-lang:developfrom
Alan4506:fix/aiohttp-unclosed-upstream

Conversation

@Alan4506
Copy link
Copy Markdown
Contributor

@Alan4506 Alan4506 commented Jun 4, 2026

Problem:

The AIOHTTPClient never closes its aiohttp.ClientSession. Because the generated client deep-copies its config (and thus the transport) on every operation call, each call produced a throwaway session copy. On garbage collection these emit Unclosed client session / Unclosed connector warnings that print to stderr.

Description of changes:

  • Share the single ClientSession across __deepcopy__ instead of copying it, so that all per-call copies of the transport point to the same session.
  • Register a weakref.finalize hook that closes the underlying connector when the client is garbage collected (or at interpreter exit). The connector is closed synchronously because no event loop may be running at finalization. Closing it also marks the session as closed, so both warnings are suppressed.

Testing:

Verified with the generated DynamoDB client running the example below (create table → put item → get item → delete table):

"""Getting started with DynamoDB using the AWS SDK for Python."""

import asyncio
import uuid

from smithy_http.aio.crt import AWSCRTHTTPClient
from aws_sdk_dynamodb.client import DynamoDBClient
from aws_sdk_dynamodb.models import (
    AttributeDefinition,
    AttributeValueN,
    AttributeValueS,
    CreateTableInput,
    DeleteTableInput,
    DescribeTableInput,
    GetItemInput,
    KeySchemaElement,
    ProvisionedThroughput,
    PutItemInput,
)
from aws_sdk_dynamodb.config import Config
from smithy_aws_core.identity import EnvironmentCredentialsResolver


TABLE_NAME = f"sdk-python-getting-started-{uuid.uuid4().hex[:12]}"
REGION = "us-east-1"


async def main() -> None:
    # 1. Create the DynamoDB client
    client = DynamoDBClient(
        config=Config(
            endpoint_uri=f"https://dynamodb.{REGION}.amazonaws.com",
            region=REGION,
            aws_credentials_identity_resolver=EnvironmentCredentialsResolver(),
        )
    )

    # 2. Create a table
    print(f"Creating table '{TABLE_NAME}'...")
    await client.create_table(
        input=CreateTableInput(
            table_name=TABLE_NAME,
            attribute_definitions=[
                AttributeDefinition(attribute_name="pk", attribute_type="S"),
            ],
            key_schema=[
                KeySchemaElement(attribute_name="pk", key_type="HASH"),
            ],
            provisioned_throughput=ProvisionedThroughput(
                read_capacity_units=5,
                write_capacity_units=5,
            ),
        )
    )

    # 3. Wait for the table to become active
    print("Waiting for table to be active...")
    while True:
        response = await client.describe_table(
            input=DescribeTableInput(table_name=TABLE_NAME)
        )
        if response.table and response.table.table_status == "ACTIVE":
            break
        await asyncio.sleep(2)
    print("Table is active.")

    try:
        # 4. Put an item
        print("Writing an item...")
        await client.put_item(
            input=PutItemInput(
                table_name=TABLE_NAME,
                item={
                    "pk": AttributeValueS(value="user-001"),
                    "name": AttributeValueS(value="Alice"),
                    "age": AttributeValueN(value="30"),
                },
            )
        )

        # 5. Get the item back
        print("Reading the item back...")
        get_response = await client.get_item(
            input=GetItemInput(
                table_name=TABLE_NAME,
                key={"pk": AttributeValueS(value="user-001")},
                consistent_read=True,
            )
        )

        item = get_response.item
        if item:
            print(f"  pk:   {item['pk'].value}")
            print(f"  name: {item['name'].value}")
            print(f"  age:  {item['age'].value}")
        else:
            print("  Item not found!")

    finally:
        # 6. Delete the table
        print(f"Deleting table '{TABLE_NAME}'...")
        await client.delete_table(
            input=DeleteTableInput(table_name=TABLE_NAME)
        )
        print("Done.")


if __name__ == "__main__":
    asyncio.run(main())

Before:

(python-client-codegen) yuxnchen@80a9973bd433 python-client-codegen % python getting_started_dynamodb.py
Creating table 'sdk-python-getting-started-4df481afe7fc'...
/Users/yuxnchen/sdk/newServiceProject/smithy-python/packages/aws-sdk-signers/src/aws_sdk_signers/signers.py:794: AWSSDKWarning: Payload signing is enabled. This may result in decreased performance for large request bodies.
  warnings.warn(
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x10b3bb440>
Waiting for table to be active...
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x10b3bbef0>
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x10b3bbbf0>
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x10b3bbfb0>
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x10b3cb320>
Table is active.
Writing an item...
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x10b3cb3b0>
Reading the item back...
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x10b3cb110>
  pk:   user-001
  name: Alice
  age:  30
Deleting table 'sdk-python-getting-started-4df481afe7fc'...
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x10b3cb950>
Done.
Unclosed client session
client_session: <aiohttp.client.ClientSession object at 0x10b3bb170>
Unclosed connector
connections: ['deque([(<aiohttp.client_proto.ResponseHandler object at 0x10b3a7cb0>, 697165.2273415)])']
connector: <aiohttp.connector.TCPConnector object at 0x10b3bb1a0>

After:

(python-client-codegen) yuxnchen@80a9973bd433 python-client-codegen % python getting_started_dynamodb.py      
Creating table 'sdk-python-getting-started-1c4334bbba49'...
/Users/yuxnchen/sdk/newServiceProject/smithy-python/packages/aws-sdk-signers/src/aws_sdk_signers/signers.py:794: AWSSDKWarning: Payload signing is enabled. This may result in decreased performance for large request bodies.
  warnings.warn(
Waiting for table to be active...
Table is active.
Writing an item...
Reading the item back...
  pk:   user-001
  name: Alice
  age:  30
Deleting table 'sdk-python-getting-started-1c4334bbba49'...
Done.

Also ran the full smithy-http test suite: 334 passed, 3 skipped.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@Alan4506 Alan4506 requested a review from a team as a code owner June 4, 2026 02:31
@Alan4506 Alan4506 force-pushed the fix/aiohttp-unclosed-upstream branch from 2e033e1 to b069050 Compare June 4, 2026 02:41
"""
connector = session.connector
if connector is not None and not connector.closed:
connector._close() # type: ignore[attr-defined]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you using an internal private method _close()?

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.

2 participants