Skip to content

refactor: fix macOS SIGABRT crash in lsp-download-install by replacing url-copy-file thread with make-process (curl)#5022

Open
roryhow wants to merge 2 commits intoemacs-lsp:masterfrom
roryhow:refactor/download-install-make-process
Open

refactor: fix macOS SIGABRT crash in lsp-download-install by replacing url-copy-file thread with make-process (curl)#5022
roryhow wants to merge 2 commits intoemacs-lsp:masterfrom
roryhow:refactor/download-install-make-process

Conversation

@roryhow
Copy link
Copy Markdown

@roryhow roryhow commented Mar 23, 2026

Problem

On macOS (Emacs 30+, ARM), installing a language server via lsp-install-server crashes Emacs with EXC_CRASH (SIGABRT). The crash is a thread-safety violation in the NS/Cocoa backend.

lsp-download-install wraps the download in make-thread. Inside that thread, url-copy-file calls url-retrieve-synchronously, which pumps the macOS event loop via ns_select_1. That function contains an assertion that it only runs on the main thread — violated here, causing emacs_abort.

The crash stack:

lsp-download-install
  └─ make-thread
       └─ url-copy-file
            └─ url-retrieve-synchronously
                 └─ accept-process-output
                      └─ wait_reading_process_output
                           └─ ns_select_1        [src/nsterm.m]
                                └─ unblock_input  ← eassert (main_thread_p) → ABORT

This is also the root cause of the softer error seen on Linux in #3006 ("Attempt to accept output from process ... locked to thread"), where the NS assertion doesn't fire but the thread-safety violation still produces a Lisp error.

Fix

Replace url-copy-file (and the make-thread wrapping it) with make-process running curl, plus a :sentinel callback. The sentinel fires on the main thread via the normal Emacs event loop — no thread-safety issues on any platform.

This is the same pattern used by established Emacs HTTP libraries:

  • plz.elmake-process + :sentinel
  • request.elstart-process + set-process-sentinel

Decompression (:gzip, :zip, :targz, :tarxz) remains in a make-thread — those steps use call-process internally and do not touch the NS event loop, so they are thread-safe.

Changes

  • lsp-mode.el: add private lsp--download-file helper; refactor lsp-download-install to use it
  • test/lsp-mode-test.el: add 5 ERT tests covering missing curl, correct invocation, success, failure, and all decompress variants
  • CHANGELOG.org: entry in Unreleased

Notes

curl is a hard requirement for the download path. It ships with macOS and is present on virtually all Linux systems. A clear error is raised if it is not found. Opinions or alternatives to this approach are welcome.

…download-install

url-copy-file calls url-retrieve-synchronously, which on macOS drives the
NS/Cocoa event loop via ns_select_1. That function asserts it is running on
the main thread and aborts (EXC_CRASH / SIGABRT) when called from a
background Emacs Lisp thread spawned by make-thread.

Replace the make-thread + url-copy-file approach with make-process (curl)
and a :sentinel callback. The sentinel fires on the main thread via the
normal event loop, which is safe on all platforms. This matches the
pattern used by plz.el, request.el, and package.el. Decompression remains
in a make-thread as it is CPU-bound and does not touch the event loop.

Add ERT tests covering: missing curl, correct process invocation, successful
download, curl failure, and all decompress variants.
Copy link
Copy Markdown
Member

@jcs090218 jcs090218 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't done the manual test yet, but the code looks good to me!

Can someone give it a try on macOS? 🤔

@jcs090218 jcs090218 added the bug label Apr 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants