2929using Microsoft . Azure . WebJobs . Script . Workers ;
3030using Microsoft . Azure . WebJobs . Script . Workers . Rpc ;
3131using Microsoft . Azure . WebJobs . Script . Workers . SharedMemoryDataTransfer ;
32+ using Microsoft . Extensions . Hosting ;
3233using Microsoft . Extensions . Logging ;
3334using Microsoft . Extensions . Options ;
3435using Yarp . ReverseProxy . Forwarder ;
@@ -42,6 +43,7 @@ namespace Microsoft.Azure.WebJobs.Script.Grpc
4243 internal partial class GrpcWorkerChannel : IRpcWorkerChannel , IDisposable
4344 {
4445 private readonly IScriptEventManager _eventManager ;
46+ private readonly IScriptHostManager _scriptHostManager ;
4547 private readonly RpcWorkerConfig _workerConfig ;
4648 private readonly string _runtime ;
4749 private readonly IEnvironment _environment ;
@@ -81,16 +83,18 @@ internal partial class GrpcWorkerChannel : IRpcWorkerChannel, IDisposable
8183 private TaskCompletionSource < List < RawFunctionMetadata > > _functionsIndexingTask = new TaskCompletionSource < List < RawFunctionMetadata > > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
8284 private TimeSpan _functionLoadTimeout = TimeSpan . FromMinutes ( 1 ) ;
8385 private bool _isSharedMemoryDataTransferEnabled ;
84- private bool ? _cancelCapabilityEnabled ;
86+ private bool _isHandlesInvocationCancelMessageCapabilityEnabled ;
8587 private bool _isWorkerApplicationInsightsLoggingEnabled ;
8688 private IHttpProxyService _httpProxyService ;
8789 private Uri _httpProxyEndpoint ;
8890 private System . Timers . Timer _timer ;
8991 private bool _functionMetadataRequestSent = false ;
92+ private IOptions < ScriptJobHostOptions > _scriptHostOptions ;
9093
9194 internal GrpcWorkerChannel (
9295 string workerId ,
9396 IScriptEventManager eventManager ,
97+ IScriptHostManager hostManager ,
9498 RpcWorkerConfig workerConfig ,
9599 IWorkerProcess rpcWorkerProcess ,
96100 ILogger logger ,
@@ -105,6 +109,7 @@ internal GrpcWorkerChannel(
105109 {
106110 _workerId = workerId ;
107111 _eventManager = eventManager ;
112+ _scriptHostManager = hostManager ;
108113 _workerConfig = workerConfig ;
109114 _runtime = workerConfig . Description . Language ;
110115 _rpcWorkerProcess = rpcWorkerProcess ;
@@ -140,6 +145,10 @@ internal GrpcWorkerChannel(
140145
141146 // Temporary switch to allow fully testing new algorithm in production
142147 _messageDispatcherFactory = GetProcessorFactory ( ) ;
148+
149+ _scriptHostManager . ActiveHostChanged += HandleActiveHostChange ;
150+
151+ LoadScriptJobHostOptions ( _scriptHostManager as IServiceProvider ) ;
143152 }
144153
145154 private bool IsHttpProxyingWorker => _httpProxyEndpoint is not null ;
@@ -152,6 +161,30 @@ internal GrpcWorkerChannel(
152161
153162 public RpcWorkerConfig WorkerConfig => _workerConfig ;
154163
164+ public IOptions < ScriptJobHostOptions > JobHostOptions => _scriptHostOptions ;
165+
166+ private void HandleActiveHostChange ( object sender , ActiveHostChangedEventArgs e )
167+ {
168+ if ( e . NewHost is null )
169+ {
170+ return ;
171+ }
172+
173+ LoadScriptJobHostOptions ( e . NewHost . Services ) ;
174+ }
175+
176+ private void LoadScriptJobHostOptions ( IServiceProvider provider )
177+ {
178+ if ( provider ? . GetService ( typeof ( IOptions < ScriptJobHostOptions > ) ) is IOptions < ScriptJobHostOptions > scriptHostOptions )
179+ {
180+ _scriptHostOptions = scriptHostOptions ;
181+ }
182+ else
183+ {
184+ _workerChannelLogger . LogDebug ( "Unable to resolve ScriptJobHostOptions" ) ;
185+ }
186+ }
187+
155188 // Temporary switch that allows us to move between the "old" ThreadPool-only processor
156189 // and a "new" Channel processor (for proper ordering of messages).
157190 private IInvocationMessageDispatcherFactory GetProcessorFactory ( )
@@ -511,7 +544,7 @@ internal void ApplyCapabilities(IDictionary<string, string> capabilities, GrpcCa
511544 UpdateCapabilities ( capabilities , strategy ) ;
512545
513546 _isSharedMemoryDataTransferEnabled = IsSharedMemoryDataTransferEnabled ( ) ;
514- _cancelCapabilityEnabled ?? = ! string . IsNullOrEmpty ( _workerCapabilities . GetCapabilityState ( RpcWorkerConstants . HandlesInvocationCancelMessage ) ) ;
547+ _isHandlesInvocationCancelMessageCapabilityEnabled = ! string . IsNullOrEmpty ( _workerCapabilities . GetCapabilityState ( RpcWorkerConstants . HandlesInvocationCancelMessage ) ) ;
515548
516549 if ( ! _isSharedMemoryDataTransferEnabled )
517550 {
@@ -816,12 +849,21 @@ internal async Task SendInvocationRequest(ScriptInvocationContext context)
816849 return ;
817850 }
818851
819- // do not send an invocation request if cancellation has been requested
820852 if ( context . CancellationToken . IsCancellationRequested )
821853 {
822- _workerChannelLogger . LogWarning ( "Cancellation has been requested. The invocation request with id '{invocationId}' is canceled and will not be sent to the worker." , invocationId ) ;
823- context . ResultSource . TrySetCanceled ( ) ;
824- return ;
854+ _workerChannelLogger . LogDebug ( "Cancellation was requested prior to the invocation request ('{invocationId}') being sent to the worker." , invocationId ) ;
855+
856+ // If the worker does not support handling InvocationCancel grpc messages, or if cancellation is supported and the customer opts-out
857+ // of sending cancelled invocations to the worker, we will cancel the result source and not send the invocation to the worker.
858+ if ( ! _isHandlesInvocationCancelMessageCapabilityEnabled || ! JobHostOptions . Value . SendCanceledInvocationsToWorker )
859+ {
860+ _workerChannelLogger . LogInformation ( "Cancelling invocation '{invocationId}' due to cancellation token being signaled. "
861+ + "This invocation was not sent to the worker. Read more about this here: https://aka.ms/azure-functions-cancellations" , invocationId ) ;
862+
863+ // This will result in an invocation failure with a "FunctionInvocationCanceled" exception.
864+ context . ResultSource . TrySetCanceled ( ) ;
865+ return ;
866+ }
825867 }
826868
827869 var invocationRequest = await context . ToRpcInvocationRequest ( _workerChannelLogger , _workerCapabilities , _isSharedMemoryDataTransferEnabled , _sharedMemoryManager ) ;
@@ -834,9 +876,10 @@ await SendStreamingMessageAsync(new StreamingMessage
834876 InvocationRequest = invocationRequest
835877 } ) ;
836878
837- if ( _cancelCapabilityEnabled != null && _cancelCapabilityEnabled . Value )
879+ if ( _isHandlesInvocationCancelMessageCapabilityEnabled )
838880 {
839- context . CancellationToken . Register ( ( ) => SendInvocationCancel ( invocationRequest . InvocationId ) ) ;
881+ var cancellationCtr = context . CancellationToken . Register ( ( ) => SendInvocationCancel ( invocationRequest . InvocationId ) ) ;
882+ context . Properties . Add ( ScriptConstants . CancellationTokenRegistration , cancellationCtr ) ;
840883 }
841884
842885 if ( IsHttpProxyingWorker && context . FunctionMetadata . IsHttpTriggerFunction ( ) )
@@ -1038,6 +1081,13 @@ internal async Task InvokeResponse(InvocationResponse invokeResponse)
10381081 if ( _executingInvocations . TryRemove ( invokeResponse . InvocationId , out var invocation ) )
10391082 {
10401083 var context = invocation . Context ;
1084+
1085+ if ( context . Properties . TryGetValue ( ScriptConstants . CancellationTokenRegistration , out CancellationTokenRegistration ctr ) )
1086+ {
1087+ await ctr . DisposeAsync ( ) ;
1088+ context . Properties . Remove ( ScriptConstants . CancellationTokenRegistration ) ;
1089+ }
1090+
10411091 if ( invokeResponse . Result . IsInvocationSuccess ( context . ResultSource , capabilityEnabled ) )
10421092 {
10431093 _metricsLogger . LogEvent ( string . Format ( MetricEventNames . WorkerInvokeSucceeded , Id ) ) ;
@@ -1355,6 +1405,7 @@ protected virtual void Dispose(bool disposing)
13551405 _startLatencyMetric ? . Dispose ( ) ;
13561406 _workerInitTask ? . TrySetCanceled ( ) ;
13571407 _timer ? . Dispose ( ) ;
1408+ _scriptHostManager . ActiveHostChanged -= HandleActiveHostChange ;
13581409
13591410 // unlink function inputs
13601411 if ( _inputLinks is not null )
0 commit comments