Skip to content
This repository was archived by the owner on Mar 9, 2026. It is now read-only.

Commit 6acf2ac

Browse files
committed
fix: surface stream closures to calling future
1 parent 0ae0340 commit 6acf2ac

4 files changed

Lines changed: 42 additions & 3 deletions

File tree

black

Whitespace-only changes.

google/cloud/pubsub_v1/subscriber/_protocol/streaming_pull_manager.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,9 @@ def open(
884884
assert self._scheduler is not None
885885
scheduler_queue = self._scheduler.queue
886886
self._dispatcher = dispatcher.Dispatcher(self, scheduler_queue)
887-
self._consumer = bidi.BackgroundConsumer(self._rpc, self._on_response)
887+
self._consumer = bidi.BackgroundConsumer(
888+
self._rpc, self._on_response, on_fatal_exception=self._on_fatal_exception
889+
)
888890
self._leaser = leaser.Leaser(self)
889891
self._heartbeater = heartbeater.Heartbeater(self)
890892

@@ -1255,6 +1257,17 @@ def _on_response(self, response: gapic_types.StreamingPullResponse) -> None:
12551257

12561258
self.maybe_pause_consumer()
12571259

1260+
def _on_fatal_exception(self, exception: BaseException) -> None:
1261+
"""
1262+
Called whenever `self.consumer` receives a non-retryable exception.
1263+
We close the manager on such non-retryable cases.
1264+
"""
1265+
_LOGGER.exception(
1266+
"Streaming pull terminating after receiving non-recoverable error: %s",
1267+
exception,
1268+
)
1269+
self.close(exception)
1270+
12581271
def _should_recover(self, exception: BaseException) -> bool:
12591272
"""Determine if an error on the RPC stream should be recovered.
12601273

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"grpcio >= 1.51.3, < 2.0.0", # https://github.com/googleapis/python-pubsub/issues/609
4040
# google-api-core >= 1.34.0 is allowed in order to support google-api-core 1.x
4141
"google-auth >= 2.14.1, <3.0.0",
42-
"google-api-core[grpc] >= 1.34.0, <3.0.0,!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,!=2.10.*",
42+
"google-api-core[grpc] >= 2.25.1, <3.0.0,!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*,!=2.5.*,!=2.6.*,!=2.7.*,!=2.8.*,!=2.9.*,!=2.10.*",
4343
"proto-plus >= 1.22.0, <2.0.0",
4444
"proto-plus >= 1.22.2, <2.0.0; python_version>='3.11'",
4545
"proto-plus >= 1.25.0, < 2.0.0; python_version >= '3.13'",

tests/unit/pubsub_v1/subscriber/test_streaming_pull_manager.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1333,7 +1333,9 @@ def test_open(heartbeater, dispatcher, leaser, background_consumer, resumable_bi
13331333
leaser.return_value.start.assert_called_once()
13341334
assert manager.leaser == leaser.return_value
13351335

1336-
background_consumer.assert_called_once_with(manager._rpc, manager._on_response)
1336+
background_consumer.assert_called_once_with(
1337+
manager._rpc, manager._on_response, manager._on_fatal_exception
1338+
)
13371339
background_consumer.return_value.start.assert_called_once()
13381340
assert manager._consumer == background_consumer.return_value
13391341

@@ -1432,6 +1434,30 @@ def test_close():
14321434
assert manager.is_active is False
14331435

14341436

1437+
def test_closes_on_fatal_consumer_error():
1438+
(
1439+
manager,
1440+
consumer,
1441+
dispatcher,
1442+
leaser,
1443+
heartbeater,
1444+
scheduler,
1445+
) = make_running_manager()
1446+
1447+
error = ValueError("some fatal exception")
1448+
manager._on_fatal_exception(error)
1449+
1450+
await_manager_shutdown(manager, timeout=3)
1451+
1452+
consumer.stop.assert_called_once()
1453+
leaser.stop.assert_called_once()
1454+
dispatcher.stop.assert_called_once()
1455+
heartbeater.stop.assert_called_once()
1456+
scheduler.shutdown.assert_called_once()
1457+
1458+
assert manager.is_active is False
1459+
1460+
14351461
def test_close_inactive_consumer():
14361462
(
14371463
manager,

0 commit comments

Comments
 (0)