#thread #thread-safe #systems #api #run-time #bread #unsafe

no-std breadthread

GUI abstraction; adds a thread controller that helps get some bread

4 releases

0.1.4 Mar 30, 2023
0.1.3 Aug 21, 2021
0.1.1 Jun 15, 2021
0.1.0 Jun 15, 2021

#124 in #unsafe

MIT/Apache

41KB
738 lines

Deprecated

It was probably a bad idea to use this crate in the first place.

breadthread

Build Status crates.io docs.rs

breadthread provides a thread for getting that bread.

Certain APIs are thread unsafe, and it would be nice to be able to use them in a thread-safe context, since certain runtimes require data to be Send. breadthread provides a mechanism to export this work to another thread.

License

MIT/Apache2 License


lib.rs:

A runtime that allows thread-unsafe code to be executed on a designated

Motivation

There are quite a few APIs that are thread unsafe. The one I had in mind while designing this crate is the Windows winuser windowing API, but there are many others. While small programs may be able to get away with being thread unsafe, larger programs, with runtimes that require Safe bounds, may not.

Ordinarily, these programs will have to resort to convoluted systems to ensure that code runs on a desigated "local" thread or thread pool. The goal of this crate is to simplify these systems by providing a runtime that allows thread-unsafe code to be executed on a designated thread.

Usage

First, create a type to use as a [Tag]. This type will be used to uniquely identify the thread that the runtime will run on at compile time. This ensures that values that are native to one thread will not be used on another thread.

Any 'static type can be used as a tag, and it's recommended to use a zero-sized type.

struct MyTag;

Then, create a BreadThread type. This is the runtime that directives are sent along. To spawn a new thread to run directives on, use the new method.

use breadthread::BreadThread;

let bt = BreadThread::<'static, MyTag>::new();

However, if you already have a system that you'd like to take advantage of (a dedicated thread pool like rayon, for instance), you can use the undriven() method to create both a BreadThread and a Driver. You can transform a thread or thread-like task into the driving thread by calling drive() on the Driver.

let (bt, driver) = BreadThread::<'static, MyTag>::undriven();
my_runtime::spawn_task(move || driver.drive());

Note that the BreadThread and Driver are parameterized by a lifetime, which in this case is 'static. The lifetime is used as a bound for the directives that we send to the thread. Consider using this if you want to send a directive that borrows other data.

Now, we can call the run method on the BreadThread to run a given method.


use breadthread::DirectiveOutput;

let input_value = 7;
let value = bt.run((), move |()| {
    let ret_ty = thread_unsafe_code(input_value);
    DirectiveOutput {
        thread_safe_value: (),
        thread_unsafe_value: ret_ty,
        deleted_values: vec![],
    }
});

bt.run() expects a return type of DirectiveOutput, which consists of:

  • A thread_safe_value that is implied to be Send and Sync.
  • A thread_unsafe_value that may not be any of these. Once value returns, this value will be wrapped in Object, which essentially allows it to be sent to other threads, but only used in the driving thread. To use this value again, pass it into the bt.run() method in place of the empty tuple, and it can be used raw again. Tuples and slices of Objects can be returned using this strategy. Note that values of this kind have to implement Compatible.
  • deleted_values consists of values to be deleted from the thread's internal bank that keeps track of the values that are valid for it. By default, values returned are added to the "valid" list, so if you don't expect to use the value again, you can add it to the deleted_values list.

value is of type Value, and resolves to a tuple of thread_safe_value and the safe version of thread_unsafe_value. It can be resolved in one of three ways:

  • Poll for whether or not it's resolved using value.resolve().
  • Wait for it to be resolved by parking the thread, using value.wait().
  • With the async feature enabled, Value implements Future.

Safety

Using tag ensures that the bread thread will only have values that are tagged as valid associated with it. As long as two threads do not have the same tag, this validation is done at compile time, and the only real overhead is in sending and receiving values from the two threads.

If more than one thread is created with the same tag, by default the library panics. If this behavior is not desired, enable the fallback feature. Instead, when two threads share a tag, they will manually keep track of which values are valid for which thread.

no_std

The std feature is enabled by default. Without std, this crate only relies on the alloc crate. However, certain changes are made both externally and internally.

  • BreadThread::new(), Driv(er::drive() and Value::wait() are not present.
  • Internally, the data structures use spinlock-based APIs instead of ones based on system synchronization. This is often undesireable behavior.

It is recommended to use the std feature unless it is necessary to use this crate in a no_std environment.

Dependencies

~1–30MB
~383K SLoC