Stop admitting output from an interrupted process
Hi, I'm having a problem with elixir formatting.
So most Erlang-based programs, like mix format - the Elixir formatter, only prints a control message when the user sends an interrupt signal to the process, where the user is expected to press ^C again to really quit the program. The control message looks like the following:
$ iex
Erlang/OTP 24 [erts-12.1] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit]
Interactive Elixir (1.12.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo
(l)oaded (v)ersion (k)ill (D)b-tables (d)istribution
^C%
$
(In above demo, I pressed ^C for the first time after the prompt iex(1)> (not echoed), and the second time after the control message is printed)
I understand this style of interrupting output is atypical among unix programs. However, it doesn't play well with apheleia. What occurs to my workflow is that when I quickly modify+save multiple times in a succession, then at some point, it's likely apheleia would replace the whole buffer with following text and saved it in the file.
BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo
(l)oaded (v)ersion (k)ill (D)b-tables (d)istribution
It is annoying because when that happens I have to undo the override and save again more slowly. Fortunately, I found a way to mitigate the situation by modifying the apheleia--make-process function:
@@ -305,7 +305,7 @@ purposes. FORMATTER is nil if the command being run does not
correspond to a formatter."
(when (process-live-p apheleia--current-process)
(message "Interrupting %s" apheleia--current-process)
- (interrupt-process apheleia--current-process)
+ (kill-process apheleia--current-process)
(accept-process-output apheleia--current-process 0.1 nil 'just-this-one)
(when (process-live-p apheleia--current-process)
(kill-process apheleia--current-process)))
So I suppose the problem can be solved in one of two ways:
- stop accepting the output from an interrupted process
- simply kill the process instead of interrupting it in the first place
I'm modifying with the second method because it simply worked for me. However, I guess the first method may be more ideal without disrupting the typical flow for other nicely behaving formatters. However, I'm fairly new to emacs-lisp programming so I don't know how it should be done.
Could you provide me some help on the problem? Thanks.
What is the exit status of the formatter when it is interrupted in this manner? I would assume that when the program is interrupted, it should exit with nonzero status, and any of its output should be ignored by Apheleia.
Is it possible that Apheleia is failing to deregister the process filter from the old process once it has been abandoned, and output from that old abandoned process still gets written to the same buffer that is used for the new process? I think that would explain the behavior you are seeing. Obviously, that would be a bug we should fix in Apheleia.
What is the exit status of the formatter when it is interrupted in this manner?
When mix format is interrupted once, it doesn't quit but rather printing the control message. If it's interrupted once again, the formatter exits with status 0.
Is it possible that Apheleia is failing to deregister the process filter from the old process once it has been abandoned, and output from that old abandoned process still gets written to the same buffer that is used for the new process?
Sorry I'm not familiar with process filtering in emacs, so I can't be certain that this is what happens. I'm glad to test the hypothesis if you can give me some testing steps to follow.
If it's interrupted once again, the formatter exits with status 0.
Ah, that's probably part of the problem. Normally if a program exits with status 0 it means all is well and execution completed successfully. So Apheleia is probably assuming that whatever was outputted is valid output.
However if Apheleia is interrupting the process, it should be ignoring the output even if the exit status is 0, so let me take a quick look and see if I can figure out what is going on.
Ok yeah, so I haven't put together a repro for the issue, so I haven't tested, but I believe https://github.com/radian-software/apheleia/pull/139 should fix this, give it a try. Basically, when Apheleia spawns a process, it sets a process sentinel as a callback that gets invoked with the exit status once the process exits. If the user invokes another formatting operation before the process is done, the existing one is aborted and the new one is started. However the existing process still exits (usually with nonzero exit status, due to the abort) and the callback is invoked with that exit status. So in the case that the exit status is (unusually) zero, the malformed output is interpreted as a valid formatting result. However, we know in the case of interrupting a formatting run that we don't actually want to use the result anyway. So before interrupting the process, we set an attribute on the process plist that tells the callback to ignore the results after termination.
Hi @raxod502, thanks for the fix. I've been using the fixed version for more than a week. So far the same problem still appears though probably less frequently. I can remember seeing it two to three times during my intensive use these days. So I suspect there may still be some edge case scenario or race condition.
Hmm, ok, will reopen to continue tracking that bit then.