Subject: bug#79333: 31.0.50; Processes (still) aren't actually locked to threads

From: Spencer Baugh <[email-masked]>
Date: Fri, 29 Aug 2025 09:29:37 -0400

Eli Zaretskii <[email-masked]> writes:
>> >> (Once again, I think we should take the opportunity here to delete the
>> >> code for locking processes to threads, since IMO it is not useful, and
>> >> it is still broken)
>> >
>> > I did explain why it is useful, and you haven't brought up any
>> > arguments to the contrary.  And if "broken" means that the sentinel
>> > can run in the context of a random thread, then how come you are
>> > asking to leave it in this broken state?
>> 
>> I was working on an example to demonstrate how process locking can cause
>> problems for unrelated Lisp code when I found this bug.
>> 
>> Here's a finished example of the problem.
>> 
>> Suppose I have the following Lisp program which doesn't use threads:
>> 
>> (run-at-time .1 nil #'async-shell-command "sleep 1 && echo foobar && sleep inf")
>> (with-current-buffer (get-buffer-create shell-command-buffer-name-async)
>>   (while (string-empty-p (buffer-string))
>>     (message "waiting for some process output")
>>     (sit-for 1))
>>   (message "buffer contents: %s" (buffer-string)))
>> 
>> This is intended to represent some arbitrary package which calls
>> make-process in a timer or a hook.  The command "sleep 1 && echo foobar
>> && sleep inf" is chosen to represent some interactive executable like a
>> shell or REPL.
>> 
>> This code runs just fine, with the output appearing in the buffer as
>> expected.
>> 
>> Now suppose I have some other unrelated package which runs the following
>> code using threads:
>> 
>>   (make-thread
>>    (lambda ()
>>      (accept-process-output nil 1)
>>      (thread-join (make-thread (lambda () (while t (message "doing work") (sit-for 10)))))))
>> 
>> This is intended to represent thread 1 waiting for some other thread 2
>> to complete some long-running task.  Crucially, thread 1 is blocked in
>> thread-join, which doesn't run wait_reading_process_output, so thread 1
>> won't read output from any locked processes.
>> 
>> Running the second piece of code hangs the first piece of code, even
>> though neither of them are buggy, and they aren't visibly interacting,
>> and they can be written by totally different authors.
>> 
>> Specifically: The timer can be run in the thread created by the second
>> piece of code, and then the process will be created locked to that
>> thread.  Then the process's output will never be read by thread 1.  So
>> the first piece of code will hang.
>
> I don't understand why one thread starts a process, then another
> thread waits for its output, and the program which arranges for that
> doesn't unlock the process so the other thread could do its job.

Yes, that's the bug.

It is not intentional that the process is started in a thread.  That is
what causes the bug.

> This is what set-process-thread is
> for, and in this (IMO rather unusual) arrangement, calling it with a
> nil THREAD argument is exactly what should be done.

The point is that these are two independent pieces of code, written by
different authors.

If they just happen to interleave in this way, then the process is
*accidentally, unintentionally* started in a thread.

How would the author of snippet 1 know to call set-process-thread in
this case?