avr-device icon indicating copy to clipboard operation
avr-device copied to clipboard

Better Mutex

Open Rahix opened this issue 3 years ago • 8 comments

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 avatar Oct 02 '20 11:10 Rahix

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.

couchand avatar Oct 09 '20 14:10 couchand

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 ...

Rahix avatar Oct 09 '20 14:10 Rahix

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) {
    // ...
}

couchand avatar Oct 09 '20 14:10 couchand

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 avatar Oct 09 '20 15:10 Rahix

Actually, no:

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

        // now what
    });
}

Rahix avatar Oct 09 '20 16:10 Rahix

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

couchand avatar Oct 09 '20 17:10 couchand

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.

Rahix avatar Oct 09 '20 18:10 Rahix

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! :)

couchand avatar Oct 09 '20 19:10 couchand