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

Commit e71b1d5

Browse files
committed
added wrapped multicallables to avoid cache invalidation
1 parent c4a97e1 commit e71b1d5

2 files changed

Lines changed: 78 additions & 21 deletions

File tree

google/cloud/bigtable/data/_async/client.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -401,9 +401,6 @@ async def _manage_channel(
401401
await self._ping_and_warm_instances(channel=new_sub_channel)
402402
# cycle channel out of use, with long grace window before closure
403403
await channel.replace_wrapped_channel(new_sub_channel, grace_period)
404-
# invalidate caches
405-
self.transport._stubs = {}
406-
self.transport._prep_wrapped_messages(self.client_info)
407404
# subtract the time spent waiting for the channel to be replaced
408405
next_refresh = random.uniform(refresh_interval_min, refresh_interval_max)
409406
next_sleep = max(next_refresh - (time.monotonic() - start_timestamp), 0)

google/cloud/bigtable/data/_async/replaceable_channel.py

Lines changed: 78 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,80 @@
2121

2222
from google.cloud.bigtable.data._cross_sync import CrossSync
2323

24-
class _AsyncReplaceableChannel(aio.Channel):
24+
class _WrappedMultiCallable:
2525
"""
26-
A wrapper around a gRPC channel. All methods are passed
27-
through to the underlying channel.
26+
Wrapper class that implements the grpc MultiCallable interface.
27+
Allows generic functions that return calls to pass checks for
28+
MultiCallable objects.
2829
"""
2930

30-
def __init__(self, channel_fn: Callable[[], aio.Channel]):
31-
self._channel_fn = channel_fn
32-
self._channel = channel_fn()
31+
def __init__(self, call_factory: Callable[..., aio.Call]):
32+
self._call_factory = call_factory
33+
34+
def __call__(self, *args, **kwargs) -> aio.Call:
35+
return self._call_factory(*args, **kwargs)
36+
3337

34-
def unary_unary(self, *args, **kwargs):
35-
return self._channel.unary_unary(*args, **kwargs)
38+
class WrappedUnaryUnaryMultiCallable(
39+
_WrappedMultiCallable, aio.UnaryUnaryMultiCallable
40+
):
41+
pass
3642

37-
def unary_stream(self, *args, **kwargs):
38-
return self._channel.unary_stream(*args, **kwargs)
3943

40-
def stream_unary(self, *args, **kwargs):
41-
return self._channel.stream_unary(*args, **kwargs)
44+
class WrappedUnaryStreamMultiCallable(
45+
_WrappedMultiCallable, aio.UnaryStreamMultiCallable
46+
):
47+
pass
4248

43-
def stream_stream(self, *args, **kwargs):
44-
return self._channel.stream_stream(*args, **kwargs)
49+
50+
class WrappedStreamUnaryMultiCallable(
51+
_WrappedMultiCallable, aio.StreamUnaryMultiCallable
52+
):
53+
pass
54+
55+
56+
class WrappedStreamStreamMultiCallable(
57+
_WrappedMultiCallable, aio.StreamStreamMultiCallable
58+
):
59+
pass
60+
61+
62+
class _AsyncWrappedChannel(aio.Channel):
63+
"""
64+
A wrapper around a gRPC channel. All methods are passed
65+
through to the underlying channel.
66+
"""
67+
68+
def __init__(self, channel: aio.Channel):
69+
self._channel = channel
70+
71+
def unary_unary(self, *args, **kwargs) -> grpc.aio.UnaryUnaryMultiCallable:
72+
return WrappedUnaryUnaryMultiCallable(
73+
lambda *call_args, **call_kwargs: self._channel.unary_unary(
74+
*args, **kwargs
75+
)(*call_args, **call_kwargs)
76+
)
77+
78+
def unary_stream(self, *args, **kwargs) -> grpc.aio.UnaryStreamMultiCallable:
79+
return WrappedUnaryStreamMultiCallable(
80+
lambda *call_args, **call_kwargs: self._channel.unary_stream(
81+
*args, **kwargs
82+
)(*call_args, **call_kwargs)
83+
)
84+
85+
def stream_unary(self, *args, **kwargs) -> grpc.aio.StreamUnaryMultiCallable:
86+
return WrappedStreamUnaryMultiCallable(
87+
lambda *call_args, **call_kwargs: self._channel.stream_unary(
88+
*args, **kwargs
89+
)(*call_args, **call_kwargs)
90+
)
91+
92+
def stream_stream(self, *args, **kwargs) -> grpc.aio.StreamStreamMultiCallable:
93+
return WrappedStreamStreamMultiCallable(
94+
lambda *call_args, **call_kwargs: self._channel.stream_stream(
95+
*args, **kwargs
96+
)(*call_args, **call_kwargs)
97+
)
4598

4699
async def close(self, grace=None):
47100
return await self._channel.close(grace=grace)
@@ -62,6 +115,16 @@ def get_state(self, try_to_connect: bool = False) -> grpc.ChannelConnectivity:
62115
async def wait_for_state_change(self, last_observed_state):
63116
return await self._channel.wait_for_state_change(last_observed_state)
64117

118+
def __getattr__(self, name):
119+
return getattr(self._channel, name)
120+
121+
122+
class _AsyncReplaceableChannel(_AsyncWrappedChannel):
123+
124+
def __init__(self, channel_fn: Callable[[], aio.Channel]):
125+
self._channel_fn = channel_fn
126+
self._channel = channel_fn()
127+
65128
def create_channel(self) -> aio.Channel:
66129
return self._channel_fn()
67130

@@ -83,7 +146,4 @@ async def replace_wrapped_channel(self, new_channel: aio.Channel, grace_period:
83146
if grace_period:
84147
self._is_closed.wait(grace_period) # type: ignore
85148
old_channel.close() # type: ignore
86-
return old_channel
87-
88-
def __getattr__(self, name):
89-
return getattr(self._channel, name)
149+
return old_channel

0 commit comments

Comments
 (0)