# How Shadowsocks-Windows Catches and Reports Unhandled Exceptions in UI and Non-UI Threads

> Learn how Shadowsocks Windows catches and reports unhandled exceptions in UI and non-UI threads using Application ThreadException and AppDomain CurrentDomain UnhandledException for graceful termination and logging.

- Repository: [shadowsocks/shadowsocks-windows](https://github.com/shadowsocks/shadowsocks-windows)
- Tags: internals
- Published: 2026-03-05

---

**Shadowsocks-Windows registers two global exception handlers in [`Program.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/Program.cs)—`Application.ThreadException` for WinForms UI threads and `AppDomain.CurrentDomain.UnhandledException` for background threads—to log errors via NLog, display a modal dialog, and terminate gracefully.**

The shadowsocks/shadowsocks-windows proxy client implements a defensive crash-reporting pipeline that captures unhandled exceptions across both UI and non-UI threads. By wiring global handlers immediately after startup compatibility checks, the application ensures that any fatal error triggers consistent logging and user notification before shutdown. This article examines the exact mechanisms used to catch and report unhandled exceptions in both UI and non-UI threads based on the v4 source code.

## Global Exception Handler Registration

In [`shadowsocks-csharp/Program.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Program.cs), the application attaches both handlers before the main message loop begins. Lines 104-107 subscribe to Windows Forms UI exceptions, while lines 108-109 capture domain-wide failures from any background thread.

```csharp
// Program.cs – lines 104-109
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
Application.ThreadException += Application_ThreadException;          // UI thread
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; // Non-UI threads

```

This registration strategy ensures that **every** unhandled exception—whether from a button click or a background Task—flows through one of these two centralized handlers.

## Catching Unhandled Exceptions in UI Threads

WinForms applications rely on `Application.ThreadException` to intercept exceptions that bubble out of event handlers. When a UI callback throws, the runtime raises this event before crashing the process, allowing the `Application_ThreadException` method to execute recovery logic.

### UI Handler Implementation

The handler uses an **atomic exit guard** to prevent multiple error dialogs if several exceptions occur simultaneously. It logs the full stack trace via the NLog `logger` instance and presents a localized `MessageBox` directing users to the GitHub Issues tracker.

```csharp
// UI thread handler (Program.cs)
private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
    if (Interlocked.Increment(ref exited) == 1)               // Run only once
    {
        string errorMsg = $"Exception Detail: {Environment.NewLine}{e.Exception}";
        logger.Error(errorMsg);                               // Log to NLog
        
        MessageBox.Show(
            $"{I18N.GetString("Unexpected error, shadowsocks will exit. Please report to")} " +
            "https://github.com/shadowsocks/shadowsocks-windows/issues " +
            $"{Environment.NewLine}{errorMsg}",
            "Shadowsocks UI Error",
            MessageBoxButtons.OK,
            MessageBoxIcon.Error);
            
        Application.Exit();                                   // Terminate gracefully
    }
}

```

The dialog title **"Shadowsocks UI Error"** distinguishes these crashes from background thread failures.

## Catching Unhandled Exceptions in Non-UI Threads

Background workers, `ThreadPool` threads, and unawaited `Task` continuations do not trigger `Application.ThreadException`. Instead, they raise `AppDomain.CurrentDomain.UnhandledException`, which the application handles via `CurrentDomain_UnhandledException`.

### Background Thread Handler Implementation

This handler follows the same pattern as its UI counterpart: it checks the exit guard, logs the exception object via `logger.Error()`, and displays a distinct **"Shadowsocks non-UI Error"** message box before terminating.

```csharp
// Non-UI thread handler (Program.cs)
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    if (Interlocked.Increment(ref exited) == 1)
    {
        string errMsg = e.ExceptionObject.ToString();
        logger.Error(errMsg);                                 // Log exception details
        
        MessageBox.Show(
            $"{I18N.GetString("Unexpected error, shadowsocks will exit. Please report to")} " +
            "https://github.com/shadowsocks/shadowsocks-windows/issues " +
            $"{Environment.NewLine}{errMsg}",
            "Shadowsocks non-UI Error",
            MessageBoxButtons.OK,
            MessageBoxIcon.Error);
            
        Application.Exit();                                   // Terminate gracefully
    }
}

```

Both handlers ultimately call `Application.Exit()` to ensure the process shuts down cleanly rather than leaving zombie sockets or partial proxy configurations.

## Thread-Safe Safeguards and Logging

The **exit-once guard** (`Interlocked.Increment(ref exited) == 1`) is critical for thread safety. Because multiple threads could fail simultaneously, this atomic operation guarantees that only the first exception triggers the modal dialog and logging sequence.

The `logger` instance referenced in both handlers is defined in [`Util/Util.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/Util/Util.cs) and configured via [`Model/NlogConfig.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/Model/NlogConfig.cs). According to the source, errors are written to disk before the user sees the message box, ensuring that crash diagnostics survive even if the UI thread hangs during shutdown.

## Summary

- **Dual-handler architecture**: `Application.ThreadException` catches WinForms UI errors, while `AppDomain.CurrentDomain.UnhandledException` captures background thread failures.
- **Atomic exit guard**: `Interlocked.Increment(ref exited) == 1` prevents duplicate error dialogs during concurrent exceptions.
- **Consistent logging**: NLog records full exception details via `logger.Error()` before displaying the user notification.
- **Graceful termination**: Both handlers invoke `Application.Exit()` after showing the error message, ensuring the proxy client shuts down cleanly.
- **User guidance**: All dialogs include a localized message and a direct link to `https://github.com/shadowsocks/shadowsocks-windows/issues`.

## Frequently Asked Questions

### What is the difference between Application.ThreadException and AppDomain.CurrentDomain.UnhandledException in Shadowsocks-Windows?

`Application.ThreadException` specifically catches exceptions thrown within WinForms event handlers on the main UI thread, such as button clicks or menu selections. `AppDomain.CurrentDomain.UnhandledException` is a broader CLR-level hook that captures exceptions escaping from any other thread, including background workers, thread-pool threads, and unawaited async Tasks.

### How does Shadowsocks-Windows prevent multiple error dialogs from appearing simultaneously?

Both handlers in [`Program.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/Program.cs) share a static integer counter `exited` and use `Interlocked.Increment(ref exited) == 1` as a guard clause. This atomic operation ensures that only the first thread to encounter an unhandled exception executes the logging and dialog logic, while subsequent threads skip the UI interaction and allow the process to terminate.

### Where are unhandled exception logs stored in Shadowsocks-Windows?

The handlers write to an NLog `logger` instance defined in [`Util/Util.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/Util/Util.cs) and configured in [`Model/NlogConfig.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/Model/NlogConfig.cs). The exact log file path depends on the NLog configuration, but typically writes to the application directory or user data folder, preserving exception details even if the UI crashes.

### Why does Shadowsocks-Windows terminate immediately after catching an unhandled exception?

The application calls `Application.Exit()` inside both handlers because an unhandled exception indicates an undefined or corrupted state. Continuing execution could lead to memory leaks, security vulnerabilities, or inconsistent proxy routing. Exiting immediately ensures that the user is forced to restart with a clean state rather than operating with a potentially compromised connection.