Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better Mutex #52

Open
Rahix opened this issue Oct 2, 2020 · 8 comments
Open

Better Mutex #52

Rahix opened this issue Oct 2, 2020 · 8 comments
Labels
runtime Related to startup/interrupts/runtime

Comments

@Rahix
Copy link
Owner

Rahix commented Oct 2, 2020

We're currently relying on the mutex type from bare-metal which is actually being removed upstream so now is a good time to think about adding a similar mechanism here and maybe improving it. For starters, I'd want the mutex to actually hand out &mut _ references because 99% of use-cases need this.

It would be interesting to explore the concept of a critical-section handle a bit more:

  • Maybe ISRs should optionally get a cs-handle as an argument as nothing could interrupt them?
  • Even further, maybe main should get a cs-handle in the beginning? Interrupts are disabled so it would be sound. Then, later an interrupt::enable() function should consume the handle when enabling interrupts for the first time. Is there a problem I'm missing?
@Rahix Rahix added the runtime Related to startup/interrupts/runtime label Oct 2, 2020
@couchand
Copy link
Contributor

couchand commented Oct 9, 2020

I've been thinking about your latter two questions a lot, and came to roughly the same conclusion. I've been avoiding writing cs-based APIs for some things based on the (almost certainly misguided) concern that I already know interrupts are disabled, but it's exactly the two cases you laid out here.

One note: I've never used them, but I'm dimly aware that you can have re-entrant ISRs by manually re-enabling interrupts in an ISR, so the function to enable interrupts by consuming a CS should be callable there, too, but I think that shakes out for free.

I'd want the mutex to actually hand out &mut _ references because 99% of use-cases need this.

I would recommend making the API follow exactly from other places rather than trying to be too clever. Principle of least surprise, do one thing and do it well, and all that.

@Rahix
Copy link
Owner Author

Rahix commented Oct 9, 2020

Actually, just noticed something:

interrupt::free(|cs1| {
    interrupt::free(|cs2| {
        interrupt::hypothetical_enable(cs1);

        some_mutex.lock(&cs2);
    });
});

I don't think this is going to work unfortunately ...

@couchand
Copy link
Contributor

couchand commented Oct 9, 2020

Or even:

fn main(cs1: CriticalSection) -> ! {
    interrupt::free(|cs2| {
        interrupt::hypothetical_enable(cs1);

        some_mutex.lock(&cs2);
    });

    // ..
}

But maybe it would be a new privileged type, such as:

pub struct CriticalPrologue {
    cs: CriticalSection,
}

impl Deref for CriticalPrologue {
    type Target = CriticalSection;
    fn deref(&self) -> &Self::Target { &self.cs }
}

pub fn hypothetical_enable(cp: CriticalPrologue) {
    // ...
}

@Rahix
Copy link
Owner Author

Rahix commented Oct 9, 2020

Yeah, different kinds of tokens was also what I was thinking about but not sure if that's a good idea ... Also, it really feels like Deref-abuse to do that but I don't see a different solution here :/

@Rahix
Copy link
Owner Author

Rahix commented Oct 9, 2020

Actually, no:

fn main(other_cs: CriticalPrologue) {
    interrupt::free(|cs| {
        interrupt::enable(other_cs);

        // now what
    });
}

@couchand
Copy link
Contributor

couchand commented Oct 9, 2020

Would this be too heavy-handed a solution?

pub struct CriticalPrologue<'a>(&'a CriticalSection);

impl<'a> Deref for CriticalPrologue<'a> {
    type Target = CriticalSection;
    fn deref(&self) -> &Self::Target { &self.0 }
}

mod interrupt {
    pub fn free<T, F: FnOnce(&CriticalSection) -> T + 'static>(f: F) -> T {
        f(&CriticalSection)
    }
}

The error message isn't great but it would seem to provide the right guarantee.

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6ac82256d10cf44359bef766ef4a1a71

@Rahix
Copy link
Owner Author

Rahix commented Oct 9, 2020

When commenting out the bad main in your example, it still does not compile?

TBH, I don't see how there even could be a borrow-checker based solution here, I think this problem is inherent. If I'm not mistaken, this is pretty much analogous to the reason why RefCell cannot be statically checked.

And while there might be other dirty tricks to prevent a function call below a certain other function, I don't think that's worth it. The common pattern in most embedded rust applications, to just do all initialization inside an interrupt::free() works well enough IMO.

@couchand
Copy link
Contributor

couchand commented Oct 9, 2020

When commenting out the bad main in your example, it still does not compile?

Oh my, I really thought I had checked that. No wonder I was so confused how that worked -- it didn't.

The common pattern in most embedded rust applications, to just do all initialization inside an interrupt::free() works well enough IMO.

All this brouhaha to try and save a single cli. What a waste! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
runtime Related to startup/interrupts/runtime
Projects
None yet
Development

No branches or pull requests

2 participants