Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Host] Allow disabling reloadOnChange for Host's CreateDefaultBuilder #2940

Merged
6 commits merged into from
Mar 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Hosting/Hosting/src/Host.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,11 @@ public static IHostBuilder CreateDefaultBuilder(string[] args)
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
var env = hostingContext.HostingEnvironment;

var reloadOnChange = hostingContext.Configuration.GetValue("hostBuilder:reloadConfigOnChange", defaultValue: true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also be set to https://github.com/dotnet/runtime/blob/79ae74f5ca5c8a6fe3a48935e85bd7374959c570/src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/FileConfigurationSource.cs#L33?

Apparently this condition is true: https://github.com/dotnet/runtime/blob/79ae74f5ca5c8a6fe3a48935e85bd7374959c570/src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/FileConfigurationProvider.cs#L34 even with "hostBuilder": {"reloadConfigOnChange": false } in appsettings.json.

kernel implementation without inotify support throw the following exception on application startup and there seems to be no way to turn (this quite an unimportant) feature off:

Unhandled exception. System.IO.IOException: Function not implemented
   at System.IO.FileSystemWatcher.StartRaisingEvents()
   at System.IO.FileSystemWatcher.StartRaisingEventsIfNotDisposed()
   at System.IO.FileSystemWatcher.set_EnableRaisingEvents(Boolean value)
   at Microsoft.Extensions.FileProviders.Physical.PhysicalFilesWatcher.TryEnableFileSystemWatcher()
   at Microsoft.Extensions.FileProviders.Physical.PhysicalFilesWatcher.CreateFileChangeToken(String filter)
   at Microsoft.Extensions.FileProviders.PhysicalFileProvider.Watch(String filter)
   at Microsoft.Extensions.Configuration.FileConfigurationProvider.<.ctor>b__1_0()
   at Microsoft.Extensions.Primitives.ChangeToken.OnChange(Func`1 changeTokenProducer, Action changeTokenConsumer)
   at Microsoft.Extensions.Configuration.FileConfigurationProvider..ctor(FileConfigurationSource source)
   at Microsoft.Extensions.Configuration.Json.JsonConfigurationSource.Build(IConfigurationBuilder builder)
   at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
   at Microsoft.Extensions.Hosting.HostBuilder.BuildAppConfiguration()
   at Microsoft.Extensions.Hosting.HostBuilder.Build()
   at mymvc1.Program.Main(String[] args)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got the global opt-out knob working with: dotnet/runtime@457ed11; DOTNET_USE_POLLING_FILE_WATCHER=1 in unikernel environment completely disables inotify. sadly, it wasn't backported to net5 so i switched to net6..

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least for HostBuilder, at the time this was being changed if you used CreateDefaultBuilder() you were getting live reloading whether you wanted it or not because it was hard coded to true

If Source.ReloadOnChange is populated by an env var and is available by the time ConfigureAppConfiguration() fires then that would work a bit better.


config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange);

if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
{
Expand Down
77 changes: 77 additions & 0 deletions src/Hosting/Hosting/test/HostTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using System.Diagnostics.Tracing;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -81,6 +83,81 @@ public void CreateDefaultBuilder_EnablesValidateOnBuild()
Assert.Throws<AggregateException>(() => hostBuilder.Build());
}

[Fact]
public async Task CreateDefaultBuilder_ConfigJsonDoesNotReload()
{
var reloadFlagConfig = new Dictionary<string, string>() {{ "hostbuilder:reloadConfigOnChange", "false" }};
var appSettingsPath = Path.Combine(Path.GetTempPath(), "appsettings.json");

string SaveRandomConfig()
{
var newMessage = $"Hello ASP.NET Core: {Guid.NewGuid():N}";
File.WriteAllText(appSettingsPath, $"{{ \"Hello\": \"{newMessage}\" }}");
return newMessage;
}

var dynamicConfigMessage1 = SaveRandomConfig();

var host = Host.CreateDefaultBuilder()
.UseContentRoot(Path.GetDirectoryName(appSettingsPath))
.ConfigureHostConfiguration(builder =>
{
builder.AddInMemoryCollection(reloadFlagConfig);
})
.Build();

var config = host.Services.GetRequiredService<IConfiguration>();

Assert.Equal(dynamicConfigMessage1, config["Hello"]);

var dynamicConfigMessage2 = SaveRandomConfig();
await Task.Delay(1000); // Give reload time to fire if it's going to.
Assert.NotEqual(dynamicConfigMessage1, dynamicConfigMessage2); // Messages are different.
Assert.Equal(dynamicConfigMessage1, config["Hello"]); // Config did not reload
}

[Fact]
public async Task CreateDefaultBuilder_ConfigJsonDoesReload()
{
var reloadFlagConfig = new Dictionary<string, string>() { { "hostbuilder:reloadConfigOnChange", "true" } };
var appSettingsPath = Path.Combine(Path.GetTempPath(), "appsettings.json");

string SaveRandomConfig()
{
var newMessage = $"Hello ASP.NET Core: {Guid.NewGuid():N}";
File.WriteAllText(appSettingsPath, $"{{ \"Hello\": \"{newMessage}\" }}");
return newMessage;
}

var dynamicConfigMessage1 = SaveRandomConfig();

var host = Host.CreateDefaultBuilder()
.UseContentRoot(Path.GetDirectoryName(appSettingsPath))
.ConfigureHostConfiguration(builder =>
{
builder.AddInMemoryCollection(reloadFlagConfig);
})
.Build();

var config = host.Services.GetRequiredService<IConfiguration>();

Assert.Equal(dynamicConfigMessage1, config["Hello"]);

var dynamicConfigMessage2 = SaveRandomConfig();

var configReloadedCancelTokenSource = new CancellationTokenSource();
var configReloadedCancelToken = configReloadedCancelTokenSource.Token;

config.GetReloadToken().RegisterChangeCallback(o =>
{
configReloadedCancelTokenSource.Cancel();
}, null);
// Wait for up to 10 seconds, if config reloads at any time, cancel the wait.
await Task.WhenAny(Task.Delay(10000, configReloadedCancelToken)); // Task.WhenAny ignores the task throwing on cancellation.
Assert.NotEqual(dynamicConfigMessage1, dynamicConfigMessage2); // Messages are different.
Assert.Equal(dynamicConfigMessage2, config["Hello"]); // Config DID reload from disk
}

internal class ServiceA { }

internal class ServiceB
Expand Down