We have a couple of functions in a function app based on a consumption plan. When I stress test these functions with JMeter (200 threads simultaneously), the functions are throwing SimpleInjector.ActivationException's for about 50% of the time. Not all requests are failing.
I don't get why this is only for a part of the requests.
Call stack:
2021-07-06T08:43:41.159 [Error] Er is een probleem opgetreden
SimpleInjector.ActivationException : The constructor of type GetLocalKeyDataByDateQueryHandler contains the parameter with name 'queryValidator' and type IValidator, but IValidator is not registered. For IValidator to be resolved, it must be registered in the container.at SimpleInjector.Container.ThrowParameterTypeMustBeRegistered(InjectionTargetInfo target)at SimpleInjector.Advanced.DefaultDependencyInjectionBehavior.GetInstanceProducer(InjectionConsumerInfo dependency,Boolean throwOnFailure)at SimpleInjector.ContainerOptions.GetInstanceProducerFor(InjectionConsumerInfo consumer)at SimpleInjector.Registration.BuildConstructorParameters(ConstructorInfo constructor)at SimpleInjector.Registration.BuildNewExpression()at SimpleInjector.Registration.BuildTransientExpression()at SimpleInjector.Registration.BuildTransientDelegate()at SimpleInjector.Lifestyles.ScopedRegistration.BuildExpression()at SimpleInjector.InstanceProducer.BuildExpressionInternal()at SimpleInjector.Internals.LazyEx`1.InitializeAndReturn()at SimpleInjector.InstanceProducer.BuildInstanceCreator()at SimpleInjector.InstanceProducer.BuildAndReplaceInstanceCreatorAndCreateFirstInstance()at SimpleInjector.InstanceProducer.GetInstance()at SimpleInjector.Container.GetInstanceTServiceat async ProRail.PUIC2.Web.Functions.GetPuicDataByChangeDate.Run(GetPuicDataByDate inputParams,ILogger logger,ExecutionContext context) at D:\a\1\s\Src\Web\ProRail.PUIC2.Web.Functions\Functions\GetPuicDataByChangeDate.cs : 35
Function:
public class GetPuicDataByLocalKey
{
[FunctionName("GetPuicDataByLocalKey")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]
Models.GetPuicDataByLocalKey queryInput, ILogger logger, ExecutionContext context)
{
var container = DependencyInjection.GetContainerInstance(context);
await using (AsyncScopedLifestyle.BeginScope(container))
{
var loggingUtils = container.GetInstance<ILoggingUtils>();
try
{
logger.LogTrace(loggingUtils.MethodBegin);
container.GetInstance<Configuration.ILoggerFactory>().SetLogger(logger);
var queryHandler = container.GetInstance<IQueryHandler<GetPuicDataQuery, IEnumerable<PuicData>>>();
var results = await queryHandler.Handle(new GetPuicDataQuery
{
Source = queryInput.Source,
DateValid = queryInput.DateValid,
LocalKeyValues = queryInput.LocalKeyValues.Select(lkv => new Core.Entities.Data.KeyValue
{
Key = lkv.Key,
Value = lkv.Value
})
});
if (results == null)
{
return new JsonResult(Enumerable.Empty<Models.PuicData>());
}
// Get local keys grouped by Puic
var puicDataList = results.GroupBy(l => l.Puic).Select(g => PuicDataConverter.Convert(g));
return new JsonResult(puicDataList);
}
catch (PuicException e)
{
logger.LogError(e, "Data is waarschijnlijk niet goed");
return new BadRequestErrorMessageResult(e.Message);
}
catch (Exception e)
{
logger.LogError(e, "Er is een probleem opgetreden");
return new InternalServerErrorResult();
}
finally
{
logger.LogTrace(loggingUtils.MethodEnd);
}
}
}
}
Container config
public static class DependencyInjection
{
private static Container containerInstance = null;
public static Container GetContainerInstance(ExecutionContext context)
{
if (containerInstance == null)
{
containerInstance = new Container();
containerInstance.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
InitializeConfiguration(containerInstance, context);
RegisterSimpleTypes(containerInstance);
RegisterQueryHandlers(containerInstance);
RegisterCommandHandlers(containerInstance);
RegisterValidators(containerInstance);
RegisterDatabaseTypes(containerInstance);
}
return containerInstance;
}
[...]
private static void RegisterQueryHandlers(Container container)
{
var businessLogicAssembly = typeof(GetUsersQuery).Assembly;
container.Register(typeof(IQueryHandler<,>), businessLogicAssembly, Lifestyle.Scoped);
}
[...]
}
It seems there is a multi-threading issue with your code; the DependencyInjection.GetContainerInstance
method.
GetContainerInstance
possibly creates many Container
instances when called in parallel and the configuration of the final Container
instance will be undetermined.
To fix this issue, you will have to synchronize the creation of the container. There are many ways to do this, but you can, for instance, try this:
public static Container GetContainerInstance(ExecutionContext context)
{
// Double-checked lock. Ensures only one Container instance
// is created.
if (containerInstance is null)
{
lock (typeof(DependencyInjection))
{
if (containerInstance is null)
{
containerInstance = BuildContainer();
}
}
}
return containerInstance;
}
private static Container BuildContainer()
{
var container = new Container();
container.Options.DefaultScopedLifestyle =
new AsyncScopedLifestyle();
InitializeConfiguration(container, context);
RegisterSimpleTypes(container);
RegisterQueryHandlers(container);
RegisterCommandHandlers(container);
RegisterValidators(container);
RegisterDatabaseTypes(container);
return container;
}