Reload on change works only once
victorhurdugaci opened this issue · 15 comments
From scenario: https://github.com/aspnet/Release/issues/66
- Create the default web app in VS
- Set all the log levels to
None
inappsettings.json
- Run and notice no logs in console (expected)
- Change Microsoft log level to
Debug
and notice logs in console (expected) - Change Microsoft log level back to
None
Expected: logs stop showing up
Actual: Debug logs still show up
It seems that the reload on change only works on the first change and it stops afterwards
@victorhurdugaci thanks for the details on this. Have you tried or are you planning to try reload on non-logging scenarios? Just wondering about the scope of the issue.
It's not logging specific. You can repro with this simple console app:
ConfigurationBuilder builder = new ConfigurationBuilder();
builder.AddJsonFile(s =>
{
s.Path = "appsettings.json";
s.ReloadOnChange = true;
});
var config = builder.Build();
var token = config.GetReloadToken();
token.RegisterChangeCallback(_ =>
{
Console.WriteLine("Changed");
}, null);
while(true)
{
Thread.Sleep(1000);
}
The token only fires once
The token only fires once
That is by actually by design. We supposedly have code in the callbacks in components that are listening to a change token (e.g. Logging and reloadable options) that takes care of rewiring the callback to a new token, but apparently that isn't working correctly.
Okay, then this is a logging issue
cc @CesarBS because he tests logging
Okay, then this is a logging issue.
Yep, though this is probably the right repo for the bug anyway. Thanks.
I can't repro this issue for logging, here's the test I tried, which changes the configuration
[Fact]
public void ConsoleLogger_ReloadSettings_CanReloadMultipleTimesFromConfiguration()
{
var file = Path.GetTempFileName();
File.WriteAllText(file, @"{""LogLevel"": {""Test"":""Information""} }");
var config = new ConfigurationBuilder().SetBasePath(Path.GetTempPath()).AddJsonFile(Path.GetFileName(file), optional: false, reloadOnChange: true).Build();
// Arrange
var settings = new ConfigurationConsoleLoggerSettings(config);
var loggerFactory = new LoggerFactory();
loggerFactory.AddConsole(settings);
var logger = loggerFactory.CreateLogger("Test");
Assert.False(logger.IsEnabled(LogLevel.Trace));
// Act & Assert
for (var i = 0; i < 10; i++)
{
if (i % 2 == 0)
{
File.WriteAllText(file, @"{""LogLevel"": {""Test"":""Information""} }");
}
else
{
File.WriteAllText(file, @"{""LogLevel"": {""Test"":""Trace""} }");
}
Thread.Sleep(100);
Assert.Equal(i % 2 == 1, logger.IsEnabled(LogLevel.Trace));
}
}
I tried manually on an app as well, toggling Microsoft to None/Information a few times and things worked as far as I could tell with the logs showing up and then not showing up each time
I am currently experiencing this issue as well using rc2-final... The event only gets fired once for my appsettings.json file.
Repro Steps
- Copy
Program.cs
below. - Add appsettings.json to project
- Change properties for appsettings.json to copy to output directory.
- Compile and run the code.
- Edit the appsettings.json in the
bin\Debug
folder (ie. changingoptions2
to another value) - Notice how it updates
- Update the appsettings.json again.
Expected
The callback in RegisterChangeCallback
gets fired again.
Actual
Nothing happens. (But I can see that the values in IConfigurationRoot
have changed with each update... just that the event isn't firing.)
Program.cs
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace configuration_demo
{
class Program
{
static void Main(string[] args)
{
var autoReset = new AutoResetEvent(false);
var builder = new ConfigurationBuilder();
builder.AddJsonFile(s =>
{
s.Path = "appsettings.json";
s.ReloadOnChange = true;
});
var root = builder.Build();
var reloadToken = root.GetReloadToken();
reloadToken.RegisterChangeCallback(callback =>
{
autoReset.Set();
}, null);
Print(root);
Console.WriteLine();
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Waiting for a change in values...");
autoReset.WaitOne(TimeSpan.FromMinutes(1));
Console.WriteLine("Values changed!");
Console.WriteLine("------------------------------------");
Print(root);
Console.WriteLine("------------------------------------");
}
}
private static void Print(IConfigurationRoot root)
{
foreach (var child in root.GetChildren())
{
Print(new Stack<string>(), child);
}
}
private static void Print(Stack<string> keys, IConfigurationSection section)
{
var children = section.GetChildren();
if (!children.Any())
{
var key = ConfigurationPath.Combine(keys.Reverse());
var fullKey = string.IsNullOrEmpty(key) ? section.Key : ConfigurationPath.Combine(key, section.Key);
Console.WriteLine($"[{fullKey}] : {section.Value}");
return;
}
keys.Push(section.Key);
foreach (var child in children)
{
Print(keys, child);
}
keys.Pop();
}
}
}
RegisterChangeCallback is only going to fire once, you need to reregister it for it be fired again.
There's a helper in Primitives which takes care of making the callback persistent
ChangeToken.OnChange(() => root.GetReloadToken(), () => autoReset.Set());
@HaoK Thanks for clarifying this behaviour! Is there going to be any documentation explaining this? There weren't any docs to go off of, so I assumed that it would be persistent.
I hope so :)
I created an issue in the docs repo: dotnet/AspNetCore.Docs#1291
I have the same issue so I used code by @conniey except I use ChangeToken.OnChange as suggested.
Also I added SetBasePath because VS starts program in bin\Debug\netcoreapp1.0\ but working dir is project's root dir, where I have settings file.
My Main looks like this. I open the appsettings.json file in editor (Notepad++)
{
"key": "value"
}
Then I just add line break to the file and save. 4 times it worked, but then any changes in file no longer trigger event. I am on Windows 7 x64 SP1 Enterprise.
static void Main(string[] args)
{
var autoReset = new AutoResetEvent(false);
var builder = new ConfigurationBuilder();
builder
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
var root = builder.Build();
ChangeToken.OnChange(() => root.GetReloadToken(), () => autoReset.Set());
Print(root);
Console.WriteLine();
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Waiting for a change in values...");
autoReset.WaitOne(TimeSpan.FromMinutes(1));
Console.WriteLine("Values changed!");
Console.WriteLine("------------------------------------");
Print(root);
Console.WriteLine("------------------------------------");
}
}