Skip to content

[RFC] shared access to resources (resources = [&FOO]) #14

Open
@japaric

Description

@japaric

Summary

Extend the syntax of the resources field to let the user specify when shared,
instead of exclusive (today's default), access to a resource is required.
Specifying shared access can reduce the number of required critical sections in
some scenarios.

Background

Consider this example:

#[rtfm::app(device = ..)]
const APP: () = {
    // NOTE: `ComplexThing` implements the `Sync` trait
    static mut FOO: ComplexThing = /* .. */;

    // ..

    #[task(priority = 1, resources = [FOO])]
    fn foo() {
       resources.FOO.lock(mutate);
    }

    #[task(priority = 2, resources = [FOO])]
    fn bar() {
       resources.FOO.lock(print);
    }

    #[task(priority = 3, resources = [FOO])]
    fn baz() {
        print(resources.FOO);
    }

    // ..
};

fn mutate(foo: &mut ComplexThing) {
    // ..
}

fn print(foo: &ComplexThing) {
    println!("{}", foo);
}

With the current rules, the task bar needs a critical section to access FOO.
However, that critical section is not really needed because task baz cannot
modify FOO.

Design

The parser will be modified to accept both $ident and &$ident in lists of
resources. The former syntax, which exists today, indicates exclusive access to
the resource -- its semantics are unchanged; the latter syntax indicates shared
access to the resource.

When shared access is specified the task will receive a shared reference (&_)
to the resource data rather than a Mutex proxy.

If shared access is requested from two or more tasks that run at different
priorities then the resource type must implement the Sync trait. This is
required to prevent types with interior mutability (like Cell and RefCell)
from being accessed without a critical section, and potentially modified, from
those tasks as that could result in UB.

With the proposed changes our opening example can be updated as follows:

#[rtfm::app(device = ..)]
const APP: () = {
    // ..

    #[task(priority = 1, resources = [FOO])]
    fn foo() {
       resources.FOO.lock(mutate);
    }

    // NOTE: `&FOO` instead of `FOO` in `resources`
    #[task(priority = 2, resources = [&FOO])]
    fn bar() {
        // NOTE: no critical section is needed this time
        let reference: &ComplexThing = resources.FOO;

        print(reference);
    }

    // NOTE: `&FOO` instead of `FOO` in `resources`
    #[task(priority = 3, resources = [&FOO])]
    fn baz() {
        print(resources.FOO);
    }

    // ..
};

Unresolved questions

It's unclear what to do in this scenario, other than raise a compiler error:

#[rtfm::app(device = ..)]
const APP: () = {
    // ..

    #[task(priority = 1, resources = [&FOO])]
    fn foo() { /* .. */ }

    #[task(priority = 2, resources = [FOO])]
    fn bar() { /* .. */ }

No optimization can be applied here. The task foo needs a critical section to
access the resource data, in any form, but the lock API doesn't make sense
here because that grants an exclusive reference (&mut _) to the data and
shared access was requested.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions