From bb79ef3733145e250e0a7ea3a1313604a1298cf5 Mon Sep 17 00:00:00 2001 From: Mathew Charles Date: Fri, 15 Dec 2023 14:37:43 -0800 Subject: [PATCH] Ensure SingletonListener is applied before custom Listener decorators (#3049) --- .../WebJobsServiceCollectionExtensions.cs | 1 - .../Indexers/FunctionIndexer.cs | 23 +++++++++++-- .../Listeners/SingletonListenerDecorator.cs | 34 ------------------- .../HostListenerFactoryTests.cs | 17 ++++++---- 4 files changed, 30 insertions(+), 45 deletions(-) delete mode 100644 src/Microsoft.Azure.WebJobs.Host/Listeners/SingletonListenerDecorator.cs diff --git a/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs b/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs index 726ef971d..574e68156 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Hosting/WebJobsServiceCollectionExtensions.cs @@ -217,7 +217,6 @@ private static void AddListenerDecorators(IServiceCollection services) { // Order is important for these platform decorator registrations! // They will be applied in this order after any user registered decorators. - services.TryAddEnumerable(ServiceDescriptor.Singleton()); services.TryAddEnumerable(ServiceDescriptor.Singleton()); } } diff --git a/src/Microsoft.Azure.WebJobs.Host/Indexers/FunctionIndexer.cs b/src/Microsoft.Azure.WebJobs.Host/Indexers/FunctionIndexer.cs index b4cb334a1..a24a91dd7 100644 --- a/src/Microsoft.Azure.WebJobs.Host/Indexers/FunctionIndexer.cs +++ b/src/Microsoft.Azure.WebJobs.Host/Indexers/FunctionIndexer.cs @@ -376,7 +376,7 @@ private FunctionDefinition CreateTriggeredFunctionDefinition( ITriggeredFunctionBinding functionBinding = new TriggeredFunctionBinding(descriptor, parameterName, triggerBinding, nonTriggerBindings, _singletonManager); ITriggeredFunctionInstanceFactory instanceFactory = new TriggeredFunctionInstanceFactory(functionBinding, invoker, descriptor, _serviceScopeFactory); ITriggeredFunctionExecutor triggerExecutor = new TriggeredFunctionExecutor(descriptor, _executor, instanceFactory, _loggerFactory); - IListenerFactory listenerFactory = new ListenerFactory(descriptor, triggerExecutor, triggerBinding, _sharedQueue); + IListenerFactory listenerFactory = new ListenerFactory(descriptor, triggerExecutor, triggerBinding, _sharedQueue, _singletonManager, _loggerFactory); return new FunctionDefinition(descriptor, instanceFactory, listenerFactory); } @@ -470,19 +470,36 @@ private class ListenerFactory : IListenerFactory private readonly ITriggeredFunctionExecutor _executor; private readonly ITriggerBinding _binding; private readonly SharedQueueHandler _sharedQueue; + private readonly SingletonManager _singletonManager; + private readonly ILoggerFactory _loggerFactory; - public ListenerFactory(FunctionDescriptor descriptor, ITriggeredFunctionExecutor executor, ITriggerBinding binding, SharedQueueHandler sharedQueue) + public ListenerFactory(FunctionDescriptor descriptor, ITriggeredFunctionExecutor executor, ITriggerBinding binding, SharedQueueHandler sharedQueue, SingletonManager singletonManager, ILoggerFactory loggerFactory) { _descriptor = descriptor; _executor = executor; _binding = binding; _sharedQueue = sharedQueue; + _singletonManager = singletonManager; + _loggerFactory = loggerFactory; } public async Task CreateAsync(CancellationToken cancellationToken) { ListenerFactoryContext context = new ListenerFactoryContext(_descriptor, _executor, _sharedQueue, cancellationToken); - return await _binding.CreateListenerAsync(context); + IListener listener = await _binding.CreateListenerAsync(context); + + if (listener != null) + { + // if the listener is a Singleton, wrap it with our SingletonListener + Type listenerType = listener.GetType(); + SingletonAttribute singletonAttribute = SingletonManager.GetListenerSingletonOrNull(listenerType, _descriptor); + if (singletonAttribute != null) + { + listener = new SingletonListener(_descriptor, singletonAttribute, _singletonManager, listener, _loggerFactory); + } + } + + return listener; } } diff --git a/src/Microsoft.Azure.WebJobs.Host/Listeners/SingletonListenerDecorator.cs b/src/Microsoft.Azure.WebJobs.Host/Listeners/SingletonListenerDecorator.cs deleted file mode 100644 index 76a0b6a2f..000000000 --- a/src/Microsoft.Azure.WebJobs.Host/Listeners/SingletonListenerDecorator.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using Microsoft.Extensions.Logging; - -namespace Microsoft.Azure.WebJobs.Host.Listeners -{ - internal class SingletonListenerDecorator : IListenerDecorator - { - private readonly SingletonManager _singletonManager; - private readonly ILoggerFactory _loggerFactory; - - public SingletonListenerDecorator(SingletonManager singletonManager, ILoggerFactory loggerFactory) - { - _singletonManager = singletonManager; - _loggerFactory = loggerFactory; - } - - public IListener Decorate(ListenerDecoratorContext context) - { - var functionDescriptor = context.FunctionDefinition.Descriptor; - - // if the listener is a Singleton, wrap it with our SingletonListener - IListener listener = context.Listener; - SingletonAttribute singletonAttribute = SingletonManager.GetListenerSingletonOrNull(context.ListenerType, functionDescriptor); - if (singletonAttribute != null) - { - listener = new SingletonListener(functionDescriptor, singletonAttribute, _singletonManager, listener, _loggerFactory); - } - - return listener; - } - } -} diff --git a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs index dc0b55cd8..1fcdf875a 100644 --- a/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs +++ b/test/Microsoft.Azure.WebJobs.Host.FunctionalTests/HostListenerFactoryTests.cs @@ -50,15 +50,10 @@ public async Task CreateAsync_AppliesListenerDecorators() var innerListeners = ((IEnumerable)listener).ToArray(); var innerListener = innerListeners[0]; - // expect the first two outer listeners to be our platform listeners + // expect the first listener to be our platform FunctionListener FunctionListener functionListener = (FunctionListener)innerListener; var innerListenerField = typeof(FunctionListener).GetField("_listener", BindingFlags.NonPublic | BindingFlags.Instance); - innerListener = (SingletonListener)innerListenerField.GetValue(functionListener); - - innerListenerField = typeof(SingletonListener).GetField("_innerListener", BindingFlags.NonPublic | BindingFlags.Instance); - innerListener = (IListener)innerListenerField.GetValue(innerListener); - - TestListenerDecorator.DecoratorListener decoratorListener = (TestListenerDecorator.DecoratorListener)innerListener; + TestListenerDecorator.DecoratorListener decoratorListener = (TestListenerDecorator.DecoratorListener)innerListenerField.GetValue(functionListener); // verify all decorators were consulted, resulting in a nested stack of listeners Assert.Equal("C", decoratorListener.Tag); @@ -67,6 +62,14 @@ public async Task CreateAsync_AppliesListenerDecorators() decoratorListener = (TestListenerDecorator.DecoratorListener)decoratorListener.InnerListener; Assert.Equal("A", decoratorListener.Tag); + // after decorators, we expect the Singleton listener + SingletonListener singletonListener = (SingletonListener)decoratorListener.InnerListener; + + // finally, the QueueListener + innerListenerField = typeof(SingletonListener).GetField("_innerListener", BindingFlags.NonPublic | BindingFlags.Instance); + innerListener = (IListener)innerListenerField.GetValue(singletonListener); + Assert.Equal("QueueListener", innerListener.GetType().Name); + var logProvider = _testHost.GetTestLoggerProvider(); var logs = logProvider.GetAllLogMessages().Where(p => p.Category == LogCategories.Startup && p.FormattedMessage.Contains(nameof(IListenerDecorator))).ToArray(); Assert.Equal(3, logs.Length);