I created this test using .NET 8 on Ubuntu 24.04:
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
using Shouldly;
[TestFixture]
public class PhysicalFileProviderOnChangeTests
{
private string _tempDirectory;
private string _testFilePath;
[SetUp]
public void SetUp()
{
_tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(_tempDirectory);
_testFilePath = Path.Combine(_tempDirectory, "testfile.txt");
File.WriteAllText(_testFilePath, "Hello, World!");
var provider = new PhysicalFileProvider(_tempDirectory);
}
[Test]
public async Task TestOnChange_Handler()
{
var provider = new PhysicalFileProvider(_tempDirectory);
var changeToken = provider.Watch("testfile.txt");
var changeDetected = false;
ChangeToken.OnChange(
() => changeToken,
() => changeDetected = true
);
File.WriteAllText(_testFilePath, "New Content");
await Task.Delay(100);
changeDetected.ShouldBeTrue();
}
[TearDown]
public void TearDown()
{
if (Directory.Exists(_tempDirectory))
{
Directory.Delete(_tempDirectory, true);
}
}
}
When running this test using dotnet test
I get this exception:
The active test run was aborted. Reason: Test host process crashed : Stack overflow.
Repeat 10915 times:
--------------------------------
at System.Threading.CancellationTokenSource.Register(System.Delegate, System.Object, System.Threading.SynchronizationContext, Sys
tem.Threading.ExecutionContext)
at System.Threading.CancellationToken.Register(System.Delegate, System.Object, Boolean, Boolean)
at Microsoft.Extensions.Internal.ChangeCallbackRegistrar.UnsafeRegisterChangeCallback[[System.__Canon, System.Private.CoreLib, Ve
rsion=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Action`1<System.Object>, System.Object, System.Threading.Ca
ncellationToken, System.Action`1<System.__Canon>, System.__Canon)
at Microsoft.Extensions.Primitives.CancellationChangeToken.RegisterChangeCallback(System.Action`1<System.Object>, System.Object)
at Microsoft.Extensions.Primitives.ChangeToken+ChangeTokenRegistration`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0
, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].RegisterChangeTokenCallback(Microsoft.Extensions.Primitives.IChangeToken)
at Microsoft.Extensions.Primitives.ChangeToken+ChangeTokenRegistration`1[[System.__Canon, System.Private.CoreLib, Version=8.0.0.0
, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnChangeTokenFired()
at Microsoft.Extensions.Primitives.ChangeToken+ChangeTokenRegistration`1+<>c[[System.__Canon, System.Private.CoreLib, Version=8.0
.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].<RegisterChangeTokenCallback>b__7_0(System.Object)
at System.Threading.CancellationTokenSource.Invoke(System.Delegate, System.Object, System.Threading.CancellationTokenSource)
--------------------------------
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean)
at Microsoft.Extensions.FileProviders.Physical.PhysicalFilesWatcher+<>c.<.cctor>b__43_0(System.Object)
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(System.Threading.Thread, System.Threading.ExecutionContext, Sy
stem.Threading.ContextCallback, System.Object)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread)
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart()
Running the same test in another project, it works.
Commenting out this block, the test fails due to the true/false assertion but it doesn't crash:
ChangeToken.OnChange(
() => changeToken,
() => changeDetected = true
);
There is an opened issue in dotNet Runtime (with an unmerged potential fix).
With a potential workaround mentioned there:
The known workaround is to ensure that the producer either returns a new change token, or at least evaluates the previous state and resets itself (if possible).
So might be try to follow this workaround with smth like:
ChangeToken.OnChange(
() => new PhysicalFileProvider(_tempDirectory).Watch("testfile.txt"),
() => changeDetected = true
);