GraphiteEditor/Graphite · error

Error setting Ctrl-C handler

Error message

Error setting Ctrl-C handler

What it means

Panic when ctrlc::set_handler fails while the desktop app installs its Ctrl-C/SIGINT handler that schedules AppEvent::Exit. The ctrlc crate reports an error primarily when a handler is already installed for this process (Error::MultipleHandlers), and on some platforms when the underlying OS signal registration call returns an error. Only one ctrl-c handler may exist per process.

Source

Thrown at desktop/src/app.rs:72

impl App {
	pub(crate) fn init() {
		Window::init();
	}

	pub(crate) fn new(
		ui: UiInstance,
		wgpu_context: WgpuContext,
		app_event_receiver: Receiver<AppEvent>,
		app_event_scheduler: AppEventScheduler,
		preferences: Preferences,
		launch_documents: Vec<PathBuf>,
	) -> Self {
		let ctrlc_app_event_scheduler = app_event_scheduler.clone();
		ctrlc::set_handler(move || {
			tracing::info!("Termination signal received, exiting...");
			ctrlc_app_event_scheduler.schedule(AppEvent::Exit);
		})
		.expect("Error setting Ctrl-C handler");

		let exiting = Arc::new(AtomicBool::new(false));

		let rendering_app_event_scheduler = app_event_scheduler.clone();
		let (start_render_sender, start_render_receiver) = std::sync::mpsc::sync_channel(1);
		let exiting_clone = exiting.clone();
		std::thread::spawn(move || {
			let runtime = tokio::runtime::Runtime::new().unwrap();
			loop {
				let result = runtime.block_on(DesktopWrapper::execute_node_graph());
				rendering_app_event_scheduler.schedule(AppEvent::NodeGraphExecutionResult(result));
				let _ = start_render_receiver.recv_timeout(Duration::from_millis(10));
				if exiting_clone.load(Ordering::Relaxed) {
					break;
				}
			}
		});

View on GitHub (pinned to c507b35645)

Solutions

  1. Ensure the app object that installs the Ctrl-C handler is created exactly once per process
  2. Search the dependency tree for other ctrlc crate users and consolidate to a single owner of the handler
  3. In tests, construct the app once or factor the handler installation out of the constructor
  4. If a second registration is genuinely needed, switch to a channel-based handler shared by both call sites

Example fix

// before
ctrlc::set_handler(move || {
	tracing::info!("Termination signal received, exiting...");
	ctrlc_app_event_scheduler.schedule(AppEvent::Exit);
})
.expect("Error setting Ctrl-C handler");

// after
if let Err(e) = ctrlc::set_handler(move || {
	tracing::info!("Termination signal received, exiting...");
	ctrlc_app_event_scheduler.schedule(AppEvent::Exit);
}) {
	tracing::warn!("Ctrl-C handler not installed (already set or unsupported): {e}");
}
Defensive patterns

Strategy: try-catch

Try / catch

match ctrlc::set_handler(move || {
	tracing::info!("Termination signal received, exiting...");
	ctrlc_app_event_scheduler.schedule(AppEvent::Exit);
}) {
	Ok(()) => {}
	Err(e) if e.kind() == ctrlc::ErrorKind::MultipleHandlers => {
		tracing::warn!("Ctrl-C handler already set elsewhere; skipping");
	}
	Err(e) => tracing::warn!("Ctrl-C handler not installed: {e}"),
}

Prevention

When it happens

Trigger: App::new being constructed twice in one process (integration tests, embedders) so set_handler is called again; another crate or dependency in the same binary also registering a ctrlc handler; rare OS-level sigaction/SetConsoleCtrlHandler failures.

Common situations: Test harnesses that instantiate the full desktop App multiple times in a single test process; a dependency (CLI framework, tracing setup, CEF wrapper) that already owns the ctrl-c handler; embedding the desktop app inside another host process.

Related errors


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/e51663225e370a8f. Report an issue: GitHub.