LiveSplit / livesplit-core

livesplit-core is a library that provides a lot of functionality for creating a speedrun timer.

Home Page:https://livesplit.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[livesplit-hotkey] There seems to be a concurrency issue.

mrzhjyw opened this issue · comments

commented

thread '' panicked at livesplit-core\crates\livesplit-hotkey\src\windows\mod.rs:271:31:
already borrowed: BorrowMutError
stack backtrace:
0: 0x7ff7d84bc3ca - std::sys_common::backtrace::print::impl$0::fmt
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\std\src\sys_common\backtrace.rs:44
1: 0x7ff7d84d438b - core::fmt::rt::Argument::fmt
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\core\src\fmt\rt.rs:138
2: 0x7ff7d84d438b - core::fmt::write
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\core\src\fmt\mod.rs:1094
3: 0x7ff7d84b87d1 - std::io::Write::write_fmtstd::sys::windows::stdio::Stderr
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\std\src\io\mod.rs:1714
4: 0x7ff7d84bc14a - std::sys_common::backtrace::print
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\std\src\sys_common\backtrace.rs:47
5: 0x7ff7d84bc14a - std::sys_common::backtrace::print
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\std\src\sys_common\backtrace.rs:34
6: 0x7ff7d84be8ea - std::panicking::default_hook::closure$1
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\std\src\panicking.rs:270
7: 0x7ff7d84be558 - std::panicking::default_hook
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\std\src\panicking.rs:290
8: 0x7ff7d84bef9e - std::panicking::rust_panic_with_hook
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\std\src\panicking.rs:707
9: 0x7ff7d84bee8d - std::panicking::begin_panic_handler::closure$0
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\std\src\panicking.rs:599
10: 0x7ff7d84bcdb9 - std::sys_common::backtrace::rust_end_short_backtracestd::panicking::begin_panic_handler::closure_env$0,never$
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\std\src\sys_common\backtrace.rs:170
11: 0x7ff7d84beb90 - std::panicking::begin_panic_handler
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\std\src\panicking.rs:595
12: 0x7ff7d84dd4e5 - core::panicking::panic_fmt
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\core\src\panicking.rs:67
13: 0x7ff7d84dda04 - core::result::unwrap_failed
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\core\src\result.rs:1652
14: 0x7ff7d8097fed - enum2$<core::result::Result<core::cell::RefMut<enum2$<core::option::Option<livesplit_hotkey::windows::State> > >,core::cell::BorrowMutError> >::expect<core::cell::RefMut<enum2$<core::option::Option<livesplit_hotkey::windows::State> > >,core::cell::BorrowM
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33\library\core\src\result.rs:1034
15: 0x7ff7d809d7b4 - core::cell::RefCell<enum2$<core::option::Option<livesplit_hotkey::windows::State> > >::borrow_mut<enum2$<core::option::Option<livesplit_hotkey::windows::State> > >
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33\library\core\src\cell.rs:1017
16: 0x7ff7d80a9cd0 - livesplit_hotkey::windows::callback_proc::closure$0
at C:\Users\sosee\nuxt-tauri\src-tauri\lib\livesplit-core\crates\livesplit-hotkey\src\windows\mod.rs:271
17: 0x7ff7d809aa22 - std::thread::local::LocalKey<core::cell::RefCell<enum2$<core::option::Option<livesplit_hotkey::windows::State> > > >::try_with<core::cell::RefCell<enum2$<core::option::Option<livesplit_hotkey::windows::State> > >,livesplit_hotkey::windows::callback_proc::
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33\library\std\src\thread\local.rs:270
18: 0x7ff7d809a31e - std::thread::local::LocalKey<core::cell::RefCell<enum2$<core::option::Option<livesplit_hotkey::windows::State> > > >::with<core::cell::RefCell<enum2$<core::option::Option<livesplit_hotkey::windows::State> > >,livesplit_hotkey::windows::callback_proc::clos
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33\library\std\src\thread\local.rs:246
19: 0x7ff7d808e5c1 - livesplit_hotkey::windows::callback_proc
at C:\Users\sosee\nuxt-tauri\src-tauri\lib\livesplit-core\crates\livesplit-hotkey\src\windows\mod.rs:270
20: 0x7fff55c078a4 - GetAsyncKeyState
21: 0x7fff55c6d1a3 - CreateSystemThreads
22: 0x7fff562b33b4 - KiUserCallbackDispatcher
23: 0x7fff537c1834 - NtUserCallNextHookEx
24: 0x7fff55c126bb - WindowFromPoint
25: 0x7ff7d80a9d58 - livesplit_hotkey::windows::callback_proc::closure$0
at C:\Users\sosee\nuxt-tauri\src-tauri\lib\livesplit-core\crates\livesplit-hotkey\src\windows\mod.rs:380
26: 0x7ff7d809aa22 - std::thread::local::LocalKey<core::cell::RefCell<enum2$<core::option::Option<livesplit_hotkey::windows::State> > > >::try_with<core::cell::RefCell<enum2$<core::option::Option<livesplit_hotkey::windows::State> > >,livesplit_hotkey::windows::callback_proc::
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33\library\std\src\thread\local.rs:270
27: 0x7ff7d809a31e - std::thread::local::LocalKey<core::cell::RefCell<enum2$<core::option::Option<livesplit_hotkey::windows::State> > > >::with<core::cell::RefCell<enum2$<core::option::Option<livesplit_hotkey::windows::State> > >,livesplit_hotkey::windows::callback_proc::clos
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33\library\std\src\thread\local.rs:246
28: 0x7ff7d808e5c1 - livesplit_hotkey::windows::callback_proc
at C:\Users\sosee\nuxt-tauri\src-tauri\lib\livesplit-core\crates\livesplit-hotkey\src\windows\mod.rs:270
29: 0x7fff55c078a4 - GetAsyncKeyState
30: 0x7fff55c6d1a3 - CreateSystemThreads
31: 0x7fff562b33b4 - KiUserCallbackDispatcher
32: 0x7fff537c1534 - NtUserGetMessage
33: 0x7fff55c0535a - GetMessageW
34: 0x7ff7d80aa316 - livesplit_hotkey::windows::impl$3::new::closure$0
at C:\Users\sosee\nuxt-tauri\src-tauri\lib\livesplit-core\crates\livesplit-hotkey\src\windows\mod.rs:436
35: 0x7ff7d80b5549 - std::sys_common::backtrace::rust_begin_short_backtrace<livesplit_hotkey::windows::impl$3::new::closure_env$0,enum2$<core::result::Result<tuple$<>,livesplit_hotkey::windows::Error> > >
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33\library\std\src\sys_common\backtrace.rs:154
36: 0x7ff7d809d201 - std::thread::impl$0::spawn_unchecked
::closure$1::closure$0<livesplit_hotkey::windows::impl$3::new::closure_env$0,enum2$<core::result::Result<tuple$<>,livesplit_hotkey::windows::Error> > >
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33\library\std\src\thread\mod.rs:529
37: 0x7ff7d80b60a1 - core::panic::unwind_safe::impl$23::call_once<enum2$<core::result::Result<tuple$<>,livesplit_hotkey::windows::Error> >,std::thread::impl$0::spawn_unchecked
::closure$1::closure_env$0<livesplit_hotkey::windows::impl$3::new::closure_env$0,enum2$<core::result
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33\library\core\src\panic\unwind_safe.rs:271
38: 0x7ff7d809f8d0 - std::panicking::try::do_call<core::panic::unwind_safe::AssertUnwindSafe<std::thread::impl$0::spawn_unchecked
::closure$1::closure_env$0<livesplit_hotkey::windows::impl$3::new::closure_env$0,enum2$<core::result::Result<tuple$<>,livesplit_hotkey::windows::E
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33\library\std\src\panicking.rs:502
39: 0x7ff7d809ff43 - std::panicking::try::do_catch<core::panic::unwind_safe::AssertUnwindSafe<std::thread::impl$7::drop::closure_env$0<enum2$<core::result::Result<tuple$<>,livesplit_hotkey::windows::Error> > > >,tuple$<> >
40: 0x7ff7d809f4ce - std::panicking::try<enum2$<core::result::Result<tuple$<>,livesplit_hotkey::windows::Error> >,core::panic::unwind_safe::AssertUnwindSafe<std::thread::impl$0::spawn_unchecked
::closure$1::closure_env$0<livesplit_hotkey::windows::impl$3::new::closure_env$0,e
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33\library\std\src\panicking.rs:466
41: 0x7ff7d809d025 - std::panic::catch_unwind
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33\library\std\src\panic.rs:142
42: 0x7ff7d809d025 - std::thread::impl$0::spawn_unchecked
::closure$1<livesplit_hotkey::windows::impl$3::new::closure_env$0,enum2$<core::result::Result<tuple$<>,livesplit_hotkey::windows::Error> > >
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33\library\std\src\thread\mod.rs:528
43: 0x7ff7d8091bee - core::ops::function::FnOnce::call_once<std::thread::impl$0::spawn_unchecked
::closure_env$1<livesplit_hotkey::windows::impl$3::new::closure_env$0,enum2$<core::result::Result<tuple$<>,livesplit_hotkey::windows::Error> > >,tuple$<> >
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33\library\core\src\ops\function.rs:250
44: 0x7ff7d84c320c - std::sys::windows::thread::impl$0::new::thread_start
at /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library\std\src\sys\windows\thread.rs:57
45: 0x7fff557d257d - BaseThreadInitThunk
46: 0x7fff5626aa58 - RtlUserThreadStart

commented

Remapped keys using PowerToys.

commented

This issue report doesn't give us enough to figure out what is going on.

First of all, you should not just paste the backtrace directly into your comment, but instead upload it as a file. Pasting it directly into your issue comes off as extremely spammy and is quite annoying because github actually replaced part of your backtrace with emoji.

The other thing is we don't have any steps we can take to even reproduce this. If you can come up with a consistent way to trigger this crash and give us steps on how to do it, we can try and reproduce it on our end and fix it.

It looks like the hook callback gets called, then it calls CallNextHookEx which seems to call our callback again. I didn't think this was possible. An easy way to fix this is simply moving the call to CallNextHookEx outside the STATE.with(...), which ensures that we don't doubly lock the RefCell. The problem however is that the panic of the RefCell as seen here is meant to highlight a potential problem of unexpected recursion. So while it might fix the problem, we don't really know why our hook is even seemingly installed twice here. Can you show more of your code, so we can reproduce it?

commented

I'm sorry for not asking the question correctly in the first place. I apologize for the confusion. The issue arises when, because I use an HHKB keyboard, I use PowerToys to remap the Win key to the Ctrl key. Then, when I bind Ctrl+O in Rust, an exception occurs whenever I press the Win key (which has been remapped to Ctrl).

So you do nothing special in your code that uses livesplit-hotkey? Can we maybe see it anyway, just to make sure? I have a few changes you could try then.

commented

No other operations were performed. In fact, binding the mapped keys set up with Karabiner-Elements as hotkeys under MacOS can also result in anomalies.

Alright, try this:

unsafe extern "system" fn callback_proc(code: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
    let hook = STATE.with(|state| {
        let mut state = state.borrow_mut();
        let state = state.as_mut().expect("State should be initialized by now");

        if code >= 0 {
            let hook_struct = &*(lparam as *const KBDLLHOOKSTRUCT);
            let event = wparam as u32;
            if event == WM_KEYDOWN || event == WM_SYSKEYDOWN {
                // Windows in addition to the scan code has a notion of a
                // virtual key code. This however is already dependent on the
                // keyboard layout. So we should prefer the scan code over the
                // virtual key code. It's hard to come by what these scan codes
                // actually mean, but there's a document released by Microsoft
                // that contains most (not all sadly) mappings from USB HID to
                // the scan code (which matches the PS/2 scan code set 1 make
                // column).
                // http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf
                // Scan codes can come in an extended form `e0/e1 xx`, so you
                // need to check for the extended field in the flags, as the
                // scan code provided by itself is not extended. Also not every
                // key press somehow even has a scan code. It seems like these
                // might be caused by a special keyboard driver that directly
                // emits the virtual key codes for those keys rather than any
                // physical scan codes ever coming in. Windows has a way to
                // translate those back into scan codes though, so this is what
                // we do in that case.
                let scan_code = if hook_struct.scanCode != 0 {
                    hook_struct.scanCode + ((hook_struct.flags & LLKHF_EXTENDED) * 0xE000)
                } else {
                    MapVirtualKeyW(hook_struct.vkCode, MAPVK_VK_TO_VSC_EX)
                };

                if let Some(key_code) = parse_scan_code(scan_code) {
                    let (idx, bit) = key_idx(key_code);
                    if state.key_state[idx as usize] & bit == 0 {
                        state.key_state[idx as usize] |= bit;

                        state
                            .events
                            .send(Hotkey {
                                key_code,
                                modifiers: state.modifiers,
                            })
                            .expect("Callback Thread disconnected");

                        match key_code {
                            KeyCode::AltLeft | KeyCode::AltRight => {
                                state.modifiers.insert(Modifiers::ALT);
                            }
                            KeyCode::ControlLeft | KeyCode::ControlRight => {
                                state.modifiers.insert(Modifiers::CONTROL);
                            }
                            KeyCode::MetaLeft | KeyCode::MetaRight => {
                                state.modifiers.insert(Modifiers::META);
                            }
                            KeyCode::ShiftLeft | KeyCode::ShiftRight => {
                                state.modifiers.insert(Modifiers::SHIFT);
                            }
                            _ => {}
                        }
                    }
                }
            } else if event == WM_KEYUP || event == WM_SYSKEYUP {
                // Windows in addition to the scan code has a notion of a
                // virtual key code. This however is already dependent on the
                // keyboard layout. So we should prefer the scan code over the
                // virtual key code. It's hard to come by what these scan codes
                // actually mean, but there's a document released by Microsoft
                // that contains most (not all sadly) mappings from USB HID to
                // the scan code (which matches the PS/2 scan code set 1 make
                // column).
                // http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf
                // Scan codes can come in an extended form `e0/e1 xx`, so you
                // need to check for the extended field in the flags, as the
                // scan code provided by itself is not extended. Also not every
                // key press somehow even has a scan code. It seems like these
                // might be caused by a special keyboard driver that directly
                // emits the virtual key codes for those keys rather than any
                // physical scan codes ever coming in. Windows has a way to
                // translate those back into scan codes though, so this is what
                // we do in that case.
                let scan_code = if hook_struct.scanCode != 0 {
                    hook_struct.scanCode + ((hook_struct.flags & LLKHF_EXTENDED) * 0xE000)
                } else {
                    MapVirtualKeyW(hook_struct.vkCode, MAPVK_VK_TO_VSC_EX)
                };

                if let Some(key_code) = parse_scan_code(scan_code) {
                    let (idx, bit) = key_idx(key_code);
                    state.key_state[idx as usize] &= !bit;

                    match key_code {
                        KeyCode::AltLeft | KeyCode::AltRight => {
                            state.modifiers.remove(Modifiers::ALT);
                        }
                        KeyCode::ControlLeft | KeyCode::ControlRight => {
                            state.modifiers.remove(Modifiers::CONTROL);
                        }
                        KeyCode::MetaLeft | KeyCode::MetaRight => {
                            state.modifiers.remove(Modifiers::META);
                        }
                        KeyCode::ShiftLeft | KeyCode::ShiftRight => {
                            state.modifiers.remove(Modifiers::SHIFT);
                        }
                        _ => {}
                    }
                }
            }
        }

        state.hook
    });

    CallNextHookEx(hook, code, wparam, lparam)
}

This either resolves the issue or results in a stack overflow.

commented

Although the mapped keys cannot trigger the hotkey (the original keys work fine), it no longer causes the program to experience anomalies. Thank you.

Can you put like a println! / dbg! at the top of this function to see if the mapped keys trigger the function at all? If they do, maybe there's something we can fix about that.

commented

original keys: image

mapped keys: image

unsafe extern "system" fn callback_proc(code: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
    println!("{:?} {:?} {:?}", code, wparam, lparam);
    let hook = STATE.with(|state| {
     // ...
commented

The mapped keys seem to trigger twice; it appears to be a forwarding issue.

0 257 211742093928 -> 0 257 211742093352