Subject: Re: bug#79201: 30.1.90; set-process-thread can permanently break fd_callback_info slots

From: Spencer Baugh <[email-masked]>
Date: Wed, 13 Aug 2025 12:46:39 -0400

Eli Zaretskii <[email-masked]> writes:

>> From: Spencer Baugh <[email-masked]>
>> Cc: [email-masked],  [email-masked],  [email-masked],
>>    [email-masked]
>> Date: Tue, 12 Aug 2025 18:16:12 -0400
>> 
>> 
>> Anyway, I found a reproducer for the other bug I mentioned, where
>> waiting_thread could also get contaminated and break fd_callback_info
>> slots.  It seems to be quite a bad bug: it will happen spontaneously
>> just from running multiple threads interacting with processes.
>> 
>> If you run the following simple Lisp code after applying the following
>> patch (which is just to make the breakage more obvious; without the
>> patch, Emacs would be left in a silently broken state), Emacs will abort
>> in a few seconds - though it probably varies based on your system.
>> 
>> This indicates that .waiting_thread is somehow being left at a non-NULL
>> value even after the thread has left wait_reading_process_output.
>> 
>> Any ideas?
>
> I'm not sure I understand your idea behind
> assert_fd_callback_info_unused.  wait_reading_process_output calls
> thread_select in a loop, so a given thread which called
> wait_reading_process_output could spend more than one loop iteration
> without leaving the function.

Yes.

> Each iteration, such a thread will call add_process_read_fd and other
> similar functions, to set up the descriptors it will wait on, and then
> call thread_select.

That is incorrect.  add_process_read_fd is not usually called from
wait_reading_process_output or thread_select.  It's only called from
make-process.  There is one branch where add_process_read_fd is called
in wait_reading_process_output, for async network connections, but
that's not being hit here, since we aren't using async network
connections.

> When it calls thread_select, it releases the global lock, so some
> other thread could grab the lock and start running.

Yes.

> Then this other thread could see the .waiting_thread member non-NULL,
> because the thread which marked the descriptor as being waited on by
> it is stuck in a pselect call, called from wait_reading_process_output
> via thread_select.

Yes.  For existing file descriptors, the waiting_thread member should
often be non-NULL, and this tells other threads not to wait on that file
descriptor.

> Could the above scenario be the reason for what you see?

No, because the non-nil waiting_thread is seen in make-process, for a
process and file descriptor that is newly created.

If you run the reproducer, the assertion is hit in make-process.  Here's
the backtrace.

#0  0x0000000000422fbb in terminate_due_to_signal (sig=sig@entry=6, backtrace_limit=backtrace_limit@entry=2147483647) at emacs.c:443
#1  0x000000000061f59e in assert_fd_callback_info_unused (fd=9) at process.c:476
#2  0x000000000061f5e7 in assert_fd_callback_info_unused (fd=fd@entry=9) at process.c:509
#3  0x000000000061f5e7 in add_process_read_fd (fd=fd@entry=9) at process.c:502
#4  0x0000000000625138 in create_process (current_dir=XIL(0x7fffc4005684), new_argv=0x7fffcbbfd2c0, process=XIL(0x7fffc0000de5)) at process.c:2238
#5  0x0000000000625138 in Fmake_process (nargs=<optimized out>, args=<optimized out>) at process.c:2058
#6  0x00000000005d094c in eval_sub (form=<optimized out>) at lisp.h:2231
#7  0x00000000005d1c91 in Flet (args=XIL(0x7ffff73accc3)) at eval.c:1164
#8  0x00000000005d0a60 in eval_sub (form=<optimized out>) at lisp.h:2231
#9  0x00000000005d1130 in Fprogn (body=<optimized out>) at eval.c:445
#10 0x00000000005d1130 in funcall_lambda (fun=XIL(0x8ead4d), nargs=0, arg_vector=0x7fffcbbfd740) at eval.c:3429
#11 0x00000000005cd6e3 in Ffuncall (nargs=nargs@entry=1, args=args@entry=0x7fffcbbfd738) at eval.c:3172
#12 0x000000000064a560 in invoke_thread_function () at thread.c:747
#13 0x00000000005cbfb2 in internal_condition_case (bfun=bfun@entry=0x64a530 <invoke_thread_function>, handlers=handlers@entry=XIL(0x30), hfun=hfun@entry=0x649d80 <record_thread_error>) at eval.c:1690
#14 0x000000000064a448 in run_thread (state=0x8eb4b8) at lisp.h:1164
#15 0x00007fffee6081ea in start_thread () at /lib64/libpthread.so.0
#16 0x00007fffeae39953 in clone () at /lib64/libc.so.6

Lisp Backtrace:
"make-process" (0xcbbfd490)
"let" (0xcbbfd608)
"my-break-thread" (0xcbbfd740)