Skip to content

Unhandled exception thrown in FileSystemWatcher event handler, don't terminate task/application under linux #46978

@koepalex

Description

@koepalex

Description

When a unhandled exception is raised in event handler of FileSystemWatcher (what is local member of a task) the exception terminates the task (and application) when running under windows. Under Linux the application is not terminated.

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadException
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // Generate test file
            var filePath = GetAbsolutePath("Data/test.json");
            Directory.CreateDirectory(GetAbsolutePath("Data"));
            await File.WriteAllTextAsync(filePath, "[]", Encoding.UTF8);
            
            var cts = new CancellationTokenSource();
            Console.CancelKeyPress += (sender, args) => {
                cts.Cancel();
            };

            // Start the tasks, that do the work
            await RunMainProgramAsync(cts.Token);

            System.Console.WriteLine("Program Finished!");
        }

        private static async Task RunMainProgramAsync(CancellationToken token) 
        {
            try
            {
                await Task.Run(async () => {
                    using var observer = new FSObserver();
                    while(!token.IsCancellationRequested)
                    {
                        observer.DoSomething();
                        await Task.Delay(250);
                    }
                }, token);
            }
            catch(Exception e)
            {
                System.Console.Error.WriteLine(e.Message);
            }
        }

        public static string GetAbsolutePath(string relativePath)
        {
            var _dataRoot = new FileInfo(typeof(Program).Assembly.Location);
            string assemblyFolderPath = _dataRoot.Directory.FullName;
            string fullPath = Path.Combine(assemblyFolderPath, relativePath);
            return fullPath;
        }
    }

    class FSObserver : IDisposable
    {
        private FileSystemWatcher _fs; 
        private string _filePath;
        public FSObserver()
        {
            _filePath = Program.GetAbsolutePath("Data");
            _fs = new FileSystemWatcher();
            _fs.Path = _filePath;
            _fs.Filter = "test.json";
            _fs.EnableRaisingEvents = true;

            _fs.Changed += (sender, args) => {
                System.Console.WriteLine($"file {args.FullPath} was changed: {args.ChangeType}");
                ReloadModel();
            };
        }

        public void DoSomething()
        {
            System.Console.WriteLine("write something");
        }

        public void Dispose()
        {
            _fs?.Dispose();
        }

        private void ReloadModel()
        {
            // Simulate an error while parsing model
            throw new Exception("ohh no :-/");
        }
    }
}

Test steps:

  • run the program
  • open shell in folder with executables
  • update the file
echo "" > test.json

Behavior under windows:
Application is terminated.

Behavior under linux:
Application is still running, file system watch don't fire events on further changes to file. It seems that the tasks, that hosted the filesystem watcher is still running.

Expectation:
The behavior under both OS should be the same, from my point of view unhandled exception should crash the application.

Configuration

Tested with:

  • netcoreapp3.1 (3.1.405)
  • net5.0 (5.0.102)

Operating systems

  • Win10 (20H2)
  • Ubuntu 18.04
  • Ubuntu 20.04

Architecture:

  • x64

Regression?

n/a

Other information

Console output (Windows):

write something
file C:\ThreadException\bin\Debug\net5.0\Data\test.json was changed: Changed
Exception thrown: 'System.Exception' in ThreadException.dll
write something
Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.2\System.Diagnostics.StackTrace.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.2\System.Reflection.Metadata.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.2\System.Collections.Immutable.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.2\System.Runtime.InteropServices.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
write something
Unhandled exception. System.Exception: ohh no :-/
   at ThreadException.FSObserver.ReloadModel() in C:\Users\alkopke\repo\hackathon\ThreadException\Program.cs:line 88
   at ThreadException.FSObserver.<.ctor>b__2_0(Object sender, FileSystemEventArgs args) in C:\Users\alkopke\repo\hackathon\ThreadException\Program.cs:line 71
   at System.IO.FileSystemWatcher.NotifyFileSystemEventArgs(WatcherChangeTypes changeType, ReadOnlySpan`1 name)
   at System.IO.FileSystemWatcher.ParseEventBufferAndNotifyForEach(Byte[] buffer, UInt32 numBytes)
   at System.IO.FileSystemWatcher.ReadDirectoryChangesCallback(UInt32 errorCode, UInt32 numBytes, AsyncReadState state)
   at System.IO.FileSystemWatcher.<>c.<StartRaisingEvents>b__83_0(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* overlappedPointer)
   at System.Threading.ThreadPoolBoundHandleOverlapped.CompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
   at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pNativeOverlapped)
The program '[34932] ThreadException.dll' has exited with code 0 (0x0).

Console output (Linux):

write something
file /home/sandboxuser/ThreadException/bin/Debug/net5.0/Data/test.json was changed: Changed
Exception thrown: 'System.Exception' in ThreadException.dll
write something
Loaded '/usr/share/dotnet/shared/Microsoft.NETCore.App/5.0.2/System.Diagnostics.StackTrace.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Loaded '/usr/share/dotnet/shared/Microsoft.NETCore.App/5.0.2/System.Reflection.Metadata.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
Loaded '/usr/share/dotnet/shared/Microsoft.NETCore.App/5.0.2/System.Collections.Immutable.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
write something

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-System.IOhelp wanted[up-for-grabs] Good issue for external contributors

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions