@@ -988,20 +988,17 @@ def __init__(self, *,
988988 else None
989989 )
990990 self ._interceptors = self ._compose_interceptors (user_interceptors )
991- if channel is None :
992- channel = shared .get_async_grpc_channel (
993- host_address = self ._host_address ,
994- secure_channel = secure_channel ,
995- interceptors = self ._interceptors ,
996- channel_options = channel_options ,
997- )
998991 # Caller-owned channels cannot be retroactively wrapped with our
999992 # interceptor (``grpc.aio`` exposes no public equivalent of
1000993 # ``grpc.intercept_channel``). We document this in :meth:`__init__` and
1001994 # leave the failure-tracking opt-out implicit: callers wanting full
1002995 # resiliency should let us create the channel.
1003996 self ._channel = channel
1004- self ._stub = cast (_AsyncTaskHubSidecarServiceStub , stubs .TaskHubSidecarServiceStub (channel ))
997+ self ._stub = (
998+ cast (_AsyncTaskHubSidecarServiceStub , stubs .TaskHubSidecarServiceStub (channel ))
999+ if channel is not None
1000+ else None
1001+ )
10051002 self ._logger = shared .get_logger ("async_client" , log_handler , log_formatter )
10061003 self .default_version = default_version
10071004 self ._payload_store = payload_store
@@ -1016,6 +1013,25 @@ def _compose_interceptors(
10161013 composed .extend (user_interceptors )
10171014 return composed
10181015
1016+ def _get_stub (self ) -> _AsyncTaskHubSidecarServiceStub :
1017+ """Create the SDK-owned channel on the event loop that first uses it."""
1018+ if self ._closing :
1019+ raise RuntimeError ("The client is closed" )
1020+ if self ._stub is None :
1021+ channel = shared .get_async_grpc_channel (
1022+ host_address = self ._host_address ,
1023+ secure_channel = self ._secure_channel ,
1024+ interceptors = self ._interceptors ,
1025+ channel_options = self ._channel_options ,
1026+ )
1027+ stub = cast (
1028+ _AsyncTaskHubSidecarServiceStub ,
1029+ stubs .TaskHubSidecarServiceStub (channel ),
1030+ )
1031+ self ._channel = channel
1032+ self ._stub = stub
1033+ return self ._stub
1034+
10191035 async def close (self ) -> None :
10201036 """Closes the underlying gRPC channel.
10211037
@@ -1047,7 +1063,9 @@ async def close(self) -> None:
10471063 await asyncio .gather (* close_tasks , return_exceptions = True )
10481064 for retired_channel in retired_channels :
10491065 await retired_channel .close ()
1050- await self ._channel .close ()
1066+ channel = self ._channel
1067+ if channel is not None :
1068+ await channel .close ()
10511069
10521070 async def __aenter__ (self ) -> "AsyncTaskHubGrpcClient" :
10531071 return self
@@ -1093,6 +1111,8 @@ async def _maybe_recreate_channel(self) -> None:
10931111 if now - self ._last_recreate_time < self ._resiliency_options .min_recreate_interval_seconds :
10941112 return
10951113 old_channel = self ._channel
1114+ if old_channel is None :
1115+ return
10961116 self ._channel = shared .get_async_grpc_channel (
10971117 host_address = self ._host_address ,
10981118 secure_channel = self ._secure_channel ,
@@ -1147,13 +1167,13 @@ async def schedule_new_orchestration(self, orchestrator: task.Orchestrator[TInpu
11471167 await payload_helpers .externalize_payloads_async (
11481168 req , self ._payload_store , instance_id = req .instanceId ,
11491169 )
1150- res : pb .CreateInstanceResponse = await self ._stub .StartInstance (req )
1170+ res : pb .CreateInstanceResponse = await self ._get_stub () .StartInstance (req )
11511171 return res .instanceId
11521172
11531173 async def get_orchestration_state (self , instance_id : str , * ,
11541174 fetch_payloads : bool = True ) -> OrchestrationState | None :
11551175 req = pb .GetInstanceRequest (instanceId = instance_id , getInputsAndOutputs = fetch_payloads )
1156- res : pb .GetInstanceResponse = await self ._stub .GetInstance (req )
1176+ res : pb .GetInstanceResponse = await self ._get_stub () .GetInstance (req )
11571177 if self ._payload_store is not None and res .exists :
11581178 await payload_helpers .deexternalize_payloads_async (res , self ._payload_store )
11591179 return new_orchestration_state (req .instanceId , res , self ._data_converter )
@@ -1168,7 +1188,7 @@ async def get_orchestration_history(self,
11681188 forWorkItemProcessing = for_work_item_processing ,
11691189 )
11701190 self ._logger .info (f"Retrieving history for instance '{ instance_id } '." )
1171- stream = self ._stub .StreamInstanceHistory (req )
1191+ stream = self ._get_stub () .StreamInstanceHistory (req )
11721192 return await history_helpers .collect_history_events_async (stream , self ._payload_store )
11731193
11741194 async def list_instance_ids (self ,
@@ -1192,7 +1212,7 @@ async def list_instance_ids(self,
11921212 f"page_size={ page_size } , "
11931213 f"continuation_token={ continuation_token } "
11941214 )
1195- resp : pb .ListInstanceIdsResponse = await self ._stub .ListInstanceIds (req )
1215+ resp : pb .ListInstanceIdsResponse = await self ._get_stub () .ListInstanceIds (req )
11961216 next_token = resp .lastInstanceKey .value if resp .HasField ("lastInstanceKey" ) else None
11971217 return Page (items = list (resp .instanceIds ), continuation_token = next_token )
11981218
@@ -1209,7 +1229,7 @@ async def get_all_orchestration_states(self,
12091229
12101230 while True :
12111231 req = build_query_instances_req (orchestration_query , _continuation_token )
1212- resp : pb .QueryInstancesResponse = await self ._stub .QueryInstances (req )
1232+ resp : pb .QueryInstancesResponse = await self ._get_stub () .QueryInstances (req )
12131233 if self ._payload_store is not None :
12141234 await payload_helpers .deexternalize_payloads_async (resp , self ._payload_store )
12151235 states += [parse_orchestration_state (res , self ._data_converter ) for res in resp .orchestrationState ]
@@ -1226,7 +1246,7 @@ async def wait_for_orchestration_start(self, instance_id: str, *,
12261246 req = pb .GetInstanceRequest (instanceId = instance_id , getInputsAndOutputs = fetch_payloads )
12271247 try :
12281248 self ._logger .info (f"Waiting up to { timeout } s for instance '{ instance_id } ' to start." )
1229- res : pb .GetInstanceResponse = await self ._stub .WaitForInstanceStart (
1249+ res : pb .GetInstanceResponse = await self ._get_stub () .WaitForInstanceStart (
12301250 req ,
12311251 timeout = timeout ,
12321252 )
@@ -1245,7 +1265,7 @@ async def wait_for_orchestration_completion(self, instance_id: str, *,
12451265 req = pb .GetInstanceRequest (instanceId = instance_id , getInputsAndOutputs = fetch_payloads )
12461266 try :
12471267 self ._logger .info (f"Waiting { timeout } s for instance '{ instance_id } ' to complete." )
1248- res : pb .GetInstanceResponse = await self ._stub .WaitForInstanceCompletion (
1268+ res : pb .GetInstanceResponse = await self ._get_stub () .WaitForInstanceCompletion (
12491269 req ,
12501270 timeout = timeout ,
12511271 )
@@ -1269,7 +1289,7 @@ async def raise_orchestration_event(self, instance_id: str, event_name: str, *,
12691289 await payload_helpers .externalize_payloads_async (
12701290 req , self ._payload_store , instance_id = instance_id ,
12711291 )
1272- await self ._stub .RaiseEvent (req )
1292+ await self ._get_stub () .RaiseEvent (req )
12731293
12741294 async def terminate_orchestration (self , instance_id : str , * ,
12751295 output : Any | None = None ,
@@ -1281,17 +1301,17 @@ async def terminate_orchestration(self, instance_id: str, *,
12811301 await payload_helpers .externalize_payloads_async (
12821302 req , self ._payload_store , instance_id = instance_id ,
12831303 )
1284- await self ._stub .TerminateInstance (req )
1304+ await self ._get_stub () .TerminateInstance (req )
12851305
12861306 async def suspend_orchestration (self , instance_id : str ) -> None :
12871307 req = pb .SuspendRequest (instanceId = instance_id )
12881308 self ._logger .info (f"Suspending instance '{ instance_id } '." )
1289- await self ._stub .SuspendInstance (req )
1309+ await self ._get_stub () .SuspendInstance (req )
12901310
12911311 async def resume_orchestration (self , instance_id : str ) -> None :
12921312 req = pb .ResumeRequest (instanceId = instance_id )
12931313 self ._logger .info (f"Resuming instance '{ instance_id } '." )
1294- await self ._stub .ResumeInstance (req )
1314+ await self ._get_stub () .ResumeInstance (req )
12951315
12961316 async def rewind_orchestration (self , instance_id : str , * ,
12971317 reason : str | None = None ) -> None :
@@ -1311,7 +1331,7 @@ async def rewind_orchestration(self, instance_id: str, *,
13111331 reason = helpers .get_string_value (reason ))
13121332
13131333 self ._logger .info (f"Rewinding instance '{ instance_id } '." )
1314- await self ._stub .RewindInstance (req )
1334+ await self ._get_stub () .RewindInstance (req )
13151335
13161336 async def restart_orchestration (self , instance_id : str , * ,
13171337 restart_with_new_instance_id : bool = False ) -> str :
@@ -1330,13 +1350,13 @@ async def restart_orchestration(self, instance_id: str, *,
13301350 restartWithNewInstanceId = restart_with_new_instance_id )
13311351
13321352 self ._logger .info (f"Restarting instance '{ instance_id } '." )
1333- res : pb .RestartInstanceResponse = await self ._stub .RestartInstance (req )
1353+ res : pb .RestartInstanceResponse = await self ._get_stub () .RestartInstance (req )
13341354 return res .instanceId
13351355
13361356 async def purge_orchestration (self , instance_id : str , recursive : bool = True ) -> PurgeInstancesResult :
13371357 req = pb .PurgeInstancesRequest (instanceId = instance_id , recursive = recursive )
13381358 self ._logger .info (f"Purging instance '{ instance_id } '." )
1339- resp : pb .PurgeInstancesResponse = await self ._stub .PurgeInstances (req )
1359+ resp : pb .PurgeInstancesResponse = await self ._get_stub () .PurgeInstances (req )
13401360 return PurgeInstancesResult (resp .deletedInstanceCount , resp .isComplete .value )
13411361
13421362 async def purge_orchestrations_by (self ,
@@ -1350,7 +1370,7 @@ async def purge_orchestrations_by(self,
13501370 f"runtime_status={ [str (status ) for status in runtime_status ] if runtime_status else None } , "
13511371 f"recursive={ recursive } " )
13521372 req = build_purge_by_filter_req (created_time_from , created_time_to , runtime_status , recursive )
1353- resp : pb .PurgeInstancesResponse = await self ._stub .PurgeInstances (req )
1373+ resp : pb .PurgeInstancesResponse = await self ._get_stub () .PurgeInstances (req )
13541374 return PurgeInstancesResult (resp .deletedInstanceCount , resp .isComplete .value )
13551375
13561376 async def signal_entity (self ,
@@ -1365,15 +1385,15 @@ async def signal_entity(self,
13651385 await payload_helpers .externalize_payloads_async (
13661386 req , self ._payload_store , instance_id = str (entity_instance_id ),
13671387 )
1368- await self ._stub .SignalEntity (req )
1388+ await self ._get_stub () .SignalEntity (req )
13691389
13701390 async def get_entity (self ,
13711391 entity_instance_id : EntityInstanceId ,
13721392 include_state : bool = True
13731393 ) -> EntityMetadata | None :
13741394 req = pb .GetEntityRequest (instanceId = str (entity_instance_id ), includeState = include_state )
13751395 self ._logger .info (f"Getting entity '{ entity_instance_id } '." )
1376- res : pb .GetEntityResponse = await self ._stub .GetEntity (req )
1396+ res : pb .GetEntityResponse = await self ._get_stub () .GetEntity (req )
13771397 if not res .exists :
13781398 return None
13791399 if self ._payload_store is not None :
@@ -1392,7 +1412,7 @@ async def get_all_entities(self,
13921412
13931413 while True :
13941414 query_request = build_query_entities_req (entity_query , _continuation_token )
1395- resp : pb .QueryEntitiesResponse = await self ._stub .QueryEntities (query_request )
1415+ resp : pb .QueryEntitiesResponse = await self ._get_stub () .QueryEntities (query_request )
13961416 if self ._payload_store is not None :
13971417 await payload_helpers .deexternalize_payloads_async (resp , self ._payload_store )
13981418 entities += [EntityMetadata .from_entity_metadata (entity , query_request .query .includeState , self ._data_converter ) for entity in resp .entities ]
@@ -1418,7 +1438,7 @@ async def clean_entity_storage(self,
14181438 releaseOrphanedLocks = release_orphaned_locks ,
14191439 continuationToken = _continuation_token
14201440 )
1421- resp : pb .CleanEntityStorageResponse = await self ._stub .CleanEntityStorage (req )
1441+ resp : pb .CleanEntityStorageResponse = await self ._get_stub () .CleanEntityStorage (req )
14221442 empty_entities_removed += resp .emptyEntitiesRemoved
14231443 orphaned_locks_released += resp .orphanedLocksReleased
14241444
0 commit comments