in src/WebJobs.Script/Host/WorkerFunctionMetadataProvider.cs [53:150]
public async Task<FunctionMetadataResult> GetFunctionMetadataAsync(IEnumerable<RpcWorkerConfig> workerConfigs, bool forceRefresh)
{
_workerRuntime = _environment.GetEnvironmentVariable(EnvironmentSettingNames.FunctionWorkerRuntime);
_logger.LogInformation("Fetching metadata for workerRuntime: {workerRuntime}", _workerRuntime);
IEnumerable<FunctionMetadata> functions = new List<FunctionMetadata>();
_logger.ReadingFunctionMetadataFromProvider(_metadataProviderName);
if (_functions.IsDefaultOrEmpty || forceRefresh)
{
IEnumerable<RawFunctionMetadata> rawFunctions = new List<RawFunctionMetadata>();
if (_channelManager == null)
{
throw new InvalidOperationException(nameof(_channelManager));
}
// Scenario: Restart worker for hot reload on a readwrite file system
// We reuse the worker started in placeholderMode only when the fileSystem is readonly
// otherwise we shutdown the channel in which case the channel should not have any channels anyway
// forceRefresh in only true once in the script host initialization flow.
// forceRefresh will be false when bundle is not used (dotnet and dotnet-isolated).
if (!_environment.IsPlaceholderModeEnabled() && forceRefresh && !_scriptOptions.CurrentValue.IsFileSystemReadOnly)
{
_channelManager.ShutdownChannelsAsync().GetAwaiter().GetResult();
}
var channels = _channelManager.GetChannels(_workerRuntime);
// Start up GRPC channels if they are not already running.
if (channels?.Any() != true)
{
if (_scriptHostManager.State is ScriptHostState.Default
|| _scriptHostManager.State is ScriptHostState.Starting
|| _scriptHostManager.State is ScriptHostState.Initialized)
{
// We don't need to restart if the host hasn't even been created yet.
_logger.LogDebug("Host is starting up, initializing language worker channel");
await _channelManager.InitializeChannelAsync(workerConfigs, _workerRuntime);
}
else
{
// During the restart flow, GetFunctionMetadataAsync gets invoked
// again through a new script host initialization flow.
_logger.LogDebug("Host is running without any initialized channels, restarting the JobHost.");
await _scriptHostManager.RestartHostAsync();
}
channels = _channelManager.GetChannels(_workerRuntime);
}
if (channels is null)
{
_logger.LogDebug("Worker channels are null, there is likely an issue with the worker not being able to start.");
throw new InvalidOperationException($"No initialized language worker channel found for runtime: {_workerRuntime}.");
}
foreach (string workerId in channels.Keys.ToList())
{
if (channels.TryGetValue(workerId, out TaskCompletionSource<IRpcWorkerChannel> languageWorkerChannelTask))
{
_logger.LogDebug("Found initialized language worker channel for runtime: {workerRuntime} workerId:{workerId}", _workerRuntime, workerId);
try
{
IRpcWorkerChannel channel = await languageWorkerChannelTask.Task;
rawFunctions = await channel.GetFunctionMetadata();
if (rawFunctions.Any(x => x.UseDefaultMetadataIndexing))
{
_functions.Clear();
return new FunctionMetadataResult(useDefaultMetadataIndexing: true, _functions);
}
if (!IsNullOrEmpty(rawFunctions))
{
functions = ValidateMetadata(rawFunctions);
}
_functions = functions.ToImmutableArray();
_logger.FunctionsReturnedByProvider(_functions.IsDefault ? 0 : _functions.Count(), _metadataProviderName);
// Validate if the app has functions in legacy format and add in logs to inform about the mixed app
_ = Task.Delay(TimeSpan.FromMinutes(1)).ContinueWith(t => ValidateFunctionAppFormat(_scriptOptions.CurrentValue.ScriptPath, _logger, _environment));
break;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Removing errored webhost language worker channel for runtime: {workerRuntime} workerId:{workerId}", _workerRuntime, workerId);
await _channelManager.ShutdownChannelIfExistsAsync(_workerRuntime, workerId, ex);
}
}
}
}
return new FunctionMetadataResult(useDefaultMetadataIndexing: false, _functions);
}