14

I have an console application (in dot net core 1.1) which is scheduled in cron scheduler for every 1 min. Inside the application there is call to configuration file. I'm attaching the code below.

public static T GetAppConfig<T>(string key, T defaultValue = default(T))
{

    T value = default(T);
    logger.Debug($"Reading App Config {key}");
    try
    {
        var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
        var builder = new ConfigurationBuilder()
            .AddJsonFile($"appsettings.json", true, true)
            .AddJsonFile($"appsettings.{environmentName}.json", true, true)
            .AddEnvironmentVariables();
        var configuration = builder.Build();
        T setting = (T)Convert.ChangeType(configuration[key], typeof(T));
        value = setting;
        if (setting == null)
            value = defaultValue;
    }
    catch (Exception ex)
    {
        logger.Warn(ex, $"An exception occured reading app key {key} default value {defaultValue} applied.");
        value = defaultValue;
    }
    return value;
}

After running the application for sometime, this error is getting in my log file "the configured user limit (128) on the number of inotify instances has been reached". Please find the full stack trace.

An exception occured reading app key DeviceId default value  applied.
System.IO.IOException: The configured user limit (128) on the number of inotify instances has been reached.
   at System.IO.FileSystemWatcher.StartRaisingEvents()
   at System.IO.FileSystemWatcher.StartRaisingEventsIfNotDisposed()
   at Microsoft.Extensions.FileProviders.Physical.PhysicalFilesWatcher.CreateFileChangeToken(String filter)
   at Microsoft.Extensions.Primitives.ChangeToken.OnChange(Func`1 changeTokenProducer, Action changeTokenConsumer)
   at Microsoft.Extensions.Configuration.Json.JsonConfigurationSource.Build(IConfigurationBuilder builder)
   at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
   at app.Shared.Utilities.GetAppConfig[T](String key, T defaultValue) in /var/app/source/app/app.Shared/Utilities.cs:line 33
   at System.IO.FileSystemWatcher.StartRaisingEvents()
   at System.IO.FileSystemWatcher.StartRaisingEventsIfNotDisposed()
   at Microsoft.Extensions.FileProviders.Physical.PhysicalFilesWatcher.CreateFileChangeToken(String filter)
   at Microsoft.Extensions.Primitives.ChangeToken.OnChange(Func`1 changeTokenProducer, Action changeTokenConsumer)
   at Microsoft.Extensions.Configuration.Json.JsonConfigurationSource.Build(IConfigurationBuilder builder)
   at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
   at app.Shared.Utilities.GetAppConfig[T](String key, T defaultValue) in /var/app/source/app/app.Shared/Utilities.cs:line 33

Please tell me what is wrong with this code.

Athari
  • 33,702
  • 16
  • 105
  • 146
Vipin
  • 938
  • 5
  • 18
  • 36

3 Answers3

18
var builder = new ConfigurationBuilder()
        .AddJsonFile($"appsettings.json", true, true);

You are creating file watchers, every time you access an setting. The 3rd parameter is reloadOnChange.

You have to make sure,

var configuration = builder.Build()

is only called once in your application and store it in a place where you can access it (preferably AVOID static fields for it).

Or just disable the file watcher.

  var builder = new ConfigurationBuilder()
        .AddJsonFile($"appsettings.json", true, false);

or cleaner:

var builder = new ConfigurationBuilder()
        .AddJsonFile($"appsettings.json", optional: true, reloadOnChange: false);

Best way is to abstract that behind an interface and use dependency injection.

public interface IConfigurationManager
{
    T GetAppConfig<T>(string key, T defaultValue = default(T));
}

public class ConfigurationManager : IConfigurationManager
{
    private readonly IConfigurationRoot config;

    public ConfigurationManager(IConfigurationRoot config)
        => this.config ?? throw new ArgumentNullException(nameof(config));

    public T GetAppConfig<T>(string key, T defaultValue = default(T))
    {
        T setting = (T)Convert.ChangeType(configuration[key], typeof(T));
        value = setting;
        if (setting == null)
            value = defaultValue;
    }
}

Then instantiate and register it

services.AddSingleton<IConfigurationManager>(new ConfigurationManager(this.Configuration));

and inject it into your services via constructor

slolife
  • 19,520
  • 20
  • 78
  • 121
Tseng
  • 61,549
  • 15
  • 193
  • 205
  • Tseng, Thank for the reply. I will modify reload on change flag. Is that solve the issue? Because I'm running this under a scheduler. So if I'm making use of singleton ,next schedule run whether it use the same object. Because the container will be initialised again in main. – Vipin Aug 25 '17 at 08:17
  • Removing the reload flag should fix it, but it will still have to read the file on every execution though. As for singleton: It depends on how your scheduler works. If you create scoped containers per scheduler execution, the singleton will remain same for all instances. But that's out of scope of this question – Tseng Aug 25 '17 at 08:22
  • @Tseng why avoid static fields for configuration? – Anthony Mastrean Oct 26 '17 at 15:03
  • @AnthonyMastrean: Anti-pattern, no dependency injection, hard/impossible to test/mock, threading issues, tight coupling – Tseng Oct 26 '17 at 16:18
  • Ok, but not specifically related to consuming inotify instances. – Anthony Mastrean Oct 28 '17 at 00:28
  • But why do we get this error when `reloadOnChange` is true? This is happening on the first call. – James Hirschorn Jul 09 '20 at 06:23
6

The reason why error the configured user limit (128) on the number of inotify instances has been reached happens is right - on non Windows environment reloadOnChange cause the issue while accessing appSetting.json files.

But there is a think you could miss while adjusting this. I addition to setting reloadOnChange to false:

.AddJsonFile($"appsettings.json", optional: true, reloadOnChange: false);

you should also make sure you are not starting from default WebHost.CreateDefaultBuilder because inside it reloadOnChange is also set to true. So the best way to control what your web host is would be to configure it from scratch without not needed options (e.g. WebHost.CreateDefaultBuilder also does .UseIISIntegration() which probably don't need at all in your environment).

The example of custom web host - a copy of Microsoft WebHost.CreateDefaultBuilder but with IIS and FileWatcher dependencies removed e.g. for Linux environments.

Dmitry Pavlov
  • 30,789
  • 8
  • 97
  • 121
1

It looks like the configuration will reload automatically when the file is changed. So, you should only build the configuration once, when the application starts, and then just read from that.

Dan
  • 12,409
  • 3
  • 50
  • 87