rakslice / macemu

Basilisk II and SheepShaver Macintosh emulators
0 stars 0 forks source link

POSIX timing: deadlock between `RmvTime` and timer thread #45

Closed rakslice closed 3 years ago

rakslice commented 3 years ago

While testing some other changes in SheepShaver on Linux, I ran into this situation:

SheepShaver is hung not responding to input or redrawing.

The main thread is in

(gdb) bt
#0  __lll_lock_wait (futex=futex@entry=0xaaaab1b477e0 <wakeup_time_lock>, private=0) at lowlevellock.c:52
#1  0x0000ffff823a4b98 in __GI___pthread_mutex_lock (mutex=mutex@entry=0xaaaab1b477e0 <wakeup_time_lock>) at pthread_mutex_lock.c:80
#2  0x0000aaaab1a93390 in RmvTime(unsigned int) (tm=271287612) at ../timer.cpp:379
#3  0x0000aaaab1a90674 in EmulOp(M68kRegisters*, uint32, int) (r=r@entry=0xffffc2531490, pc=<optimized out>, selector=<optimized out>) at ../emul_op.cpp:220
#4  0x0000aaaab1ad9e38 in sheepshaver_cpu::execute_emul_op(unsigned int) (this=0xaaaae9fef360, emul_op=<optimized out>) at ../kpx_cpu/src/cpu/ppc/ppc-cpu.hpp:110
#5  0x0000000020a12068 in  ()
#6  0x0000aaaab1accc30 in basic_dyngen::execute(unsigned char*) (this=0xaaaaea0efbb0, this=0xaaaaea0efbb0, entry_point=<optimized out>)
    at ../kpx_cpu/src/cpu/jit/basic-dyngen.hpp:293
#7  powerpc_cpu::execute(unsigned int) (this=0xaaaae9fef360, entry=540092988) at ../kpx_cpu/src/cpu/ppc/ppc-cpu.cpp:594
#8  0x0000aaaab1accc30 in basic_dyngen::execute(unsigned char*) (this=0xaaaab1be8c30, this=0xaaaab1be8c30, entry_point=<optimized out>)
    at ../kpx_cpu/src/cpu/jit/basic-dyngen.hpp:293
#9  powerpc_cpu::execute(unsigned int) (this=0xaaaab1ae83e0, entry=3929737344) at ../kpx_cpu/src/cpu/ppc/ppc-cpu.cpp:594
#10 0x0000aaaaea0efbb0 in  ()
#3  0x0000aaaab1a90674 in EmulOp (r=r@entry=0xffffc2531490, pc=<optimized out>, selector=<optimized out>) at ../emul_op.cpp:220
220                             r->d[0] = RmvTime(r->a[0]);
(gdb) list
215
216                     case OP_INSTIME:                        // InsTime() replacement
217                             r->d[0] = InsTime(r->a[0], r->d[1]);
218                             break;
219                     case OP_RMVTIME:                        // RmvTime() replacement
220                             r->d[0] = RmvTime(r->a[0]);
221                             break;
222                     case OP_PRIMETIME:                      // PrimeTime() replacement
223                             r->d[0] = PrimeTime(r->a[0], r->d[0]);
224                             break;
#2  0x0000aaaab1a93390 in RmvTime (tm=271287612) at ../timer.cpp:379
379             pthread_mutex_lock(&wakeup_time_lock);
(gdb) list
374             semaphore_wait(wakeup_time_sem);
375             thread_suspend(timer_thread);
376     #endif
377     #if PRECISE_TIMING_POSIX
378             timer_thread_suspend();
379             pthread_mutex_lock(&wakeup_time_lock);
380     #endif
381             if (ReadMacInt16(tm + qType) & 0x8000) {
382
383                     // Yes, make task inactive and remove it from the Time Manager queue

i.e. the main thread has run RmvTime which has just suspended the timer thread and is now waiting on wakeup_time_lock.

Meanwhile in the timer thread

(gdb) thread 16
[Switching to thread 16 (Thread 0xffff227fbe60 (LWP 15700))]
#0  0x0000ffff810f944c in __GI___sigsuspend (set=0xaaaab1b47848 <suspend_handler_mask>) at ../sysdeps/unix/sysv/linux/sigsuspend.c:26
26      ../sysdeps/unix/sysv/linux/sigsuspend.c: No such file or directory.
(gdb) bt
#0  0x0000ffff810f944c in __GI___sigsuspend (set=0xaaaab1b47848 <suspend_handler_mask>) at ../sysdeps/unix/sysv/linux/sigsuspend.c:26
#1  0x0000ffff824105b8 in <signal handler called> ()
#2  0x0000aaaab1a92f78 in timer_func(void*) (arg=<optimized out>) at ../timer.cpp:593
#3  0x0000ffff823a24fc in start_thread (arg=0xffffc2531a9f) at pthread_create.c:477
#4  0x0000ffff811960cc in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/clone.S:78
(gdb) up
#1  <signal handler called>
(gdb) up
#2  0x0000aaaab1a92f78 in timer_func (arg=<optimized out>) at ../timer.cpp:593
593                             wakeup_time = wakeup_time_max;
(gdb) list
588                     timer_current_time(system_time);
589                     if (timer_cmp_time(wakeup_time, system_time) < 0) {
590
591                             // Timer expired, trigger interrupt
592                             pthread_mutex_lock(&wakeup_time_lock);
593                             wakeup_time = wakeup_time_max;
594                             pthread_mutex_unlock(&wakeup_time_lock);
595                             SetInterruptFlag(INTFLAG_TIMER);
596                             TriggerInterrupt();
597                     }

The timer thread is in the middle of the critical section on wakeup_time_lock but it has received the suspend from the main thread's timer_thread_suspend() and is now suspended so it is never going to release the lock.

A deadlock.

rakslice commented 3 years ago

Fixed upstream.

The InsTime/RmvTime stress test program I created to reproduce the deadlock is at https://gist.github.com/rakslice/f2329427874e38edc649f4b24bd7328c