@@ -37,6 +37,7 @@ public class DurableTaskGrpcWorker : IHostedService, IAsyncDisposable
3737 readonly ILogger logger ;
3838 readonly IConfiguration ? configuration ;
3939 readonly GrpcChannel sidecarGrpcChannel ;
40+ readonly bool ownsChannel ;
4041 readonly TaskHubSidecarServiceClient sidecarClient ;
4142 readonly WorkerContext workerContext ;
4243
@@ -62,8 +63,19 @@ public class DurableTaskGrpcWorker : IHostedService, IAsyncDisposable
6263 this . orchestrators = builder . taskProvider . orchestratorsBuilder . ToImmutable ( ) ;
6364 this . activities = builder . taskProvider . activitiesBuilder . ToImmutable ( ) ;
6465
65- string sidecarAddress = builder . address ?? SdkUtils . GetSidecarAddress ( this . configuration ) ;
66- this . sidecarGrpcChannel = GrpcChannel . ForAddress ( sidecarAddress ) ;
66+ if ( builder . channel != null )
67+ {
68+ // Use the channel from the builder, which was given to us by the app (thus we don't own it and can't dispose it)
69+ this . sidecarGrpcChannel = builder . channel ;
70+ this . ownsChannel = false ;
71+ }
72+ else
73+ {
74+ // We have to create our own channel and are responsible for disposing it
75+ this . sidecarGrpcChannel = GrpcChannel . ForAddress ( builder . address ?? SdkUtils . GetSidecarAddress ( this . configuration ) ) ;
76+ this . ownsChannel = true ;
77+ }
78+
6779 this . sidecarClient = new TaskHubSidecarServiceClient ( this . sidecarGrpcChannel ) ;
6880 }
6981
@@ -156,6 +168,12 @@ async ValueTask IAsyncDisposable.DisposeAsync()
156168 {
157169 }
158170
171+ if ( this . ownsChannel )
172+ {
173+ await this . sidecarGrpcChannel . ShutdownAsync ( ) ;
174+ this . sidecarGrpcChannel . Dispose ( ) ;
175+ }
176+
159177 GC . SuppressFinalize ( this ) ;
160178 }
161179
@@ -294,10 +312,34 @@ async Task OnRunOrchestratorAsync(P.OrchestratorRequest request)
294312 result . CustomStatus ,
295313 result . Actions ) ;
296314
297- this . logger . SendingOrchestratorResponse ( name , response . InstanceId , response . Actions . Count ) ;
315+ this . logger . SendingOrchestratorResponse (
316+ name ,
317+ response . InstanceId ,
318+ response . Actions . Count ,
319+ GetActionsListForLogging ( response . Actions ) ) ;
320+
298321 await this . sidecarClient . CompleteOrchestratorTaskAsync ( response ) ;
299322 }
300323
324+ static string GetActionsListForLogging ( IReadOnlyList < P . OrchestratorAction > actions )
325+ {
326+ if ( actions . Count == 0 )
327+ {
328+ return string . Empty ;
329+ }
330+ else if ( actions . Count == 1 )
331+ {
332+ return actions [ 0 ] . OrchestratorActionTypeCase . ToString ( ) ;
333+ }
334+ else
335+ {
336+ // Returns something like "ScheduleTask x5, CreateTimer x1,..."
337+ return string . Join ( ", " , actions
338+ . GroupBy ( a => a . OrchestratorActionTypeCase )
339+ . Select ( group => $ "{ group . Key } x{ group . Count ( ) } ") ) ;
340+ }
341+ }
342+
301343 OrchestratorExecutionResult CreateOrchestrationFailedActionResult ( Exception e )
302344 {
303345 return this . CreateOrchestrationFailedActionResult (
@@ -398,6 +440,7 @@ public sealed class Builder
398440 internal IServiceProvider ? services ;
399441 internal IConfiguration ? configuration ;
400442 internal string ? address ;
443+ internal GrpcChannel ? channel ;
401444
402445 internal Builder ( )
403446 {
@@ -411,6 +454,23 @@ public Builder UseAddress(string address)
411454 return this ;
412455 }
413456
457+ /// <summary>
458+ /// Configures a <see cref="GrpcChannel"/> to use for communicating with the sidecar process.
459+ /// </summary>
460+ /// <remarks>
461+ /// This builder method allows you to provide your own gRPC channel for communicating with the Durable Task
462+ /// sidecar service. Channels provided using this method won't be disposed when the worker is disposed.
463+ /// Rather, the caller remains responsible for shutting down the channel after disposing the worker.
464+ /// </remarks>
465+ /// <param name="channel">The gRPC channel to use.</param>
466+ /// <returns>Returns this <see cref="Builder"/> instance.</returns>
467+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="channel"/> is <c>null</c>.</exception>
468+ public Builder UseGrpcChannel ( GrpcChannel channel )
469+ {
470+ this . channel = channel ?? throw new ArgumentNullException ( nameof ( channel ) ) ;
471+ return this ;
472+ }
473+
414474 public Builder UseLoggerFactory ( ILoggerFactory loggerFactory )
415475 {
416476 this . loggerFactory = loggerFactory ?? throw new ArgumentNullException ( nameof ( loggerFactory ) ) ;
0 commit comments