Skip to content

Comments

Unsound synchronization on Linux#1173

Open
sftse wants to merge 2 commits intotauri-apps:devfrom
sftse:c-dev
Open

Unsound synchronization on Linux#1173
sftse wants to merge 2 commits intotauri-apps:devfrom
sftse:c-dev

Conversation

@sftse
Copy link
Contributor

@sftse sftse commented Jan 19, 2026

Setting the theme from multiple threads will result in undefined behavior on Linux.

Window is Sync but RefCell is not. The methods that set the theme and fullscreen property take a &Window and will internally launder it into a &RefCell accessible from multiple threads.

@Legend-Master

@sftse sftse requested a review from a team as a code owner January 19, 2026 15:29
@github-actions
Copy link
Contributor

github-actions bot commented Jan 19, 2026

Package Changes Through 29967cb

There are 1 changes which include tao with patch

Planned Package Versions

The following package releases are the planned based on the context of changes in this pull request.

package current next
tao 0.34.5 0.34.6

Add another change file through the GitHub UI by following this link.


Read about change files or the docs at github.com/jbolda/covector

@sftse
Copy link
Contributor Author

sftse commented Jan 19, 2026

Fixed CI

@FabianLars
Copy link
Member

can't wait for the day we're finally back with winit. happy to see you getting rid of lazy_static :)

refactor: remove one mem::transmute

are you sure that's not needed? i don't know what it does from the top of my head but winit also still has that code.

@sftse
Copy link
Contributor Author

sftse commented Jan 19, 2026

are you sure that's not needed? i don't know what it does from the top of my head but winit also still has that code.

It's not unsound, since ToUnicode takes a &mut [u16] (that's just dandy, so what is the safety contract then at all? docs say nothing) but it took me a serious double-take because of the &mut to uninitialized memory. I think this used to be UB but maybe got changed? Not sure, but doesn't matter that much to me, can revert if you want.

Edit: This doesn't totally explain that the current use of mem::transmute is sound, but I did check it with MIRI to be 100% sure that at least by itself the mem::transmute can't be unsound.

@sftse
Copy link
Contributor Author

sftse commented Jan 19, 2026

Dropped the mem::transmute change

@sftse
Copy link
Contributor Author

sftse commented Jan 19, 2026

The diffs migrating from lazy_static to once_cell are a bit tedious to verify, difftastic helps a lot here.

@Legend-Master
Copy link
Contributor

Genuine question, what's benefit of switching from lazy_static to once_cell?

And if we want to do it, I would prefer we split it in a separate PR so when we squash merge them, we get 2 different commits for checking with git blame later

Also, you'll need to sign your commits 🙃

@sftse
Copy link
Contributor Author

sftse commented Jan 20, 2026

Genuine question, what's benefit of switching from lazy_static to once_cell?

I've seen lazy_static macros obscure dead code detection, they are a nuisance to autoformat and the once_cell api is the one that got stabilized to std, so the future change to the stabilized api is just a switching of types.
Additionally, pervasively used crates in the ecosystem harm the ability of cargo-depgraph to layout graphs in an intelligible way.

Not really strong arguments, but grug dev see unnecessary dependency, grug dev remove.

@FabianLars
Copy link
Member

less macros = more good

@sftse
Copy link
Contributor Author

sftse commented Jan 20, 2026

To not make the same mistake as in my other PR, here's a program that crashes

fn main() {
    use tao::window::Fullscreen;
    let event_loop = tao::event_loop::EventLoop::new();
    let win = tao::window::Window::new(&event_loop).unwrap();
    std::thread::scope(|s| {
        s.spawn(|| loop {
            win.set_fullscreen(None);
        });
        s.spawn(|| loop {
            win.set_fullscreen(Some(Fullscreen::Borderless(None)));
        });
    });
}

Memory fault

Copy link
Contributor

@Legend-Master Legend-Master left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not the most familiar with the Linux implementation, the PR's code looks good and correct to me though

For the theme part, I initially did it through Mutex in #937 and later @amrbashir changed it to RefCell e595125, so maybe there's a reason for this?

fullscreen: RefCell::new(attributes.fullscreen),
inner_size_constraints: RefCell::new(attributes.inner_size_constraints),
preferred_theme: RefCell::new(preferred_theme),
fullscreen: RwLock::new(attributes.fullscreen),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the chances for read/write are not different by too much, maybe we should use Mutex here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used a RwLock because that is the most direct port of a RefCell to the multithreaded case. A Mutex can't have multiple readers, RwLock and RefCell can.

But scopes here make it clear there can't be deadlocks regardless of Mutex or RwLock, so we can move to Mutex.

@sftse
Copy link
Contributor Author

sftse commented Jan 24, 2026

Any chance to merge this? We can switch to Mutex if that is required, but such a trivial unsoundness needs a fix asap.

@Legend-Master
Copy link
Contributor

Looking at the code, it seems like we use Rc and RefCell in Windows and then implement Send and Sync, this doesn't look right to me

Any chance to merge this? We can switch to Mutex if that is required, but such a trivial unsoundness needs a fix asap.

I don't really know the reason for us to mark the Window as Send + Sync even though we use Rc and RefCell internally, so I want to reach out whoever knows it first before merging (must have a reason right?) cc @tauri-apps/wg-webview

@sftse
Copy link
Contributor Author

sftse commented Jan 25, 2026

The code doesn't look very defensively written, but when I was taking a look at the Rc it didn't seem too problematic. It would be dangerous if we had a derived Clone on Window, but because the Rc are private and none of them seem cloneable from a public method, the boundaries seem to clearly distinguish outside from the inside. As long as no Rc crosses that boundary, we should be fine, on either side of it the mistakes should be caught by Rust's Send-Sync traits.

@sftse
Copy link
Contributor Author

sftse commented Jan 25, 2026

Here's what I understood, we can only move the Rc handles as a unit across thread boundaries.

mod foo {
    use std::rc::Rc;
    unsafe impl Send for Foo {}

    // UNSOUND: #[derive(Clone)]
    pub struct Foo(Rc<()>);
    impl Foo {
        pub fn new() -> Foo {
            Foo(Rc::new(()))
        }
    }
}

use foo::Foo;

fn main() {
    let foo = Foo::new();
    // safe, can only move all Rc handles across thread boundary at the same time
    std::thread::spawn(move || foo);

    // UNSOUND: if we can clone, split Rc handles across threads
    // let handle = foo.clone();
    // std::thread::spawn(move || handle);
}

That said, the atomics inside the Rc<AtomicI32> seem unnecessary in that case.

@Legend-Master
Copy link
Contributor

It is only fine if the ones we initially cloned to the ApplicationWindow callback functions are also moved and dropped together which seems to be the case though, but now, ApplicationWindow is marked !Sync and !Send...

@sftse
Copy link
Contributor Author

sftse commented Feb 7, 2026

I see there are some additional things to investigate, but if we can't resolve those in the near future, does it make sense to delay this?

Window on winit is Send and Sync, so we probably want to retain those if we want to move back to mainline.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants