{"record":{"id":"64ae6c13ff7abcee","repo":"vercel/next.js","slug":"failed-to-set-ctrl-c-handler","errorCode":null,"errorMessage":"failed to set ctrl_c handler","messagePattern":"failed to set ctrl_c handler","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"turbopack/crates/turbopack-trace-utils/src/exit.rs","lineNumber":69,"sourceCode":"    /// Waits for `SIGINT` using [`tokio::signal::ctrl_c`], and exits the process with exit code `0`\n    /// after running any futures scheduled with [`ExitHandler::on_exit`].\n    ///\n    /// As this uses global process signals, this must only be called once, and will panic if called\n    /// multiple times. Use this when you own the process (e.g. `turbopack-cli`).\n    ///\n    /// If you don't own the process (e.g. you're called as a library, such as in `next-swc`), use\n    /// [`ExitHandler::new_receiver`] instead.\n    ///\n    /// This may listen for other signals, like `SIGTERM` or `SIGPIPE` in the future.\n    pub fn listen() -> &'static Arc<ExitHandler> {\n        let (handler, receiver) = Self::new_receiver();\n        if GLOBAL_EXIT_HANDLER.set(handler).is_err() {\n            panic!(\"ExitHandler::listen must only be called once\");\n        }\n        tokio::spawn(async move {\n            tokio::signal::ctrl_c()\n                .await\n                .expect(\"failed to set ctrl_c handler\");\n            receiver.run_exit_handler().await;\n            std::process::exit(0);\n        });\n        GLOBAL_EXIT_HANDLER.get().expect(\"value is set\")\n    }\n\n    /// Creates an [`ExitHandler`] that can be manually controlled with an [`ExitReceiver`].\n    ///\n    /// This does not actually exit the process or listen for any signals. If you'd like that\n    /// behavior, use [`ExitHandler::listen`].\n    ///\n    /// Because this API has no global side-effects and can be called many times within the same\n    /// process, it is possible to use it to provide a mock [`ExitHandler`] inside unit tests.\n    pub fn new_receiver() -> (Arc<ExitHandler>, ExitReceiver) {\n        let (tx, rx) = mpsc::unbounded_channel();\n        (Arc::new(ExitHandler { tx }), ExitReceiver { rx })\n    }\n","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/vercel/next.js/blob/0ae8c72462952df163f1b1e0726641bc5b40dc93/turbopack/crates/turbopack-trace-utils/src/exit.rs#L51-L87","documentation":"ExitHandler::listen() spawns an async task that awaits tokio::signal::ctrl_c() to install a SIGINT handler for graceful shutdown; it calls .expect(\"failed to set ctrl_c handler\") on the result. If installing the handler fails, the task panics. The most common cause is calling listen() from a non-main thread (Unix signal delivery requires the main thread) or when another signal handler is already installed.","triggerScenarios":"Calling ExitHandler::listen() from a worker thread or a tokio runtime that is not driving the main thread; invoking it twice (it also panics on the second GLOBAL_EXIT_HANDLER set); environments where SIGINT installation is blocked.","commonSituations":"Embedding the Turbopack/trace runtime inside a host process that doesn't own the main thread; a library context (next-swc) that should use ExitHandler::new_receiver() instead; conflicting signal handlers from another crate.","solutions":["Ensure ExitHandler::listen() is called only once and from the main thread / a runtime driving the main thread.","If you don't own the process, use ExitHandler::new_receiver() instead of listen() — it avoids global signal installation.","Remove any conflicting custom SIGINT handler installed by another part of the host process."],"exampleFix":"// before: called from a library / worker thread\nlet handler = ExitHandler::listen();\n\n// after: use the non-global receiver in library contexts\nlet (handler, _receiver) = ExitHandler::new_receiver();","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Call ExitHandler::listen() exactly once and only when you own the process main thread.","In library/embedded contexts, use ExitHandler::new_receiver() instead to avoid global signal installation.","Ensure no other crate has installed a conflicting SIGINT handler."],"tags":["signals","ctrl-c","exit-handler","panic","tokio"],"analyzedSha":"0ae8c72462952df163f1b1e0726641bc5b40dc93","analyzedAt":"2026-08-06T19:44:29.143Z","schemaVersion":2},"datasetVersion":"2026-08-07T02:17:10.218Z"}