Skip to content
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Unreleased

- Windows and macOS hosts now shut the embedded backend down orderly on
session end. The Windows system adapter gains InstallShutdownHook
(SetConsoleCtrlHandler: CTRL_C/CLOSE/LOGOFF/SHUTDOWN) and the scaffold
wires it plus the window-close path to a thread-safe backend stop; the
macOS scaffold installs an NSApplicationDelegate whose
applicationShouldTerminate stops the backend for Cmd-Q, logout, and OS
shutdown. Mirrors the Linux InstallShutdownHook shipped by #128 and
wired into the scaffold by #168. Fixes #120.
- Document the large-payload conventions (docs/large-payloads.md): cursor
pagination with fail-closed limit caps for report-shaped RPCs, stable-id
tree nodes with windowed children for virtualized trees, and typed
Expand All @@ -13,6 +21,7 @@
the listener) plus a sequential request-capture fake server with
loopback close-race handling. Test suites only; never enters an embedded
payload. Fixes #98.

- Cut ~100 MB (48%) from Windows release payloads. The packaged app no
longer ships MSBuild debug symbols (*.pdb, 60+ MB) or the ONNX and
DirectML inference runtimes (~40 MB) that Windows App SDK
Expand Down
21 changes: 21 additions & 0 deletions platform/macos/host/Sources/RivetHost/RivetHostApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import RivetSystem

@main
struct RivetHostApp: App {
@NSApplicationDelegateAdaptor(ApplicationDelegate.self)
private var applicationDelegate
@StateObject private var model = AppModel()
private let activationRouter = RivetActivationRouter()

Expand All @@ -23,6 +25,22 @@ struct RivetHostApp: App {
}
}

/// Every termination path — Cmd-Q, window close, logout, and OS shutdown —
/// passes through applicationShouldTerminate before the process exits, so
/// the embedded backend gets one orderly stop hook. The AppModel registers
/// its backend shutdown once startup succeeds.
@MainActor
final class ApplicationDelegate: NSObject, NSApplicationDelegate {
static var orderlyShutdown: (() -> Void)?

nonisolated func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
// AppKit delivers this callback on the main thread, where the
// registered shutdown closure lives.
MainActor.assumeIsolated { Self.orderlyShutdown?() }
return .terminateNow
}
}

@MainActor
final class AppModel: ObservableObject {
@Published var status = "Starting embedded Racket CS…"
Expand All @@ -41,6 +59,9 @@ final class AppModel: ObservableObject {
)
let backend = EmbeddedRacketBackend(configuration: config)
self.backend = backend
ApplicationDelegate.orderlyShutdown = { [weak backend] in
backend?.stop()
}

Task.detached { [backend] in
do {
Expand Down
17 changes: 17 additions & 0 deletions platform/windows/host/MainWindow.xaml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,26 @@ rivet::windows::RacketRuntimeConfig runtime_config() {
MainWindow::MainWindow() {
InitializeComponent();
Title(L"Rivet — Racket + WinUI 3");
// Ordinary window close and OS session end (logoff/shutdown) both funnel
// into the same orderly backend stop. The console-control callback runs
// on its own thread, so it only touches the thread-safe stop() — never
// XAML state.
Closed([this](auto const&, auto const&) { StopBackendOrderly(); });
try {
rivet::system::InstallShutdownHook([this] { StopBackendOrderly(); });
} catch (std::exception const&) {
// Signal coverage is platform policy and never blocks startup; the
// window-close path above still runs the orderly shutdown.
}
InitializeBackendAsync();
}

void MainWindow::StopBackendOrderly() {
if (auto backend = std::atomic_load(&backend_); backend != nullptr) {
backend->stop();
}
}

winrt::fire_and_forget MainWindow::InitializeBackendAsync() {
auto const dispatcher = DispatcherQueue();
auto const weak = get_weak();
Expand Down
4 changes: 4 additions & 0 deletions platform/windows/host/MainWindow.xaml.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ struct MainWindow : MainWindowT<MainWindow> {
return rivet::system::NativeWindowHandle(*this);
}

// Thread-safe orderly backend stop shared by the window-close path and
// the console-control shutdown hook (logoff/CTRL events).
void StopBackendOrderly();

void Increment_Click(winrt::Windows::Foundation::IInspectable const& sender,
Microsoft::UI::Xaml::RoutedEventArgs const& args);

Expand Down
28 changes: 28 additions & 0 deletions platform/windows/system/system_services.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <shellapi.h>
#include <wincred.h>

#include <functional>
#include <stdexcept>
#include <system_error>
#include <utility>
Expand Down Expand Up @@ -178,4 +179,31 @@ std::vector<std::wstring> ActivationArguments() {
return result;
}

namespace {

std::function<void()> g_shutdown_callback;

BOOL WINAPI ConsoleCtrlHandler(DWORD event_type) {
switch (event_type) {
case CTRL_C_EVENT:
case CTRL_CLOSE_EVENT:
case CTRL_LOGOFF_EVENT:
case CTRL_SHUTDOWN_EVENT:
if (g_shutdown_callback != nullptr) g_shutdown_callback();
return TRUE;
default:
return FALSE;
}
}

} // namespace

void InstallShutdownHook(std::function<void()> callback) {
g_shutdown_callback = std::move(callback);
if (::SetConsoleCtrlHandler(&ConsoleCtrlHandler, TRUE) == 0) {
g_shutdown_callback = nullptr;
ThrowLastError("SetConsoleCtrlHandler");
}
}

} // namespace rivet::system
10 changes: 10 additions & 0 deletions platform/windows/system/system_services.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ using CrashCallback = void (*)(EXCEPTION_POINTERS const*) noexcept;
void InstallCrashHook(CrashCallback callback,
std::wstring const& restart_arguments = L"");

// Mirrors the Linux adapter's InstallShutdownHook: the callback runs on a
// console-control thread for CTRL_C_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_
// EVENT, and CTRL_SHUTDOWN_EVENT (session logoff and OS shutdown reach GUI
// processes without a console through the last two). Keep it synchronous
// and allocation-light — the OS terminates the process shortly after the
// handler returns TRUE — and touch only thread-safe state such as the
// backend's stop(). Throws std::system_error when the handler cannot be
// registered.
void InstallShutdownHook(std::function<void()> callback);

// Windows delivers URL scheme and file-association activations through the
// process command line for unpackaged/MSI apps. Registration belongs to the
// installer; this helper normalizes the received payload for application code.
Expand Down
11 changes: 11 additions & 0 deletions tests/codegen.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
(file->string (build-path project-root "windows" "RivetHost.vcxproj")))
(define windows-main-header
(file->string (build-path project-root "windows" "MainWindow.xaml.h")))
(define windows-main-source
(file->string (build-path project-root "windows" "MainWindow.xaml.cpp")))
(define macos-app-source
(file->string (build-path project-root "macos-host" "Sources" "RivetHost" "RivetHostApp.swift")))
(define macos-package
(file->string (build-path project-root "macos-host" "Package.swift")))
(define linux-cmake
Expand All @@ -46,6 +50,13 @@
(check-true
(regexp-match? #rx"rivet::system::NativeWindowHandle" windows-main-header))
(check-true (regexp-match? #rx"HWND WindowHandle" windows-main-header))
(check-true
(regexp-match? #rx"rivet::system::InstallShutdownHook" windows-main-source))
(check-true (regexp-match? #rx"StopBackendOrderly" windows-main-header))
(check-true
(regexp-match? #rx"applicationShouldTerminate" macos-app-source))
(check-true
(regexp-match? #rx"orderlyShutdown" macos-app-source))
(check-false
(regexp-match? #rx"<WindowsTargetPlatformMinVersion>10\\.0\\.19041\\.0"
windows-project))
Expand Down
Loading