GUIs and the Main Thread

Continuing the discussion from My gamedever wishlist for Rust:

[quote="tomaka, post:1, topic:2859, full:true"]

  • No way to detect at compile-time whether we are in the main thread. This one is a bit weird, but on OS/X some GUI operations can only be done in the main thread. It would be nicer if this was detectable at compile-time.[/quote]
    This probably deserves its own topic. How do you define the main thread and what kind of compile-time support would you expect?

In the Gtk-rs project we have a similar requirement to uphold:

You should only use GTK+ and GDK from the thread gtk_init() and gtk_main() were called on. This is usually referred to as the “main thread”.

Enforcing this statically would probably mean that gtk_init should return a !Send + !Sync struct and any other object constructors take a reference to it (or a clone) as a parameter to prevent creation of, say, a window in another thread. Work out the thread safety/affinity story · Issue #3 · gtk-rs/gtk · GitHub has links to more discussions. We haven't acted on this yet.

3 Likes

I already suggested using a "context" value (may even be empty, but must be !Send + !Sync) in the other thread. For a library like GTK, this seems like a good fit, API-wise.

Even then, there's no guarantee that the thread fetching the context is a main thread, or that no two threads fetch their own contexts (ok, the latter can be avoided at runtime, at the cost of a mutex).

An issue with a context that has to be passed to every constructor

let button = Button::new(&context);

is that you have to pay, in boilerplate, for what you may not use (multi-threading). This would be a turn-off for anyone who has worked with GTK in other languages.

A possible way to hide this ubiquitous extra parameter might be to make the context a factory factory, allowing you to write:

let button = gtk.button().new();

This seems far from conventional though.


I could live with the context value if it was some kind of standard idiom (maybe even specific type) shared by different libraries. Still, keeping track of this manually seems a bit silly.

Preliminary note: everything I say here is second-hand information. I don't have a mac so I have never tried it myself.

The problem is that on OS/X it must not be the same thread as gtk_init() and gtk_main(), it has the be the main thread, in other words the thread where main was called.

Currently glutin crashes if you try to create a window in a different thread. That's not a bug in glutin, but the fact that glutin doesn't check whether it is in the main thread (well, you could consider as a bug the fact that it's not detected).

If this was just "the same thread", glutin would spawn a background thread and do all the operations there.

I guess this one is the problem. From Application Kit Framework Thread Safety:

[quote]#### NSView Restrictions

The NSView class is generally not thread-safe. You should create, destroy, resize, move, and perform other operations on NSView objects only from the main thread of an application. Drawing from secondary threads is thread-safe as long as you bracket drawing calls with calls to lockFocusIfCanDraw and unlockFocus.
[/quote]

But you've probably already seen this back at Examples of API that are "intricately" single-threaded? - #2 by comex


At least it seems possible to do dynamic checks for "mainness" of a thread -- both in Cocoa and in GTK.

Why a factory-factory? It seems just gtk.button() works fine without having to have extra calls (certainly for constructors without many arguments).

In any case, I think that approach is exactly the right one for Rust: use methods on things with the correct Send and Sync (un)implementation to get the guarantees one needs.

(There's still the question of enforcing that the context value is only created on the main thread, of course.)

2 Likes

[quote="huon, post:6, topic:2863"]
Why a factory-factory? It seems just gtk.button() works fine without having to have extra calls (certainly for constructors without many arguments).
[/quote]Button has more than one constructor. Let's say they're new and new_from_icon_name. So now we must add something like

let button = gtk.button_from_icon_name(...);

This seems clearer:

let button = gtk.button().new_from_icon_name(...);

One might suggest a creative builder-based solution but it doesn't scale.

Consider that the existing APIs

let button = Button::new();
let button = Button::new_from_icon_name(...);

would probably be kept for those who prefer dynamic checking. We'd want consistency between these two.

I've played with the same issue. One other API idea I came up with is:

use gtk::build::{ Button, IconButton };

let button = gtk.create(Button {
    label: "Hello",
});

let icon_button = gtk.create(IconButton {
    stock_id: "gtk-save",
});

But I haven't tried it and don't know if it works out nicely. What I do like is that I can theoretially pass builder objects into other builder objects.

I don't want to cut off your discussion, for the me the main problem is this:

Personnally I have absolutely zero care about how the API looks like. What I'm looking for is a way to trigger a compile-time error when the user tries to create a window is something else than the main thread.

Whether it is Button::new(foo) or foo.build_button() or something else is a minor detail for me.

[quote="tomaka, post:9, topic:2863"]
What I'm looking for is a way to trigger a compile-time error when the user tries to create a window is something else than the main thread.
[/quote]Fair enough but I thought you had some ideas about the way to achieve this. Should fn main receive a MainThread token or something?

It could look like this:

#[main_thread]
fn foo() {}

fn main() {
    foo();   // ok

    thread::spawn(|| foo()  /* compilation error */);
}

The rules being:

  • Any function marked as #[main_thread] needs to be called from the main thread.
  • Any function or closure that calls a function that needs to be called from the main thread needs to be called from the main thread as well.
  • All function pointers and closures implement the AnyThread trait expect those that need to be called from the main thread.
  • The thread::spawn function gets an additional bound: fn spawn<F>(_: F) where F: 'static + Send + AnyThread

That would require some pretty serious language changes, however I can't find a lightweight solution.

There's also the possibility to not solve the problem and simply return an error at runtime when we are not in the main thread.

I'm not sure whether this is too strong of a guarantee based on a) the massive change that it would introduce and b) how well you could guarantee that.

I also don't see how you could actually prove this property without analyzing the whole program. For example, what happens in this case:

extern "C" fn foo() { bar() }

#[main_thread]
fn bar() {}

I cannot force foo() to be called in the main thread. I'd be interested if proving this property is actually possible.

Also, it would introduce the language to the concept of a Thread, which is actually a library concern in Rust.

I think the solution of having a token that can only be constructed in the main thread and is a clever solution that leaves one thing to the developer: construct the token at the right place.

I agree with skade; also it is much easier to write a lint that ensures some function is only called from another function called "main" than to follow all code paths to something running in a non-main thread.

The combination: main token getter + lint (that ensures at compile time the main token is only fetched in main()) seems to solve the issue well enough.

If there is interest in such a lint, I'll gladly write one.

LaaS? Lints as a Service?

This is not just a restriction of OS X, I think many (most?) GUI toolkits work that way. At least the one that I know best (.NET/Windows Forms)

The "main" thread is the thread where the main event loop is running, not necessarily the thread where the main() function is called.

IMO this cannot be solved statically. Most toolkits provide a way to call a function/callback on the main thread (from a background thread). This is nothing other than injecting the callback into the event loop and waiting until it finishes.
This is a very important pattern since GUI programming is usually event based.

Example:
In C# there's a class "BackgroundWorker" that has two events (callbacks), "DoWork" and "RunWorkerCompleted".

  • DoWork is run on a background thread
  • RunWorkerCompleted is guaranteed to run on the main thread.

I don't think that this can be solved statically.

[quote="troplin, post:15, topic:2863"]
This is not just a restriction of OS X
[/quote]The working assumption right now is that OSX is different in that the main loop and stuff actually must happen in the fn main thread.

[quote="troplin, post:15, topic:2863"]
The "main" thread is the thread where the main event loop is running
[/quote]What makes you say this can't be enforced statically? The ways to do just that have already been outlined above.

My bad, didn't read carefully enough.

The event loop is not Rust code and probably cannot be analyzed, even if you have the whole program.
So every toolkit function taking a callback has to be annotated if it is guaranteed that this callback is only called on the main thread. And then you have to trust the toolkit that this is really the case.
So yes it's probably possible if you get all this right.

But if it really has to be the main() thread (as opposed to the event loop thread), I think this will break down. In the case of a plugin, the main function isn't even Rust code.

Every GUI library that I have seen is single-threaded. But I like Windows' flexibility here, as it only applies to individual controls. You can create controls (HWND) from any thread, and they are associated with that thread. Messages for that control are added to that thread's message queue and you need to read them out from that thread (well, you can also have threads share a message queue). No thread is more special than another. You can even have a child control on a different thread than its parent (though I would not recommend doing that).

OS X is different. You have to create controls and handle events on the thread that called main().

I have worked some on a cross-platform GUI library, and I would be in favor of some way to check that the current thread is the one that called main(). Without a check, it is possible to write code on Windows that won't run on OS X, and there's no way of even doing a runtime check on Windows.

From https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html: "The main thread is the one blocked in the run method of NSApplication..." That seems like it's easy to detect from any GUI library: the GUI library is the code that invokes the run method. I'm not sure how this got turned into an argument about detecting the thread which Rust's main() was invoked on.

I'm not sure how this got turned into an argument about detecting the thread which Rust's main() was invoked on.