Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-awscrt for openSUSE:Factory 
checked in at 2026-04-09 17:48:21
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-awscrt (Old)
 and      /work/SRC/openSUSE:Factory/.python-awscrt.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-awscrt"

Thu Apr  9 17:48:21 2026 rev:4 rq:1345527 version:0.32.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-awscrt/python-awscrt.changes      
2026-03-17 19:05:07.722241081 +0100
+++ /work/SRC/openSUSE:Factory/.python-awscrt.new.21863/python-awscrt.changes   
2026-04-09 17:48:23.226336175 +0200
@@ -1,0 +2,8 @@
+Thu Apr  9 11:53:10 UTC 2026 - John Paul Adrian Glaubitz 
<[email protected]>
+
+- Update to version 0.32.0
+  * Support free-thread build by @TingDaoK in (#725)
+  * Add support for notifying end of remote stream by @TingDaoK in (#727)
+- Refresh skip-test-requiring-network.patch and add more tests to skip
+
+-------------------------------------------------------------------

Old:
----
  awscrt-0.31.3.tar.gz

New:
----
  awscrt-0.32.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-awscrt.spec ++++++
--- /var/tmp/diff_new_pack.lIjOeY/_old  2026-04-09 17:48:24.086371934 +0200
+++ /var/tmp/diff_new_pack.lIjOeY/_new  2026-04-09 17:48:24.086371934 +0200
@@ -18,16 +18,14 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-awscrt
-Version:        0.31.3
+Version:        0.32.0
 Release:        0
 Summary:        A common runtime for AWS Python projects
 License:        Apache-2.0
 URL:            https://github.com/awslabs/aws-crt-python
 Source:         %{url}/archive/v%{version}.tar.gz#/awscrt-%{version}.tar.gz
-
 # PATCH-FIX-OPENSUSE skip-test-requiring-network.patch [email protected] -- 
one test requires internet connection, skip it
 Patch:          skip-test-requiring-network.patch
-
 BuildRequires:  python-rpm-macros
 BuildRequires:  cmake(aws-c-auth)
 BuildRequires:  cmake(aws-c-cal)

++++++ awscrt-0.31.3.tar.gz -> awscrt-0.32.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aws-crt-python-0.31.3/.github/workflows/ci.yml 
new/aws-crt-python-0.32.0/.github/workflows/ci.yml
--- old/aws-crt-python-0.31.3/.github/workflows/ci.yml  2026-03-06 
22:06:47.000000000 +0100
+++ new/aws-crt-python-0.32.0/.github/workflows/ci.yml  2026-03-27 
00:11:43.000000000 +0100
@@ -56,6 +56,9 @@
           - cp311-cp311
           - cp312-cp312
           - cp313-cp313
+          - cp313-cp313t
+          - cp314-cp314
+          - cp314-cp314t
     permissions:
       id-token: write # This is required for requesting the JWT
     steps:
@@ -81,6 +84,9 @@
           - cp311-cp311
           - cp312-cp312
           - cp313-cp313
+          - cp313-cp313t
+          - cp314-cp314
+          - cp314-cp314t
     permissions:
       id-token: write # This is required for requesting the JWT
     steps:
@@ -108,6 +114,7 @@
           - cp311-cp311
           - cp312-cp312
           - cp313-cp313
+          - cp313-cp313t
     permissions:
       id-token: write # This is required for requesting the JWT
     steps:
@@ -133,6 +140,7 @@
           - cp311-cp311
           - cp312-cp312
           - cp313-cp313
+          - cp313-cp313t
     permissions:
       id-token: write # This is required for requesting the JWT
     steps:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aws-crt-python-0.31.3/awscrt/aio/http.py 
new/aws-crt-python-0.32.0/awscrt/aio/http.py
--- old/aws-crt-python-0.31.3/awscrt/aio/http.py        2026-03-06 
22:06:47.000000000 +0100
+++ new/aws-crt-python-0.32.0/awscrt/aio/http.py        2026-03-27 
00:11:43.000000000 +0100
@@ -22,6 +22,7 @@
 from io import BytesIO
 from concurrent.futures import Future
 from typing import List, Tuple, Optional, Callable, AsyncIterator
+import threading
 
 
 class AIOHttpClientConnectionUnified(HttpClientConnectionBase):
@@ -328,13 +329,15 @@
         '_completion_future',
         '_stream_completed',
         '_status_code',
-        '_loop')
+        '_loop',
+        '_deque_lock')
 
     def __init__(self,
                  connection: AIOHttpClientConnection,
                  request: HttpRequest,
                  request_body_generator: AsyncIterator[bytes] = None,
                  loop: Optional[asyncio.AbstractEventLoop] = None) -> None:
+
         # Initialize the parent class
         http2_manual_write = request_body_generator is not None and 
connection.version is HttpVersion.Http2
         super()._init_common(connection, request, 
http2_manual_write=http2_manual_write)
@@ -347,14 +350,15 @@
             raise TypeError("loop must be an instance of 
asyncio.AbstractEventLoop")
         self._loop = loop
 
-        # deque is thread-safe for appending and popping, so that we don't need
-        # locks to handle the callbacks from the C thread
+        # Lock to protect check-then-act sequences on deques for thread safety 
in free-threaded Python
+        self._deque_lock = threading.Lock()
         self._chunk_futures = deque()
         self._received_chunks = deque()
         self._stream_completed = False
 
         # Create futures for async operations
         self._completion_future = Future()
+        self._remote_completion_future = Future()
         self._response_status_future = Future()
         self._response_headers_future = Future()
         self._status_code = None
@@ -373,12 +377,31 @@
         self._response_headers_future.set_result(name_value_pairs)
 
     def _on_body(self, chunk: bytes) -> None:
-        """Process body chunk on the correct event loop thread."""
-        if self._chunk_futures:
-            future = self._chunk_futures.popleft()
-            future.set_result(chunk)
-        else:
-            self._received_chunks.append(chunk)
+        """Process body chunk - called from C thread."""
+        with self._deque_lock:
+            if self._chunk_futures:
+                future = self._chunk_futures.popleft()
+            else:
+                self._received_chunks.append(chunk)
+                return
+
+        # Set result outside lock (Future is thread-safe)
+        future.set_result(chunk)
+
+    def _resolve_pending_chunk_futures(self) -> None:
+        """Helper to resolve all pending chunk futures with empty bytes.
+
+        This indicates end of stream to any waiting get_next_response_chunk() 
calls.
+        Must be called when either the stream completes or remote peer sends 
END_STREAM.
+        """
+        # Resolve all pending chunk futures with lock protection
+        with self._deque_lock:
+            pending_futures = list(self._chunk_futures)
+            self._chunk_futures.clear()
+
+        # Set results outside lock (Future is thread-safe)
+        for future in pending_futures:
+            future.set_result(b"")
 
     def _on_complete(self, error_code: int) -> None:
         """Set the completion status of the stream."""
@@ -387,10 +410,12 @@
         else:
             
self._completion_future.set_exception(awscrt.exceptions.from_code(error_code))
 
-        # Resolve all pending chunk futures with an empty string to indicate 
end of stream
-        while self._chunk_futures:
-            future = self._chunk_futures.popleft()
-            future.set_result("")
+        self._resolve_pending_chunk_futures()
+
+    def _on_h2_remote_end_stream(self) -> None:
+        """Called when the remote peer has finished sending (HTTP/2 only)."""
+        self._remote_completion_future.set_result(None)
+        self._resolve_pending_chunk_futures()
 
     async def _set_request_body_generator(self, body_iterator: 
AsyncIterator[bytes]):
         ...
@@ -418,14 +443,17 @@
             bytes: The next chunk of data from the response body.
                 Returns empty bytes when the stream is completed and no more 
chunks are left.
         """
-        if self._received_chunks:
-            return self._received_chunks.popleft()
-        elif self._completion_future.done():
-            return b""
-        else:
-            future = Future()
-            self._chunk_futures.append(future)
-            return await asyncio.wrap_future(future, loop=self._loop)
+        with self._deque_lock:
+            if self._received_chunks:
+                return self._received_chunks.popleft()
+            elif self._completion_future.done() or 
self._remote_completion_future.done():
+                return b""
+            else:
+                future = Future()
+                self._chunk_futures.append(future)
+
+        # Await outside lock
+        return await asyncio.wrap_future(future, loop=self._loop)
 
     async def wait_for_completion(self) -> int:
         """Wait asynchronously for the stream to complete.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aws-crt-python-0.31.3/awscrt/http.py 
new/aws-crt-python-0.32.0/awscrt/http.py
--- old/aws-crt-python-0.31.3/awscrt/http.py    2026-03-06 22:06:47.000000000 
+0100
+++ new/aws-crt-python-0.32.0/awscrt/http.py    2026-03-27 00:11:43.000000000 
+0100
@@ -569,6 +569,14 @@
         else:
             
self._completion_future.set_exception(awscrt.exceptions.from_code(error_code))
 
+    def _on_h2_remote_end_stream(self) -> None:
+        """Called when remote peer sends END_STREAM (HTTP/2 only).
+
+        This callback is only invoked for HTTP/2 connections. HTTP/1.x streams
+        will never receive this callback. Base implementation does nothing.
+        """
+        pass
+
     def update_window(self, increment_size: int) -> None:
         """
         Update the stream's flow control window.
@@ -613,14 +621,57 @@
 
 
 class Http2ClientStream(HttpClientStreamBase):
+    __slots__ = ('_remote_end_stream_future',)
+
     def __init__(self,
                  connection: HttpClientConnection,
                  request: 'HttpRequest',
                  on_response: Optional[Callable[..., None]] = None,
                  on_body: Optional[Callable[..., None]] = None,
                  manual_write: bool = False) -> None:
+        self._remote_end_stream_future = Future()
         self._init_common(connection, request, on_response, on_body, 
manual_write)
 
+    @property
+    def remote_end_stream_future(self) -> "concurrent.futures.Future":
+        """
+        concurrent.futures.Future: Future that completes when the remote peer 
has finished
+        sending (HTTP/2 only). This occurs when the server sends an END_STREAM 
flag.
+
+        The future will contain a result of None on success, or an exception 
if the stream
+        encounters an error before END_STREAM is received (e.g., RST_STREAM).
+
+        This is different from `completion_future` which completes when both 
the
+        client and server have finished (bidirectional stream closure).
+
+        Note: This future only applies to HTTP/2 connections. It will complete 
when the
+        server sends END_STREAM, which may occur before the client finishes 
sending.
+        In case of stream completed without END_STREAM received, this future 
will complete
+        with exception.
+        """
+        return self._remote_end_stream_future
+
+    def _on_h2_remote_end_stream(self) -> None:
+        """Internal callback when remote peer sends END_STREAM (HTTP/2 
only)."""
+        if not self._remote_end_stream_future.done():
+            self._remote_end_stream_future.set_result(None)
+
+    def _on_complete(self, error_code: int) -> None:
+        # done with HttpRequest, drop reference
+        self._request = None  # type: ignore
+
+        # Ensure remote_completion_future is always resolved
+        if not self._remote_end_stream_future.done():
+            # Stream completed successfully but END_STREAM was never received,
+            # complete `remote_completion_future` with exception.
+            self._remote_end_stream_future.set_exception(
+                RuntimeError("Stream completed without receiving remote 
END_STREAM"))
+
+        if error_code == 0:
+            self._completion_future.set_result(self._response_status_code)
+        else:
+            
self._completion_future.set_exception(awscrt.exceptions.from_code(error_code))
+
     def activate(self) -> None:
         """Begin sending the request.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/aws-crt-python-0.31.3/continuous-delivery/build-wheels-manylinux2014-aarch64.sh
 
new/aws-crt-python-0.32.0/continuous-delivery/build-wheels-manylinux2014-aarch64.sh
--- 
old/aws-crt-python-0.31.3/continuous-delivery/build-wheels-manylinux2014-aarch64.sh
 2026-03-06 22:06:47.000000000 +0100
+++ 
new/aws-crt-python-0.32.0/continuous-delivery/build-wheels-manylinux2014-aarch64.sh
 2026-03-27 00:11:43.000000000 +0100
@@ -22,6 +22,12 @@
 /opt/python/cp313-cp313/bin/python -m build
 auditwheel repair --plat manylinux2014_aarch64 dist/awscrt-*cp313*.whl
 
+# The free-threaded build does not currently support the Limited C API or the 
stable ABI. Built them separately
+/opt/python/cp313-cp313t/bin/python -m build
+auditwheel repair --plat manylinux2014_aarch64 dist/awscrt-*cp313t*.whl
+/opt/python/cp314-cp314t/bin/python -m build
+auditwheel repair --plat manylinux2014_aarch64 dist/awscrt-*cp314t*.whl
+
 rm dist/*.whl
 cp -rv wheelhouse/* dist/
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/aws-crt-python-0.31.3/continuous-delivery/build-wheels-manylinux2014-x86_64.sh
 
new/aws-crt-python-0.32.0/continuous-delivery/build-wheels-manylinux2014-x86_64.sh
--- 
old/aws-crt-python-0.31.3/continuous-delivery/build-wheels-manylinux2014-x86_64.sh
  2026-03-06 22:06:47.000000000 +0100
+++ 
new/aws-crt-python-0.32.0/continuous-delivery/build-wheels-manylinux2014-x86_64.sh
  2026-03-27 00:11:43.000000000 +0100
@@ -22,6 +22,12 @@
 /opt/python/cp313-cp313/bin/python -m build
 auditwheel repair --plat manylinux2014_x86_64 dist/awscrt-*cp313*.whl
 
+# The free-threaded build does not currently support the Limited C API or the 
stable ABI. Built them separately
+/opt/python/cp313-cp313t/bin/python -m build
+auditwheel repair --plat manylinux2014_x86_64 dist/awscrt-*cp313t*.whl
+/opt/python/cp314-cp314t/bin/python -m build
+auditwheel repair --plat manylinux2014_x86_64 dist/awscrt-*cp314t*.whl
+
 rm dist/*.whl
 cp -rv wheelhouse/* dist/
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/aws-crt-python-0.31.3/continuous-delivery/build-wheels-musllinux-1-1-aarch64.sh
 
new/aws-crt-python-0.32.0/continuous-delivery/build-wheels-musllinux-1-1-aarch64.sh
--- 
old/aws-crt-python-0.31.3/continuous-delivery/build-wheels-musllinux-1-1-aarch64.sh
 2026-03-06 22:06:47.000000000 +0100
+++ 
new/aws-crt-python-0.32.0/continuous-delivery/build-wheels-musllinux-1-1-aarch64.sh
 2026-03-27 00:11:43.000000000 +0100
@@ -22,6 +22,11 @@
 /opt/python/cp313-cp313/bin/python -m build
 auditwheel repair --plat musllinux_1_1_aarch64 dist/awscrt-*cp313*.whl
 
+# The free-threaded build does not currently support the Limited C API or the 
stable ABI. Built them separately
+/opt/python/cp313-cp313t/bin/python -m build
+auditwheel repair --plat musllinux_1_1_aarch64 dist/awscrt-*cp313t*.whl
+# Musllinux-1-1 is EOL without python>3.13
+
 rm dist/*.whl
 cp -rv wheelhouse/* dist/
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/aws-crt-python-0.31.3/continuous-delivery/build-wheels-musllinux-1-1-x86_64.sh
 
new/aws-crt-python-0.32.0/continuous-delivery/build-wheels-musllinux-1-1-x86_64.sh
--- 
old/aws-crt-python-0.31.3/continuous-delivery/build-wheels-musllinux-1-1-x86_64.sh
  2026-03-06 22:06:47.000000000 +0100
+++ 
new/aws-crt-python-0.32.0/continuous-delivery/build-wheels-musllinux-1-1-x86_64.sh
  2026-03-27 00:11:43.000000000 +0100
@@ -22,6 +22,11 @@
 /opt/python/cp313-cp313/bin/python -m build
 auditwheel repair --plat musllinux_1_1_x86_64 dist/awscrt-*cp313*.whl
 
+# The free-threaded build does not currently support the Limited C API or the 
stable ABI. Built them separately
+/opt/python/cp313-cp313t/bin/python -m build
+auditwheel repair --plat musllinux_1_1_x86_64 dist/awscrt-*cp313t*.whl
+# Musllinux-1-1 is EOL without python>3.13
+
 rm dist/*.whl
 cp -rv wheelhouse/* dist/
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/aws-crt-python-0.31.3/continuous-delivery/build-wheels-osx.sh 
new/aws-crt-python-0.32.0/continuous-delivery/build-wheels-osx.sh
--- old/aws-crt-python-0.31.3/continuous-delivery/build-wheels-osx.sh   
2026-03-06 22:06:47.000000000 +0100
+++ new/aws-crt-python-0.32.0/continuous-delivery/build-wheels-osx.sh   
2026-03-27 00:11:43.000000000 +0100
@@ -15,4 +15,8 @@
 # We are using the Python 3.13 stable ABI from Python 3.13 onwards because of 
deprecated functions.
 /Library/Frameworks/Python.framework/Versions/3.13/bin/python3 -m build
 
+# The free-threaded build does not currently support the Limited C API or the 
stable ABI. Built them separately
+/Library/Frameworks/PythonT.framework/Versions/3.13/bin/python3.13t -m build
+/Library/Frameworks/PythonT.framework/Versions/3.14/bin/python3.14t -m build
+
 #now you just need to run twine (that's in a different script)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/aws-crt-python-0.31.3/continuous-delivery/build-wheels-win32.bat 
new/aws-crt-python-0.32.0/continuous-delivery/build-wheels-win32.bat
--- old/aws-crt-python-0.31.3/continuous-delivery/build-wheels-win32.bat        
2026-03-06 22:06:47.000000000 +0100
+++ new/aws-crt-python-0.32.0/continuous-delivery/build-wheels-win32.bat        
2026-03-27 00:11:43.000000000 +0100
@@ -11,6 +11,10 @@
 :: We are using the 3.13 stable ABI from 3.13 onwards because of deprecated 
functions.
 "C:\Program Files (x86)\Python313-32\python.exe" -m build || goto error
 
+:: The free-threaded build does not currently support the Limited C API or the 
stable ABI. Built them separately
+"C:\Program Files (x86)\Python313-32\python3.13t.exe" -m build || goto error
+"C:\Program Files (x86)\Python314-32\python3.14t.exe" -m build || goto error
+
 goto :EOF
 
 :error
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/aws-crt-python-0.31.3/continuous-delivery/build-wheels-win64.bat 
new/aws-crt-python-0.32.0/continuous-delivery/build-wheels-win64.bat
--- old/aws-crt-python-0.31.3/continuous-delivery/build-wheels-win64.bat        
2026-03-06 22:06:47.000000000 +0100
+++ new/aws-crt-python-0.32.0/continuous-delivery/build-wheels-win64.bat        
2026-03-27 00:11:43.000000000 +0100
@@ -10,6 +10,10 @@
 :: We are using the 3.13 stable ABI from 3.13 onwards because of deprecated 
functions.
 "C:\Program Files\Python313\python.exe" -m build || goto error
 
+:: The free-threaded build does not currently support the Limited C API or the 
stable ABI. Built them separately
+"C:\Program Files\Python-313\python3.13t.exe" -m build || goto error
+"C:\Program Files\Python314\python3.14t.exe" -m build || goto error
+
 goto :EOF
 
 :error
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aws-crt-python-0.31.3/setup.py 
new/aws-crt-python-0.32.0/setup.py
--- old/aws-crt-python-0.31.3/setup.py  2026-03-06 22:06:47.000000000 +0100
+++ new/aws-crt-python-0.32.0/setup.py  2026-03-27 00:11:43.000000000 +0100
@@ -25,6 +25,9 @@
 # sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET').
 MACOS_DEPLOYMENT_TARGET_MIN = "10.15"
 
+# True if this is a free-threaded Python build.
+FREE_THREADED_BUILD = sysconfig.get_config_var("Py_GIL_DISABLED") == 1
+
 # This is the minimum version of the Windows SDK needed for schannel.h with 
SCH_CREDENTIALS and
 # TLS_PARAMETERS. These are required to build Windows Binaries with TLS 1.3 
support.
 WINDOWS_SDK_VERSION_TLS1_3_SUPPORT = "10.0.17763.0"
@@ -428,7 +431,10 @@
     def get_tag(self):
         python, abi, plat = super().get_tag()
         # on CPython, our wheels are abi3 and compatible back to 3.11
-        if python.startswith("cp") and sys.version_info >= (3, 13):
+        if FREE_THREADED_BUILD:
+            # free-threaded builds don't use limited API, so skip abi3 tag
+            return python, abi, plat
+        elif python.startswith("cp") and sys.version_info >= (3, 13):
             # 3.13 deprecates PyWeakref_GetObject(), adds alternative
             return "cp313", "abi3", plat
         elif python.startswith("cp") and sys.version_info >= (3, 11):
@@ -532,7 +538,11 @@
                     extra_link_args += ['-Wl,--fatal-warnings']
 
     # prefer building with stable ABI, so a wheel can work with multiple major 
versions
-    if sys.version_info >= (3, 13):
+    if FREE_THREADED_BUILD and sys.version_info[:2] <= (3, 14):
+        # 3.14 free threaded (aka no gil) does not support limited api.
+        # disable it for now. 3.15 promises to support limited api + free 
threading combo
+        py_limited_api = False
+    elif sys.version_info >= (3, 13):
         # 3.13 deprecates PyWeakref_GetObject(), adds alternative
         define_macros.append(('Py_LIMITED_API', '0x030D0000'))
         py_limited_api = True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aws-crt-python-0.31.3/source/cbor.c 
new/aws-crt-python-0.32.0/source/cbor.c
--- old/aws-crt-python-0.31.3/source/cbor.c     2026-03-06 22:06:47.000000000 
+0100
+++ new/aws-crt-python-0.32.0/source/cbor.c     2026-03-27 00:11:43.000000000 
+0100
@@ -160,6 +160,8 @@
     PyObject *value = NULL;
     Py_ssize_t pos = 0;
 
+    /* Accessing the pydict without lock, since the cbor_decoder and encoder 
are not thread-safe. It's user's
+     * responsibility to not modifying the map from another thread. */
     while (PyDict_Next(py_dict, &pos, &key, &value)) {
         PyObject *key_result = s_cbor_encoder_write_pyobject(encoder, key);
         if (!key_result) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aws-crt-python-0.31.3/source/http_connection.c 
new/aws-crt-python-0.32.0/source/http_connection.c
--- old/aws-crt-python-0.31.3/source/http_connection.c  2026-03-06 
22:06:47.000000000 +0100
+++ new/aws-crt-python-0.32.0/source/http_connection.c  2026-03-27 
00:11:43.000000000 +0100
@@ -152,10 +152,17 @@
     }
 
     *out_settings = aws_mem_calloc(allocator, py_list_size, sizeof(struct 
aws_http2_setting));
+    PyObject *setting_py = NULL;
+    bool strong_ref = false;
 
     for (Py_ssize_t i = 0; i < py_list_size; i++) {
-        PyObject *setting_py = PyList_GetItem(initial_settings_py, i);
+#ifdef Py_GIL_DISABLED
+        setting_py = PyList_GetItemRef(initial_settings_py, i);
+        strong_ref = true;
+#else
+        setting_py = PyList_GetItem(initial_settings_py, i); // Borrowed 
Reference
 
+#endif
         /* Get id attribute */
         enum aws_http2_settings_id id = PyObject_GetAttrAsIntEnum(setting_py, 
"Http2Setting", "id");
         if (PyErr_Occurred()) {
@@ -169,11 +176,17 @@
         }
         (*out_settings)[i].id = id;
         (*out_settings)[i].value = value;
+        if (strong_ref) {
+            Py_DECREF(setting_py);
+        }
     }
 
     *out_size = (size_t)py_list_size;
     return AWS_OP_SUCCESS;
 error:
+    if (setting_py && strong_ref) {
+        Py_DECREF(setting_py);
+    }
     *out_size = 0;
     aws_mem_release(allocator, *out_settings);
     *out_settings = NULL;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aws-crt-python-0.31.3/source/http_stream.c 
new/aws-crt-python-0.32.0/source/http_stream.c
--- old/aws-crt-python-0.31.3/source/http_stream.c      2026-03-06 
22:06:47.000000000 +0100
+++ new/aws-crt-python-0.32.0/source/http_stream.c      2026-03-27 
00:11:43.000000000 +0100
@@ -213,6 +213,24 @@
     /*************** GIL RELEASE ***************/
 }
 
+static void s_on_h2_remote_end_stream(struct aws_http_stream *native_stream, 
void *user_data) {
+    (void)native_stream;
+    struct http_stream_binding *stream = user_data;
+
+    /*************** GIL ACQUIRE ***************/
+    PyGILState_STATE state;
+    if (aws_py_gilstate_ensure(&state)) {
+        return; /* Python has shut down. Nothing matters anymore, but don't 
crash */
+    }
+
+    PyObject *result = PyObject_CallMethod(stream->self_proxy, 
"_on_h2_remote_end_stream", "()");
+    if (result) {
+        Py_DECREF(result);
+    }
+    PyGILState_Release(state);
+    /*************** GIL RELEASE ***************/
+}
+
 static void s_stream_capsule_destructor(PyObject *http_stream_capsule) {
     struct http_stream_binding *stream = 
PyCapsule_GetPointer(http_stream_capsule, s_capsule_name_http_stream);
 
@@ -283,6 +301,7 @@
         .on_response_header_block_done = s_on_incoming_header_block_done,
         .on_response_body = s_on_incoming_body,
         .on_complete = s_on_stream_complete,
+        .on_h2_remote_end_stream = s_on_h2_remote_end_stream,
         .user_data = stream,
         .http2_use_manual_data_writes = http2_manual_write,
     };
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aws-crt-python-0.31.3/source/module.c 
new/aws-crt-python-0.32.0/source/module.c
--- old/aws-crt-python-0.31.3/source/module.c   2026-03-06 22:06:47.000000000 
+0100
+++ new/aws-crt-python-0.32.0/source/module.c   2026-03-27 00:11:43.000000000 
+0100
@@ -619,7 +619,13 @@
 }
 
 int aws_py_gilstate_ensure(PyGILState_STATE *out_state) {
+    /* If Python >= 3.13 */
+#if PY_VERSION_HEX >= 0x030D0000
+    // Py_IsFinalizing is part of the Stable ABI since version 3.13.
+    if (AWS_LIKELY(!Py_IsFinalizing())) {
+#else
     if (AWS_LIKELY(Py_IsInitialized())) {
+#endif
         *out_state = PyGILState_Ensure();
         return AWS_OP_SUCCESS;
     }
@@ -1023,6 +1029,10 @@
         return NULL;
     }
 
+#ifdef Py_GIL_DISABLED
+    PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
+#endif
+
     s_init_allocator();
 
     /* Don't report this memory when dumping possible leaks. */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aws-crt-python-0.31.3/source/s3_client.c 
new/aws-crt-python-0.32.0/source/s3_client.c
--- old/aws-crt-python-0.31.3/source/s3_client.c        2026-03-06 
22:06:47.000000000 +0100
+++ new/aws-crt-python-0.32.0/source/s3_client.c        2026-03-27 
00:11:43.000000000 +0100
@@ -365,15 +365,27 @@
         network_interface_names =
             aws_mem_calloc(allocator, num_network_interface_names, 
sizeof(struct aws_byte_cursor));
         for (size_t i = 0; i < num_network_interface_names; ++i) {
-            PyObject *str_obj = PyList_GetItem(network_interface_names_py, i); 
/* Borrowed reference */
+            bool strong_ref = false;
+#ifdef Py_GIL_DISABLED
+            PyObject *str_obj = PyList_GetItemRef(network_interface_names_py, 
i);
+            strong_ref = true;
+#else
+            PyObject *str_obj = PyList_GetItem(network_interface_names_py, i); 
// Borrowed Reference
+#endif
             if (!str_obj) {
                 goto cleanup;
             }
             network_interface_names[i] = 
aws_byte_cursor_from_pyunicode(str_obj);
             if (network_interface_names[i].ptr == NULL) {
+                if (strong_ref) {
+                    Py_DECREF(str_obj);
+                }
                 PyErr_SetString(PyExc_TypeError, "Expected all 
network_interface_names elements to be strings.");
                 goto cleanup;
             }
+            if (strong_ref) {
+                Py_DECREF(str_obj);
+            }
         }
     }
     struct aws_s3_file_io_options fio_opts = {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aws-crt-python-0.31.3/test/test_aiohttp_client.py 
new/aws-crt-python-0.32.0/test/test_aiohttp_client.py
--- old/aws-crt-python-0.31.3/test/test_aiohttp_client.py       2026-03-06 
22:06:47.000000000 +0100
+++ new/aws-crt-python-0.32.0/test/test_aiohttp_client.py       2026-03-27 
00:11:43.000000000 +0100
@@ -62,11 +62,13 @@
         self.end_headers()
 
 
-class TestAsyncClient(NativeResourceTest):
+class AsyncLocalServerTestBase(NativeResourceTest):
+    """Base class for async tests that use local HTTP/1.x server"""
     hostname = 'localhost'
     timeout = 5  # seconds
 
     def _start_server(self, secure, http_1_0=False):
+        """Start local HTTP server"""
         # HTTP/1.0 closes the connection at the end of each request
         # HTTP/1.1 will keep the connection alive
         if http_1_0:
@@ -89,10 +91,14 @@
         self.server_thread.start()
 
     def _stop_server(self):
+        """Stop local HTTP server"""
         self.server.shutdown()
         self.server.server_close()
         self.server_thread.join()
 
+
+class TestAsyncClient(AsyncLocalServerTestBase):
+
     async def _new_client_connection(self, secure, proxy_options=None):
         if secure:
             tls_ctx_opt = TlsContextOptions()
@@ -473,8 +479,8 @@
         asyncio.run(self._test_cross_thread_http2_client())
 
 
[email protected](os.environ.get('AWS_TEST_LOCALHOST'), 'set env var to run 
test: AWS_TEST_LOCALHOST')
-class TestAsyncClientMockServer(NativeResourceTest):
+class AsyncMockServerTestBase(NativeResourceTest):
+    """Base class for async tests that use the H2 mock server"""
     timeout = 5  # seconds
     p_server = None
     mock_server_url = None
@@ -499,7 +505,6 @@
     def _wait_for_server_ready(self):
         """Wait until server is accepting connections."""
         max_attempts = 20
-
         for attempt in range(max_attempts):
             try:
                 with socket.create_connection(("127.0.0.1", 
self.mock_server_url.port), timeout=1):
@@ -520,6 +525,47 @@
             self.p_server.kill()
         super().tearDown()
 
+    async def _new_mock_h2_connection(
+            self,
+            manual_window_management=False,
+            initial_window_size=None,
+            initial_settings=None):
+        """Create HTTP/2 async client connection to local mock server"""
+        event_loop_group = EventLoopGroup()
+        host_resolver = DefaultHostResolver(event_loop_group)
+        bootstrap = ClientBootstrap(event_loop_group, host_resolver)
+
+        port = self.mock_server_url.port
+        if port is None:
+            port = 443
+
+        tls_ctx_options = TlsContextOptions()
+        tls_ctx_options.verify_peer = False
+        tls_ctx = ClientTlsContext(tls_ctx_options)
+        tls_conn_opt = tls_ctx.new_connection_options()
+        tls_conn_opt.set_server_name(self.mock_server_url.hostname)
+        tls_conn_opt.set_alpn_list(["h2"])
+
+        if initial_settings is None:
+            initial_settings = [Http2Setting(Http2SettingID.ENABLE_PUSH, 0)]
+
+        kwargs = {
+            'host_name': self.mock_server_url.hostname,
+            'port': port,
+            'bootstrap': bootstrap,
+            'tls_connection_options': tls_conn_opt,
+            'initial_settings': initial_settings,
+            'manual_window_management': manual_window_management
+        }
+        if initial_window_size is not None:
+            kwargs['initial_window_size'] = initial_window_size
+
+        return await AIOHttp2ClientConnection.new(**kwargs)
+
+
[email protected](os.environ.get('AWS_TEST_LOCALHOST'), 'set env var to run 
test: AWS_TEST_LOCALHOST')
+class TestAsyncClientMockServer(AsyncMockServerTestBase):
+
     def _on_remote_settings_changed(self, settings):
         # The mock server has the default settings with
         # ENABLE_PUSH = 0
@@ -659,91 +705,193 @@
         asyncio.run(self._test_h2_mock_server_settings())
 
 
-class AIOFlowControlTest(NativeResourceTest):
+class AIOFlowControlTest(AsyncLocalServerTestBase):
+    """HTTP/1.1 async flow control tests using local server"""
     timeout = 10.0
 
+    async def _new_h1_client_connection(
+            self,
+            secure,
+            manual_window_management=False,
+            initial_window_size=None,
+            read_buffer_capacity=None):
+        """Create HTTP/1.1 async client connection to local server"""
+        if secure:
+            tls_ctx_opt = TlsContextOptions()
+            tls_ctx_opt.verify_peer = False
+            tls_ctx = ClientTlsContext(tls_ctx_opt)
+            tls_conn_opt = tls_ctx.new_connection_options()
+            tls_conn_opt.set_server_name(self.hostname)
+        else:
+            tls_conn_opt = None
+
+        event_loop_group = EventLoopGroup()
+        host_resolver = DefaultHostResolver(event_loop_group)
+        bootstrap = ClientBootstrap(event_loop_group, host_resolver)
+
+        kwargs = {
+            'host_name': self.hostname,
+            'port': self.port,
+            'bootstrap': bootstrap,
+            'tls_connection_options': tls_conn_opt,
+            'manual_window_management': manual_window_management
+        }
+        if initial_window_size is not None:
+            kwargs['initial_window_size'] = initial_window_size
+        if read_buffer_capacity is not None:
+            kwargs['read_buffer_capacity'] = read_buffer_capacity
+
+        return await AIOHttpClientConnection.new(**kwargs)
+
     async def _test_h1_manual_window_management_happy_path(self):
         """Test HTTP/1.1 manual window management happy path"""
-        tls_ctx_opt = TlsContextOptions()
-        tls_ctx_opt.verify_peer = False
-        tls_ctx_opt.alpn_list = ['http/1.1']
-        tls_ctx = ClientTlsContext(tls_ctx_opt)
-        tls_options = tls_ctx.new_connection_options()
-        tls_options.set_server_name("httpbin.org")
+        self._start_server(secure=True)
+        try:
+            connection = await self._new_h1_client_connection(
+                secure=True,
+                manual_window_management=True,
+                initial_window_size=5,
+                read_buffer_capacity=1000
+            )
 
-        connection = await AIOHttpClientConnection.new(
-            host_name="httpbin.org",
-            port=443,
-            tls_connection_options=tls_options,
-            manual_window_management=True,
-            initial_window_size=5,
-            read_buffer_capacity=1000
-        )
+            # Create a file with known size for testing
+            test_data = b'0123456789'  # 10 bytes
+            test_file_path = 'test_aio_flow_control_data.txt'
+            with open(test_file_path, 'wb') as f:
+                f.write(test_data)
 
-        request = HttpRequest('GET', '/bytes/10')
-        request.headers.add('host', 'httpbin.org')
-        stream = connection.request(request)
+            try:
+                request = HttpRequest('GET', '/' + test_file_path)
+                request.headers.add('host', self.hostname)
+                stream = connection.request(request)
 
-        response = Response()
-        status_code = await response.collect_response(stream)
+                chunks_received = []
+                body = bytearray()
 
-        self.assertEqual(200, status_code)
-        self.assertEqual(10, len(response.body))
-        await connection.close()
+                await stream.get_response_status_code()
+                while True:
+                    chunk = await stream.get_next_response_chunk()
+                    if not chunk:
+                        break
+                    chunks_received.append(len(chunk))
+                    body.extend(chunk)
+                    stream.update_window(len(chunk))
+
+                self.assertEqual(test_data, bytes(body))
+                self.assertGreater(len(chunks_received), 0, "No data chunks 
received")
+
+                await connection.close()
+            finally:
+                # Clean up test file
+                if os.path.exists(test_file_path):
+                    os.remove(test_file_path)
+        finally:
+            self._stop_server()
 
     def test_h1_manual_window_management_happy_path(self):
         asyncio.run(self._test_h1_manual_window_management_happy_path())
 
+    async def _test_h1_stream_flow_control_blocks_and_resumes(self):
+        """Test that HTTP/1.1 stream flow control actually blocks and 
resumes"""
+        self._start_server(secure=True)
+        try:
+            connection = await self._new_h1_client_connection(
+                secure=True,
+                manual_window_management=True,
+                initial_window_size=1,
+                read_buffer_capacity=1000
+            )
+
+            # Create a file with 100 bytes for testing
+            test_data = bytes(range(100))
+            test_file_path = 'test_aio_flow_control_100.txt'
+            with open(test_file_path, 'wb') as f:
+                f.write(test_data)
+
+            try:
+                request = HttpRequest('GET', '/' + test_file_path)
+                request.headers.add('host', self.hostname)
+                stream = connection.request(request)
+
+                chunks_received = []
+                body = bytearray()
+
+                await stream.get_response_status_code()
+                while True:
+                    chunk = await stream.get_next_response_chunk()
+                    if not chunk:
+                        break
+                    chunks_received.append(len(chunk))
+                    body.extend(chunk)
+                    stream.update_window(len(chunk))
+
+                self.assertEqual(test_data, bytes(body))
+                # With window=1, we should receive many small chunks
+                self.assertGreater(len(chunks_received), 1, "Should receive 
multiple chunks with tiny window")
+
+                await connection.close()
+            finally:
+                # Clean up test file
+                if os.path.exists(test_file_path):
+                    os.remove(test_file_path)
+        finally:
+            self._stop_server()
+
+    def test_h1_stream_flow_control_blocks_and_resumes(self):
+        asyncio.run(self._test_h1_stream_flow_control_blocks_and_resumes())
+
+
[email protected](os.environ.get('AWS_TEST_LOCALHOST'), 'set env var to run 
test: AWS_TEST_LOCALHOST')
+class AIOFlowControlH2Test(AsyncMockServerTestBase):
+    """HTTP/2 async flow control tests using local mock server"""
+    timeout = 10.0
+
     async def _test_h2_manual_window_management_happy_path(self):
         """Test HTTP/2 manual window management happy path"""
-        tls_ctx_opt = TlsContextOptions()
-        tls_ctx_opt.verify_peer = False
-        tls_ctx_opt.alpn_list = ['h2']
-        tls_ctx = ClientTlsContext(tls_ctx_opt)
-        tls_options = tls_ctx.new_connection_options()
-        tls_options.set_server_name("httpbin.org")
-
-        connection = await AIOHttp2ClientConnection.new(
-            host_name="httpbin.org",
-            port=443,
-            tls_connection_options=tls_options,
+        connection = await self._new_mock_h2_connection(
             manual_window_management=True,
             initial_window_size=65536
         )
 
-        request = HttpRequest('GET', '/get')
-        request.headers.add('host', 'httpbin.org')
+        # GET request with x-repeat-data header to download data
+        request = HttpRequest('GET', self.mock_server_url.path)
+        request.headers.add('host', self.mock_server_url.hostname)
+        request.headers.add('x-repeat-data', '100')  # Request 100 bytes of 
data
+
         stream = connection.request(request)
 
-        response = Response()
-        status_code = await response.collect_response(stream)
+        chunks_received = []
+        body = bytearray()
+
+        await stream.get_response_status_code()
+        while True:
+            chunk = await stream.get_next_response_chunk()
+            if not chunk:
+                break
+            chunks_received.append(len(chunk))
+            body.extend(chunk)
+            stream.update_window(len(chunk))
+
+        self.assertGreater(len(body), 0, "No response body received")
+        self.assertGreater(len(chunks_received), 0, "No data chunks received")
 
-        self.assertEqual(200, status_code)
-        self.assertGreater(len(response.body), 0, "No data received")
         await connection.close()
 
     def test_h2_manual_window_management_happy_path(self):
         asyncio.run(self._test_h2_manual_window_management_happy_path())
 
     async def _test_h2_stream_flow_control_blocks_and_resumes(self):
-        """Test that stream flow control actually blocks and resumes"""
-        tls_ctx_opt = TlsContextOptions()
-        tls_ctx_opt.verify_peer = False
-        tls_ctx_opt.alpn_list = ['h2']
-        tls_ctx = ClientTlsContext(tls_ctx_opt)
-        tls_options = tls_ctx.new_connection_options()
-        tls_options.set_server_name("httpbin.org")
-
-        connection = await AIOHttp2ClientConnection.new(
-            host_name="httpbin.org",
-            port=443,
-            tls_connection_options=tls_options,
+        """Test that HTTP/2 stream flow control actually blocks and resumes"""
+        connection = await self._new_mock_h2_connection(
             manual_window_management=True,
-            initial_window_size=10  # Tiny window
+            initial_window_size=10  # Small window to force multiple chunks
         )
 
-        request = HttpRequest('GET', '/bytes/100')
-        request.headers.add('host', 'httpbin.org')
+        # GET request with x-repeat-data header to download data
+        request = HttpRequest('GET', self.mock_server_url.path)
+        request.headers.add('host', self.mock_server_url.hostname)
+        request.headers.add('x-repeat-data', '100')  # Request 100 bytes of 
data
+
         stream = connection.request(request)
 
         chunks_received = []
@@ -758,53 +906,89 @@
             body.extend(chunk)
             stream.update_window(len(chunk))
 
-        self.assertEqual(100, len(body))
-        self.assertEqual(len(chunks_received), 10, "Should receive exactly 10 
chunks")
+        self.assertGreater(len(body), 0, "No response body received")
+        # With small window, we should receive multiple chunks
+        self.assertGreater(len(chunks_received), 1, "Should receive multiple 
chunks with small window")
+
         await connection.close()
 
     def test_h2_stream_flow_control_blocks_and_resumes(self):
         asyncio.run(self._test_h2_stream_flow_control_blocks_and_resumes())
 
-    async def _test_h1_stream_flow_control_blocks_and_resumes(self):
-        """Test that HTTP/1.1 stream flow control actually blocks and 
resumes"""
-        tls_ctx_opt = TlsContextOptions()
-        tls_ctx_opt.verify_peer = False
-        tls_ctx_opt.alpn_list = ['http/1.1']
-        tls_ctx = ClientTlsContext(tls_ctx_opt)
-        tls_options = tls_ctx.new_connection_options()
-        tls_options.set_server_name("httpbin.org")
 
-        connection = await AIOHttpClientConnection.new(
+class AIOHttp2RemoteEndStreamTest(NativeResourceTest):
+    """Test suite for HTTP/2 on_h2_remote_end_stream callback in asyncio"""
+    timeout = 10.0
+
+    async def _new_httpbin_h2_connection(self):
+        """Create HTTP/2 connection to httpbin.org"""
+        event_loop_group = EventLoopGroup()
+        host_resolver = DefaultHostResolver(event_loop_group)
+        bootstrap = ClientBootstrap(event_loop_group, host_resolver)
+
+        tls_ctx_options = TlsContextOptions()
+        tls_ctx = ClientTlsContext(tls_ctx_options)
+        tls_conn_opt = tls_ctx.new_connection_options()
+        tls_conn_opt.set_server_name("httpbin.org")
+        tls_conn_opt.set_alpn_list(["h2"])
+
+        connection = await AIOHttp2ClientConnection.new(
             host_name="httpbin.org",
             port=443,
-            tls_connection_options=tls_options,
-            manual_window_management=True,
-            initial_window_size=1,  # Tiny window
-            read_buffer_capacity=1000
-        )
+            bootstrap=bootstrap,
+            tls_connection_options=tls_conn_opt)
+
+        return connection
+
+    async def _test_h2_remote_end_stream_ordering(self):
+        """Test that on_h2_remote_end_stream fires before on_complete when 
server finishes first"""
+        connection = await self._new_httpbin_h2_connection()
 
-        request = HttpRequest('GET', '/bytes/100')
+        # Use httpbin.org 404 path - server responds immediately
+        request = HttpRequest('POST', 
'/this-path-does-not-exist-deliberately-404')
         request.headers.add('host', 'httpbin.org')
-        stream = connection.request(request)
 
-        chunks_received = []
-        body = bytearray()
+        complete_success = asyncio.Event()
+        remote_finished = asyncio.Event()
+        complete_fired = asyncio.Event()
+
+        async def slow_body_generator():
+            # Send first chunk WITHOUT end_stream
+            yield b'chunk1'
+            # Wait for server to finish
+            await remote_finished.wait()
+            if not complete_fired.is_set():
+                # Verify complete hasn't fired yet
+                complete_success.set()
+            # Now finish sending
+            yield b'chunk2'
+
+        stream = connection.request(request, 
request_body_generator=slow_body_generator())
+
+        # Read response
+        status_code = await stream.get_response_status_code()
+        self.assertEqual(404, status_code)
 
-        await stream.get_response_status_code()
+        # Read all response body
         while True:
             chunk = await stream.get_next_response_chunk()
             if not chunk:
                 break
-            chunks_received.append(len(chunk))
-            body.extend(chunk)
-            stream.update_window(len(chunk))
 
-        self.assertEqual(100, len(body))
-        self.assertEqual(len(chunks_received), 100, "Should receive exactly 
100 chunks")
+        # set remove stream
+        remote_finished.set()
+
+        # Wait for stream to complete successfully
+        await stream.wait_for_completion()
+        complete_fired.set()
+
+        self.assertTrue(complete_success.is_set())
+
         await connection.close()
 
-    def test_h1_stream_flow_control_blocks_and_resumes(self):
-        asyncio.run(self._test_h1_stream_flow_control_blocks_and_resumes())
+    def test_h2_remote_end_stream_ordering(self):
+        """Test callback ordering with early server response"""
+        asyncio.run(self._test_h2_remote_end_stream_ordering())
 
 
 if __name__ == '__main__':
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/aws-crt-python-0.31.3/test/test_http_client.py 
new/aws-crt-python-0.32.0/test/test_http_client.py
--- old/aws-crt-python-0.31.3/test/test_http_client.py  2026-03-06 
22:06:47.000000000 +0100
+++ new/aws-crt-python-0.32.0/test/test_http_client.py  2026-03-27 
00:11:43.000000000 +0100
@@ -48,11 +48,13 @@
         self.end_headers()
 
 
-class TestClient(NativeResourceTest):
+class LocalServerTestBase(NativeResourceTest):
+    """Base class for tests that use local HTTP/1.x server"""
     hostname = 'localhost'
     timeout = 5  # seconds
 
     def _start_server(self, secure, http_1_0=False):
+        """Start local HTTP server"""
         # HTTP/1.0 closes the connection at the end of each request
         # HTTP/1.1 will keep the connection alive
         if http_1_0:
@@ -74,10 +76,14 @@
         self.server_thread.start()
 
     def _stop_server(self):
+        """Stop local HTTP server"""
         self.server.shutdown()
         self.server.server_close()
         self.server_thread.join()
 
+
+class TestClient(LocalServerTestBase):
+
     def _new_client_connection(self, secure, proxy_options=None, 
cipher_pref=TlsCipherPref.DEFAULT):
         if secure:
             tls_ctx_opt = TlsContextOptions()
@@ -402,16 +408,15 @@
         self._test_connect(secure=True, cipher_pref=TlsCipherPref.PQ_DEFAULT)
 
 
[email protected](os.environ.get('AWS_TEST_LOCALHOST'), 'set env var to run 
test: AWS_TEST_LOCALHOST')
-class TestClientMockServer(NativeResourceTest):
-
+class MockServerTestBase(NativeResourceTest):
+    """Base class for tests that use the H2 mock server"""
     timeout = 5  # seconds
     p_server = None
     mock_server_url = None
 
     def setUp(self):
         super().setUp()
-        # Start the mock server from the aws-c-http.
+        # Start the mock server from the aws-c-http
         server_path = os.path.join(
             os.path.dirname(__file__),
             '..',
@@ -429,7 +434,6 @@
     def _wait_for_server_ready(self):
         """Wait until server is accepting connections."""
         max_attempts = 20
-
         for attempt in range(max_attempts):
             try:
                 with socket.create_connection(("127.0.0.1", 
self.mock_server_url.port), timeout=1):
@@ -450,13 +454,49 @@
             self.p_server.kill()
         super().tearDown()
 
+    def _new_mock_h2_connection(self, manual_window_management=False, 
initial_window_size=None, initial_settings=None):
+        """Create HTTP/2 client connection to local mock server"""
+        event_loop_group = EventLoopGroup()
+        host_resolver = DefaultHostResolver(event_loop_group)
+        bootstrap = ClientBootstrap(event_loop_group, host_resolver)
+
+        port = self.mock_server_url.port
+        if port is None:
+            port = 443
+
+        tls_ctx_options = TlsContextOptions()
+        tls_ctx_options.verify_peer = False
+        tls_ctx = ClientTlsContext(tls_ctx_options)
+        tls_conn_opt = tls_ctx.new_connection_options()
+        tls_conn_opt.set_server_name(self.mock_server_url.hostname)
+        tls_conn_opt.set_alpn_list(["h2"])
+
+        if initial_settings is None:
+            initial_settings = [Http2Setting(Http2SettingID.ENABLE_PUSH, 0)]
+
+        kwargs = {
+            'host_name': self.mock_server_url.hostname,
+            'port': port,
+            'bootstrap': bootstrap,
+            'tls_connection_options': tls_conn_opt,
+            'initial_settings': initial_settings,
+            'manual_window_management': manual_window_management
+        }
+        if initial_window_size is not None:
+            kwargs['initial_window_size'] = initial_window_size
+
+        connection_future = Http2ClientConnection.new(**kwargs)
+        return connection_future.result(self.timeout)
+
+
[email protected](os.environ.get('AWS_TEST_LOCALHOST'), 'set env var to run 
test: AWS_TEST_LOCALHOST')
+class TestClientMockServer(MockServerTestBase):
+
     def _on_remote_settings_changed(self, settings):
         # The mock server has the default settings with
         # ENABLE_PUSH = 0
         # MAX_CONCURRENT_STREAMS = 100
         # MAX_HEADER_LIST_SIZE = 2**16
-        # using [email protected], code can be found in
-        # 
https://github.com/python-hyper/h2/blob/191ac06e0949fcfe3367b06eeb101a5a1a335964/src/h2/connection.py#L340-L359
         # Check the settings here
         self.assertEqual(len(settings), 3)
         for i in settings:
@@ -468,31 +508,32 @@
                 self.assertEqual(i.value, 2**16)
 
     def _new_mock_connection(self, initial_settings=None):
+        """Create connection with remote settings callback"""
+        kwargs = {'initial_settings': initial_settings}
+        connection_future = Http2ClientConnection.new(
+            host_name=self.mock_server_url.hostname,
+            port=self.mock_server_url.port if self.mock_server_url.port else 
443,
+            bootstrap=self._create_client_bootstrap(),
+            tls_connection_options=self._create_tls_connection_options(),
+            initial_settings=initial_settings if initial_settings else 
[Http2Setting(Http2SettingID.ENABLE_PUSH, 0)],
+            on_remote_settings_changed=self._on_remote_settings_changed)
+        return connection_future.result(self.timeout)
 
+    def _create_client_bootstrap(self):
+        """Create client bootstrap"""
         event_loop_group = EventLoopGroup()
         host_resolver = DefaultHostResolver(event_loop_group)
-        bootstrap = ClientBootstrap(event_loop_group, host_resolver)
+        return ClientBootstrap(event_loop_group, host_resolver)
 
-        port = self.mock_server_url.port
-        # only test https
-        if port is None:
-            port = 443
+    def _create_tls_connection_options(self):
+        """Create TLS connection options for mock server"""
         tls_ctx_options = TlsContextOptions()
-        tls_ctx_options.verify_peer = False  # allow localhost
+        tls_ctx_options.verify_peer = False
         tls_ctx = ClientTlsContext(tls_ctx_options)
         tls_conn_opt = tls_ctx.new_connection_options()
         tls_conn_opt.set_server_name(self.mock_server_url.hostname)
         tls_conn_opt.set_alpn_list(["h2"])
-        if initial_settings is None:
-            initial_settings = [Http2Setting(Http2SettingID.ENABLE_PUSH, 0)]
-
-        connection_future = 
Http2ClientConnection.new(host_name=self.mock_server_url.hostname,
-                                                      port=port,
-                                                      bootstrap=bootstrap,
-                                                      
tls_connection_options=tls_conn_opt,
-                                                      
initial_settings=initial_settings,
-                                                      
on_remote_settings_changed=self._on_remote_settings_changed)
-        return connection_future.result(self.timeout)
+        return tls_conn_opt
 
     def test_h2_mock_server_manual_write(self):
         connection = self._new_mock_connection()
@@ -652,177 +693,272 @@
         self.body.extend(chunk)
 
 
-class FlowControlTest(NativeResourceTest):
+class FlowControlTest(LocalServerTestBase):
+    """HTTP/1.1 flow control tests using local server"""
     timeout = 10.0
 
-    def setUp(self):
-        super().setUp()
-        tls_ctx_opt = TlsContextOptions()
-        tls_ctx_opt.verify_peer = False
-        tls_ctx_opt.alpn_list = ['h2', 'http/1.1']
-        tls_ctx = ClientTlsContext(tls_ctx_opt)
-        self.tls_options = tls_ctx.new_connection_options()
-        self.tls_options.set_server_name("httpbin.org")
+    def _new_h1_client_connection(
+            self,
+            secure,
+            manual_window_management=False,
+            initial_window_size=None,
+            read_buffer_capacity=None):
+        """Create HTTP/1.1 client connection to local server"""
+        if secure:
+            tls_ctx_opt = TlsContextOptions()
+            tls_ctx_opt.verify_peer = False
+            tls_ctx = ClientTlsContext(tls_ctx_opt)
+            tls_conn_opt = tls_ctx.new_connection_options()
+            tls_conn_opt.set_server_name(self.hostname)
+        else:
+            tls_conn_opt = None
+
+        event_loop_group = EventLoopGroup()
+        host_resolver = DefaultHostResolver(event_loop_group)
+        bootstrap = ClientBootstrap(event_loop_group, host_resolver)
+
+        kwargs = {
+            'host_name': self.hostname,
+            'port': self.port,
+            'bootstrap': bootstrap,
+            'tls_connection_options': tls_conn_opt,
+            'manual_window_management': manual_window_management
+        }
+        if initial_window_size is not None:
+            kwargs['initial_window_size'] = initial_window_size
+        if read_buffer_capacity is not None:
+            kwargs['read_buffer_capacity'] = read_buffer_capacity
+
+        connection_future = HttpClientConnection.new(**kwargs)
+        return connection_future.result(self.timeout)
 
     def test_h1_manual_window_management_happy_path(self):
         """Test HTTP/1.1 manual window management happy path"""
-        connection_future = HttpClientConnection.new(
-            host_name="httpbin.org",
-            port=443,
-            tls_connection_options=self.tls_options,
-            manual_window_management=True,
-            initial_window_size=5,
-            read_buffer_capacity=1000
-        )
-
+        self._start_server(secure=True)
         try:
-            connection = connection_future.result(timeout=self.timeout)
-            request = HttpRequest('GET', '/bytes/10')
-            request.headers.add('host', 'httpbin.org')
+            connection = self._new_h1_client_connection(
+                secure=True,
+                manual_window_management=True,
+                initial_window_size=5,
+                read_buffer_capacity=1000
+            )
+
+            # Create a file with known size for testing
+            test_data = b'0123456789'  # 10 bytes
+            test_file_path = 'test_flow_control_data.txt'
+            with open(test_file_path, 'wb') as f:
+                f.write(test_data)
 
-            response = Response()
-            received_chunks = []
-            window_updates_sent = []
+            try:
+                request = HttpRequest('GET', '/' + test_file_path)
+                request.headers.add('host', self.hostname)
 
-            def on_body_with_window_update(http_stream, chunk, **kwargs):
-                received_chunks.append(len(chunk))
-                response.body.extend(chunk)
-                if hasattr(http_stream, '_binding') and http_stream._binding:
+                response = Response()
+                received_chunks = []
+                window_updates_sent = []
+
+                def on_body_with_window_update(http_stream, chunk, **kwargs):
+                    received_chunks.append(len(chunk))
+                    response.body.extend(chunk)
                     http_stream.update_window(len(chunk))
                     window_updates_sent.append(len(chunk))
 
-            stream = connection.request(request, response.on_response, 
on_body_with_window_update)
-            stream.activate()
-            stream_completion_result = 
stream.completion_future.result(timeout=self.timeout)
+                stream = connection.request(request, response.on_response, 
on_body_with_window_update)
+                stream.activate()
+                stream_completion_result = 
stream.completion_future.result(timeout=self.timeout)
 
-            self.assertEqual(200, response.status_code)
-            self.assertEqual(200, stream_completion_result)
-            self.assertEqual(10, len(response.body))
-
-            if len(response.body) > 0:
+                self.assertEqual(200, response.status_code)
+                self.assertEqual(200, stream_completion_result)
+                self.assertEqual(test_data, bytes(response.body))
                 self.assertGreater(len(received_chunks), 0, "No data chunks 
received")
                 self.assertGreater(len(window_updates_sent), 0, "No window 
updates sent")
                 self.assertEqual(sum(received_chunks), 
sum(window_updates_sent),
                                  "Window updates don't match received data")
 
-            connection.close()
-        except Exception as e:
-            self.skipTest(f"HTTP/1.1 flow control test skipped due to 
connection issue: {e}")
+                self.assertEqual(None, connection.close().result(self.timeout))
+            finally:
+                # Clean up test file
+                if os.path.exists(test_file_path):
+                    os.remove(test_file_path)
+        finally:
+            self._stop_server()
+
+    def test_h1_stream_flow_control_blocks_and_resumes(self):
+        """Test that HTTP/1.1 stream flow control actually blocks and 
resumes"""
+        self._start_server(secure=True)
+        try:
+            connection = self._new_h1_client_connection(
+                secure=True,
+                manual_window_management=True,
+                initial_window_size=1,
+                read_buffer_capacity=1000
+            )
+
+            # Create a file with 100 bytes for testing
+            test_data = bytes(range(100))
+            test_file_path = 'test_flow_control_100.txt'
+            with open(test_file_path, 'wb') as f:
+                f.write(test_data)
+
+            try:
+                request = HttpRequest('GET', '/' + test_file_path)
+                request.headers.add('host', self.hostname)
+
+                response = Response()
+                chunks_received = []
+
+                def on_body(http_stream, chunk, **kwargs):
+                    chunks_received.append(len(chunk))
+                    response.body.extend(chunk)
+                    http_stream.update_window(len(chunk))
+
+                stream = connection.request(request, response.on_response, 
on_body)
+                stream.activate()
+                stream.completion_future.result(timeout=self.timeout)
+
+                self.assertEqual(test_data, bytes(response.body))
+                # With window=1, we should receive many small chunks
+                self.assertGreater(len(chunks_received), 1, "Should receive 
multiple chunks with tiny window")
+
+                self.assertEqual(None, connection.close().result(self.timeout))
+            finally:
+                # Clean up test file
+                if os.path.exists(test_file_path):
+                    os.remove(test_file_path)
+        finally:
+            self._stop_server()
+
+
[email protected](os.environ.get('AWS_TEST_LOCALHOST'), 'set env var to run 
test: AWS_TEST_LOCALHOST')
+class FlowControlH2Test(MockServerTestBase):
+    """HTTP/2 flow control tests using local mock server"""
+    timeout = 10.0
 
     def test_h2_manual_window_management_happy_path(self):
         """Test HTTP/2 manual window management happy path"""
-        connection_future = Http2ClientConnection.new(
-            host_name="nghttp2.org",
-            port=443,
-            tls_connection_options=self.tls_options,
+        connection = self._new_mock_h2_connection(
             manual_window_management=True,
             initial_window_size=65536
         )
 
-        try:
-            connection = connection_future.result(timeout=self.timeout)
-            request = HttpRequest('GET', '/httpbin/get')
-            request.headers.add('host', 'nghttp2.org')
+        # GET request with x-repeat-data header to download data
+        request = HttpRequest('GET', self.mock_server_url.path)
+        request.headers.add('host', self.mock_server_url.hostname)
+        request.headers.add('x-repeat-data', '100')  # Request 100 bytes of 
data
 
-            response = Response()
-            received_chunks = []
-            window_updates_sent = []
+        response = Response()
+        received_chunks = []
+        window_updates_sent = []
 
-            def on_body_with_window_update(http_stream, chunk, **kwargs):
-                received_chunks.append(len(chunk))
-                response.body.extend(chunk)
-                if hasattr(http_stream, '_binding') and http_stream._binding:
-                    http_stream.update_window(len(chunk))
-                    window_updates_sent.append(len(chunk))
+        def on_body_with_window_update(http_stream, chunk, **kwargs):
+            received_chunks.append(len(chunk))
+            response.body.extend(chunk)
+            http_stream.update_window(len(chunk))
+            window_updates_sent.append(len(chunk))
 
-            stream = connection.request(request, response.on_response, 
on_body_with_window_update)
-            stream.activate()
-            stream_completion_result = 
stream.completion_future.result(timeout=self.timeout)
+        stream = connection.request(request, response.on_response, 
on_body_with_window_update)
+        stream.activate()
 
-            self.assertEqual(200, response.status_code)
-            self.assertEqual(200, stream_completion_result)
-            self.assertGreater(len(received_chunks), 0, "No data chunks 
received")
-            self.assertGreater(len(window_updates_sent), 0, "No window updates 
sent")
+        stream_completion_result = 
stream.completion_future.result(timeout=self.timeout)
 
-            connection.close()
-        except Exception as e:
-            self.skipTest(f"HTTP/2 flow control test skipped due to connection 
issue: {e}")
+        self.assertEqual(200, response.status_code)
+        self.assertEqual(200, stream_completion_result)
+        self.assertGreater(len(response.body), 0, "No response body received")
+        self.assertGreater(len(received_chunks), 0, "No data chunks received")
+        self.assertGreater(len(window_updates_sent), 0, "No window updates 
sent")
+
+        self.assertEqual(None, connection.close().result(self.timeout))
 
     def test_h2_stream_flow_control_blocks_and_resumes(self):
-        """Test that stream flow control actually blocks and resumes"""
-        connection_future = Http2ClientConnection.new(
-            host_name="httpbin.org",
-            port=443,
-            tls_connection_options=self.tls_options,
+        """Test that HTTP/2 stream flow control actually blocks and resumes"""
+        connection = self._new_mock_h2_connection(
             manual_window_management=True,
-            initial_window_size=1  # Tiny window - will block immediately
+            initial_window_size=10  # Small window to force multiple chunks
         )
 
-        try:
-            connection = connection_future.result(timeout=self.timeout)
-            request = HttpRequest('GET', '/bytes/100')
-            request.headers.add('host', 'httpbin.org')
+        # GET request with x-repeat-data header to download data
+        request = HttpRequest('GET', self.mock_server_url.path)
+        request.headers.add('host', self.mock_server_url.hostname)
+        request.headers.add('x-repeat-data', '100')  # Request 100 bytes of 
data
 
-            response = Response()
-            chunks_received = []
+        response = Response()
+        chunks_received = []
 
-            def on_body(http_stream, chunk, **kwargs):
-                chunks_received.append(len(chunk))
-                response.body.extend(chunk)
-                # Update window to allow more data
-                http_stream.update_window(len(chunk))
+        def on_body(http_stream, chunk, **kwargs):
+            chunks_received.append(len(chunk))
+            response.body.extend(chunk)
+            # Update window to allow more data
+            http_stream.update_window(len(chunk))
 
-            stream = connection.request(request, response.on_response, on_body)
-            stream.activate()
-            stream.completion_future.result(timeout=self.timeout)
+        stream = connection.request(request, response.on_response, on_body)
+        stream.activate()
 
-            self.assertEqual(100, len(response.body))
-            # With window=10, we should receive many small chunks
-            self.assertEqual(len(chunks_received), 100, "Expected multiple 
chunks with tiny window")
+        stream.completion_future.result(timeout=self.timeout)
 
-            connection.close()
-        except Exception as e:
-            self.skipTest(f"HTTP/2 flow control test skipped: {e}")
+        self.assertEqual(200, response.status_code)
+        self.assertGreater(len(response.body), 0, "No response body received")
+        # With small window, we should receive multiple chunks
+        self.assertGreater(len(chunks_received), 1, "Should receive multiple 
chunks with small window")
 
-    def test_h1_stream_flow_control_blocks_and_resumes(self):
-        """Test that HTTP/1.1 stream flow control actually blocks and 
resumes"""
-        connection_future = HttpClientConnection.new(
+        self.assertEqual(None, connection.close().result(self.timeout))
+
+
+class Http2RemoteEndStreamTest(NativeResourceTest):
+    """Test suite for HTTP/2 on_h2_remote_end_stream callback"""
+    timeout = 10  # seconds
+
+    def _new_httpbin_h2_connection(self):
+        """Create HTTP/2 connection to httpbin.org"""
+        event_loop_group = EventLoopGroup()
+        host_resolver = DefaultHostResolver(event_loop_group)
+        bootstrap = ClientBootstrap(event_loop_group, host_resolver)
+
+        tls_ctx_options = TlsContextOptions()
+        tls_ctx = ClientTlsContext(tls_ctx_options)
+        tls_conn_opt = tls_ctx.new_connection_options()
+        tls_conn_opt.set_server_name("httpbin.org")
+        tls_conn_opt.set_alpn_list(["h2"])
+
+        connection_future = Http2ClientConnection.new(
             host_name="httpbin.org",
             port=443,
-            tls_connection_options=self.tls_options,
-            manual_window_management=True,
-            initial_window_size=1,  # Tiny window
-            read_buffer_capacity=1000
-        )
-
-        try:
-            connection = connection_future.result(timeout=self.timeout)
-            request = HttpRequest('GET', '/bytes/100')
-            request.headers.add('host', 'httpbin.org')
+            bootstrap=bootstrap,
+            tls_connection_options=tls_conn_opt)
 
-            response = Response()
-            chunks_received = []
+        return connection_future.result(self.timeout)
 
-            def on_body(http_stream, chunk, **kwargs):
-                chunks_received.append(len(chunk))
-                response.body.extend(chunk)
-                http_stream.update_window(len(chunk))
+    def test_h2_remote_end_stream_ordering(self):
+        """Test that on_h2_remote_end_stream fires before on_complete when 
server finishes first"""
+        connection = self._new_httpbin_h2_connection()
+
+        # Use httpbin.org 404 path - server responds immediately
+        request = HttpRequest('POST', 
'/this-path-does-not-exist-deliberately-404')
+        request.headers.add('host', 'httpbin.org')
+        response = Response()
 
-            stream = connection.request(request, response.on_response, on_body)
-            stream.activate()
-            stream.completion_future.result(timeout=self.timeout)
+        stream = connection.request(request, response.on_response, 
response.on_body, manual_write=True)
+        stream.activate()
 
-            self.assertEqual(100, len(response.body))
-            # With window=1, we should receive many small chunks
-            self.assertEqual(len(chunks_received), 100, "Should receive 
exactly 100 chunks")
+        # Send first chunk WITHOUT end_stream - keeping the client side open
+        stream.write_data(BytesIO(b'chunk1'), 
end_stream=False).result(self.timeout)
 
-            connection.close()
-        except Exception as e:
-            self.skipTest(f"HTTP/1.1 flow control test skipped: {e}")
+        # Wait for server to finish (remote_end_stream_future completes)
+        # Server will respond immediately with 404 and send END_STREAM
+        remote_result = stream.remote_end_stream_future.result(self.timeout)
+        self.assertIsNone(remote_result, "remote_end_stream_future should 
complete with None")
+
+        # Verify completion_future has NOT completed yet (stream still open)
+        self.assertFalse(stream.completion_future.done(),
+                         "completion_future should not be done until client 
closes")
+
+        # Now send final chunk WITH end_stream to close client side
+        stream.write_data(BytesIO(b'chunk2'), 
end_stream=True).result(self.timeout)
+
+        # Wait for stream completion
+        completion_result = stream.completion_future.result(self.timeout)
+        self.assertEqual(404, completion_result, "Should get 404 status code")
 
-    def tearDown(self):
-        self.tls_options = None
-        super().tearDown()
+        connection.close().result(self.timeout)
 
 
 if __name__ == '__main__':

++++++ skip-test-requiring-network.patch ++++++
--- /var/tmp/diff_new_pack.lIjOeY/_old  2026-04-09 17:48:24.406385239 +0200
+++ /var/tmp/diff_new_pack.lIjOeY/_new  2026-04-09 17:48:24.418385738 +0200
@@ -1,7 +1,7 @@
-diff -Nru aws-crt-python-0.31.2.orig/test/test_aiohttp_client.py 
aws-crt-python-0.31.2/test/test_aiohttp_client.py
---- aws-crt-python-0.31.2.orig/test/test_aiohttp_client.py     2026-02-12 
19:06:36.000000000 +0100
-+++ aws-crt-python-0.31.2/test/test_aiohttp_client.py  2026-02-15 
11:34:39.055485729 +0100
-@@ -290,6 +290,7 @@
+diff -Nru aws-crt-python-0.32.0.orig/test/test_aiohttp_client.py 
aws-crt-python-0.32.0/test/test_aiohttp_client.py
+--- aws-crt-python-0.32.0.orig/test/test_aiohttp_client.py     2026-03-27 
00:11:43.000000000 +0100
++++ aws-crt-python-0.32.0/test/test_aiohttp_client.py  2026-04-09 
13:47:45.883983789 +0200
+@@ -296,6 +296,7 @@
  
          return connection
  
@@ -9,7 +9,7 @@
      async def _test_h2_client(self):
          url = 
urlparse("https://d1cz66xoahf9cl.cloudfront.net/http_test_doc.txt";)
          connection = await self._new_h2_client_connection(url)
-@@ -311,6 +312,7 @@
+@@ -317,6 +318,7 @@
  
          await connection.close()
  
@@ -17,7 +17,7 @@
      async def _test_h2_manual_write_exception(self):
          url = 
urlparse("https://d1cz66xoahf9cl.cloudfront.net/http_test_doc.txt";)
          connection = await self._new_h2_client_connection(url)
-@@ -430,6 +432,7 @@
+@@ -436,6 +438,7 @@
          finally:
              self._stop_server()
  
@@ -25,42 +25,50 @@
      async def _test_cross_thread_http2_client(self):
          """Test using an HTTP/2 client from a different thread/event loop."""
          url = 
urlparse("https://d1cz66xoahf9cl.cloudfront.net/http_test_doc.txt";)
-@@ -691,6 +694,7 @@
-         self.assertEqual(10, len(response.body))
-         await connection.close()
+@@ -743,6 +746,7 @@
+ 
+         return await AIOHttpClientConnection.new(**kwargs)
  
 +    @unittest.skip("Requires network")
+     async def _test_h1_manual_window_management_happy_path(self):
+         """Test HTTP/1.1 manual window management happy path"""
+         self._start_server(secure=True)
+@@ -791,6 +795,7 @@
      def test_h1_manual_window_management_happy_path(self):
          asyncio.run(self._test_h1_manual_window_management_happy_path())
  
-@@ -722,6 +726,7 @@
-         self.assertGreater(len(response.body), 0, "No data received")
-         await connection.close()
++    @unittest.skip("Requires network")
+     async def _test_h1_stream_flow_control_blocks_and_resumes(self):
+         """Test that HTTP/1.1 stream flow control actually blocks and 
resumes"""
+         self._start_server(secure=True)
+@@ -846,6 +851,7 @@
+     """HTTP/2 async flow control tests using local mock server"""
+     timeout = 10.0
  
 +    @unittest.skip("Requires network")
+     async def _test_h2_manual_window_management_happy_path(self):
+         """Test HTTP/2 manual window management happy path"""
+         connection = await self._new_mock_h2_connection(
+@@ -880,6 +886,7 @@
      def test_h2_manual_window_management_happy_path(self):
          asyncio.run(self._test_h2_manual_window_management_happy_path())
  
-@@ -762,6 +767,7 @@
-         self.assertEqual(len(chunks_received), 10, "Should receive exactly 10 
chunks")
-         await connection.close()
- 
 +    @unittest.skip("Requires network")
-     def test_h2_stream_flow_control_blocks_and_resumes(self):
-         asyncio.run(self._test_h2_stream_flow_control_blocks_and_resumes())
+     async def _test_h2_stream_flow_control_blocks_and_resumes(self):
+         """Test that HTTP/2 stream flow control actually blocks and resumes"""
+         connection = await self._new_mock_h2_connection(
+@@ -940,6 +947,7 @@
  
-@@ -803,6 +809,7 @@
-         self.assertEqual(len(chunks_received), 100, "Should receive exactly 
100 chunks")
-         await connection.close()
+         return connection
  
 +    @unittest.skip("Requires network")
-     def test_h1_stream_flow_control_blocks_and_resumes(self):
-         asyncio.run(self._test_h1_stream_flow_control_blocks_and_resumes())
- 
-diff -Nru aws-crt-python-0.31.2.orig/test/test_http_client.py 
aws-crt-python-0.31.2/test/test_http_client.py
---- aws-crt-python-0.31.2.orig/test/test_http_client.py        2026-02-12 
19:06:36.000000000 +0100
-+++ aws-crt-python-0.31.2/test/test_http_client.py     2026-02-15 
11:32:30.115453571 +0100
-@@ -354,6 +354,7 @@
+     async def _test_h2_remote_end_stream_ordering(self):
+         """Test that on_h2_remote_end_stream fires before on_complete when 
server finishes first"""
+         connection = await self._new_httpbin_h2_connection()
+diff -Nru aws-crt-python-0.32.0.orig/test/test_http_client.py 
aws-crt-python-0.32.0/test/test_http_client.py
+--- aws-crt-python-0.32.0.orig/test/test_http_client.py        2026-03-27 
00:11:43.000000000 +0100
++++ aws-crt-python-0.32.0/test/test_http_client.py     2026-04-09 
13:49:32.250254694 +0200
+@@ -360,6 +360,7 @@
                                                        
tls_connection_options=tls_conn_opt)
          return connection_future.result(self.timeout)
  
@@ -68,7 +76,7 @@
      def test_h2_client(self):
          url = 
urlparse("https://d1cz66xoahf9cl.cloudfront.net/http_test_doc.txt";)
          connection = self._new_h2_client_connection(url)
-@@ -376,6 +377,7 @@
+@@ -382,6 +383,7 @@
  
          self.assertEqual(None, connection.close().exception(self.timeout))
  
@@ -76,9 +84,17 @@
      def test_h2_manual_write_exception(self):
          url = 
urlparse("https://d1cz66xoahf9cl.cloudfront.net/http_test_doc.txt";)
          connection = self._new_h2_client_connection(url)
-diff -Nru aws-crt-python-0.31.2.orig/test/test_io.py 
aws-crt-python-0.31.2/test/test_io.py
---- aws-crt-python-0.31.2.orig/test/test_io.py 2026-02-12 19:06:36.000000000 
+0100
-+++ aws-crt-python-0.31.2/test/test_io.py      2026-02-15 11:36:23.312116529 
+0100
+@@ -927,6 +929,7 @@
+ 
+         return connection_future.result(self.timeout)
+ 
++    @unittest.skip("Requires network")
+     def test_h2_remote_end_stream_ordering(self):
+         """Test that on_h2_remote_end_stream fires before on_complete when 
server finishes first"""
+         connection = self._new_httpbin_h2_connection()
+diff -Nru aws-crt-python-0.32.0.orig/test/test_io.py 
aws-crt-python-0.32.0/test/test_io.py
+--- aws-crt-python-0.32.0.orig/test/test_io.py 2026-03-27 00:11:43.000000000 
+0100
++++ aws-crt-python-0.32.0/test/test_io.py      2026-04-09 13:44:01.482192866 
+0200
 @@ -33,6 +33,7 @@
          event_loop_group_two = EventLoopGroup.get_or_create_static_default()
          self.assertTrue(event_loop_group_one == event_loop_group_two)

Reply via email to