From 17376a1a47070f2fddd2bc955d8b07d316d40e31 Mon Sep 17 00:00:00 2001 From: Vibhav Singamshetty Date: Wed, 15 Apr 2026 14:00:02 -0700 Subject: [PATCH 1/2] fix(asyncio): Avoid InvalidStateError on late callbacks after cancel --- pulsar/asyncio.py | 2 ++ tests/asyncio_test.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/pulsar/asyncio.py b/pulsar/asyncio.py index 7fa7c3d7..2db4935f 100644 --- a/pulsar/asyncio.py +++ b/pulsar/asyncio.py @@ -828,6 +828,8 @@ async def close(self) -> None: def _set_future(future: asyncio.Future, result: _pulsar.Result, value: Any): def complete(): + if future.done(): + return if result == _pulsar.Result.Ok: future.set_result(value) else: diff --git a/tests/asyncio_test.py b/tests/asyncio_test.py index 8a441c44..3cc1078c 100644 --- a/tests/asyncio_test.py +++ b/tests/asyncio_test.py @@ -39,6 +39,7 @@ Consumer, Producer, PulsarException, + _set_future, ) from pulsar.schema import ( # pylint: disable=import-error AvroSchema, @@ -484,5 +485,25 @@ class ExampleRecord(Record): # pylint: disable=too-few-public-methods self.assertEqual(msg.value().int_field, 42) +class AsyncioSetFutureTest(IsolatedAsyncioTestCase): + """Tests for asyncio bridge helpers (no live Pulsar broker).""" + + async def test_set_future_noop_when_future_cancelled(self): + loop = asyncio.get_running_loop() + fut = loop.create_future() + fut.cancel() + _set_future(fut, _pulsar.Result.Ok, None) + await asyncio.sleep(0) + self.assertTrue(fut.cancelled()) + + async def test_set_future_noop_when_future_already_resolved(self): + loop = asyncio.get_running_loop() + fut = loop.create_future() + fut.set_result("first") + _set_future(fut, _pulsar.Result.Ok, "late") + await asyncio.sleep(0) + self.assertEqual(fut.result(), "first") + + if __name__ == '__main__': main() From ee4851b9cc49c7f9b36cc799f7746d0d70522548 Mon Sep 17 00:00:00 2001 From: Vibhav Singamshetty Date: Wed, 15 Apr 2026 14:15:21 -0700 Subject: [PATCH 2/2] chore(ci): Publish wheels to GitHub Releases on tag; bump version to 3.11.0a2 --- .../workflows/ci-build-release-wheels.yaml | 28 +++++++++++++++++++ pulsar/__about__.py | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-build-release-wheels.yaml b/.github/workflows/ci-build-release-wheels.yaml index fc2bc4e1..ad87ff20 100644 --- a/.github/workflows/ci-build-release-wheels.yaml +++ b/.github/workflows/ci-build-release-wheels.yaml @@ -23,6 +23,9 @@ on: tags: - '*' +permissions: + contents: write + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -183,3 +186,28 @@ jobs: with: name: wheel-windows-py${{matrix.python.version}} path: dist/*.whl + + publish-github-release: + name: Publish wheels to GitHub Release + needs: + - linux-wheel + - mac-wheels + - windows-wheels + runs-on: ubuntu-latest + steps: + - name: Download all wheel artifacts + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: List wheels + run: find dist -name '*.whl' | sort + + - name: Create GitHub Release with wheel assets + uses: softprops/action-gh-release@v2 + with: + files: dist/**/*.whl + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/pulsar/__about__.py b/pulsar/__about__.py index dd2cc65f..c5a0519d 100644 --- a/pulsar/__about__.py +++ b/pulsar/__about__.py @@ -16,4 +16,4 @@ # specific language governing permissions and limitations # under the License. # -__version__='3.11.0a1' +__version__='3.11.0a2'