Currently there’s a lot of fuzz about concurrency in Emacs again. The topic rekindled on emacs-devel and promptly made it to Reddit as well. The lack of proper concurrency is a long-standing issue in Emacs, vexing users and developers alike. Users are frustrated to see Emacs freezing now and then, and developers are frustrated because so far they are unable to fix this source of constant frustration without major pain.
The Concurrency Branch
There’s unanimous agreement that Emacs needs better support for concurrency, and indeed over the last few years Tom Tromey (of ELPA fame) and others worked on concurrency for Emacs Lisp—dubbed as “the concurrency branch” in the community. The branch has come far, to the point where there’s prospect of merging it into master.
I find that impressive, and don’t dare to imagine what amount of pain the
developers most have gone through. Emacs is riddled with a tremendous amount of
mutable global state—the mortal enemy of concurrency. It’s all over the
place, in the editor as well as in the language: The mere existence of global
dynamic variables makes proper parallelism impossible to implement, not to
mention all the implicit state in variables like
It’s no surprise then that the concurrency branch doesn’t even try to implement true parallelism. Instead, it provides a model of concurrency without parallelism that’s similar to that of other dynamic languages such as Python or Ruby. As usual, Emacs’ great documentation is a good resource to understand how this model is implemented in Emacs Lisp.
The branch adds threads to Emacs Lisp. Like Python a global interpreter lock
ensures that only a single thread runs at any given time to avoid any race
conditions and inconsistencies in the interpreter state. Unlike Python which
sort of preempts threads by automatically releasing the interpreter lock and
switching threads after every 100 byte code instructions threads in Emacs Lisp
will be entirely cooperative. Emacs only releases the lock and switches threads
when blocking for IO or when
thread-yield is explicitly called.
This simple form of concurrency is the result of a couple of year’s work: The beginnings of the branch date back to 2012, maybe even earlier. It’s a step forward for Emacs Lisp in terms of clarifying the semantics of the language, but in the cold light of the day does it add much?
Heaven Or Hell?
The branch is generally met with uncritical enthusiasm. Talking to other Emacs Lisp developers and users I have the impression that many people naively believe that the concurrency branch is the immediate cure to all the blocking operations that interrupt their Emacs workflow. I don’t think that’s the case, for so many reasons.
Even if the branch offered a good solution it’ll take years, many years, before it hits downstream packages. There’s no story about backwards compatibility in the branch: A package that uses threads won’t run on older Emacs versions no matter what. This alone stops popular packages from using threads for a couple of years; they need to wait until Emacs versions with concurrency are sufficiently widely spread before they can drop compatibility with older versions and start to use threads. And, well, that’s just the start: Embracing threads for blocking operations involves a major refactoring.
Remember that threads will be cooperative. Shoving a blocking operation to
another thread won’t change anything: It’ll block Emacs just as it used to, just
on a different thread. To truly make it non-blocking it needs to be carefully
thread-yield at strategic spots and audited for potential race
conditions— the assumption of a single-thread environment that all Emacs Lisp
code implicitly carries with it suddenly doesn’t hold any more.
Communicating between threads will be challenging, too: Emacs will offer mutexes for locking and conditions for events between threads. Code that makes use of threads needs to be rewritten around these primitives for communication.
That’s not a simple straight-forward change. It fundamentally changes the look of code, and the mental model behind it, moving it from a local sequence of instructions to a distributed scattered mess of small disconnected pieces. As a result code is much harder to understand, much harder to debug, and much less welcoming to new developers.
We already see that in Emacs, with asynchronous processes which use callbacks to communicate state back to Emacs. Flycheck makes heavy use of asynchronous processes for responsive syntax checking. Conceptually that’s simple and straightforward, but the implementation of syntax checking is scattered over many callbacks. Thus, what’s supposed to be a simple operation—and is in languages with modern concurrency concepts—is the single hardest piece of code in Flycheck.
The Past, The Present, The Future?
The concurrency branch is hailed as Emacs Lisp’s future, but to me it looks like a terrible blast from the past. It’s the nineties-style Java-way of concurrency: Threads, yield, mutexes, locks. And a long tearful story of races, deadlocks, pain and suffering. It’s been a single big failure in every major programming language, Java first and foremost, how come we believe that it would be a success in Emacs?
I don’t doubt that some packages will profit from threads but often they’ll be too hard to use, and developers will just continue with synchronous operations for the sake of simplicity. That’s simply what we’ve seen for years in GUI applications that were limited to the thread model of concurrency. It’s only in recent years with better concurrency models that GUI applications became highly concurrent.
Threads are broken because they leak the details of execution into concurrency code. What Emacs needs is a decent simple concurrency model that’s independent of execution and abstracts the implementation of concurrency. A ubiquitous model that’s applicable not only to concurrency execution but to all sorts of concurrency like asynchronous processes or asynchronous IO. A model that can not only move expensive computations into background but also simplify asynchronous processes and networking without mucking about with callbacks.
We already know such a model: Futures. The idea is as old as threads but became successful in many programming languages in recent years. It’s futures that brought concurrent programming to the masses.
To have a future Emacs Lisp needs a Future.