-
Notifications
You must be signed in to change notification settings - Fork 435
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
Safe initialization of pinned structs #290
Comments
Related rust-lang/rust#85579 by @alex. |
Actually not quite so related :) |
Related != equivalent :) Both improve/simplify the |
I've got to admit that, coming to this project from scratch, I was scratching my head a little bit when I saw these abstractions. I'm sure there's a reason why they are the way they are, I'm hoping to learn why. Maybe #289 indicates that even we couldn't use this safely inside a simple driver? If we could not, what hope do driver developers new to Rust have? @nbdd0121 this looks like a creative solution. Possibly only a partial solution though? Because people could still make structs with I remember having an interesting conversation with @ojeda where he mentioned that in Rust, a little bit of inefficiency is acceptable if it allows us to improve safety. I wonder if we could make a fn new_mutex(data: T) -> Result<Pin<Box<Mutex<T>>>> Then driver authors wouldn't need any Penalty is one extra allocation per mutex-protected structure. May be worth it in many circumstances? Could be you folks have been here before, and the new guy is just going around in circles :) |
It's indeed weird! The fundamentally problem is that currently in Rust you don't have a way to create a type pinned -- you always need to create it and then pin it. E.g.
That's a major problem.
No! The main merit of pub fn new_arc<T, E, F>(f: F) -> Result<Pin<Arc<T>>, E>
where
F: for<'a> FnOnce(PinInit<'a, T>) -> PinInitResult<'a, T, E>, you can see that the return type is already pinned. In fact, the whole design about
I don't think kernel people will like it; an allocation required for creating each With impl<T> Mutex<T> {
fn new(this: PinInit<'a, Self>, data: T) -> PinInitResult<'a, Self, kernel::Error> { /* ... */ }
}
try_new_box(|s| Mutex::new(s, data)) // Result<Pin<Box<Mutex<T>>>>
try_new_arc(|s| Mutex::new(s, data)) // Result<Pin<Arc<Mutex<T>>>> |
Yeah, extra allocations are quite bad, and more than just a little bit of inefficiency. :( |
Yeah I agree with you guys on this one. Gary's solution looks like the way forward. I'm still learning when it comes to pinning... |
@TheSven73 if you haven't seen it yet, https://fasterthanli.me/articles/pin-and-suffering is the best resource I've read |
Thank you @alex! I really need to understand pinning properly - the next stage of the |
@nbdd0121 How does your |
|
Thanks! I think I'm agreeing with you that our current I believe Wedson is adding a work-around for |
What do you all think of wedsonaf@a369dcd? (It's a WIP, so cleanups are name changes are still needed.) Anyway, using it the example above becomes: impl SharedState {
fn try_new() -> Result<Pin<Ref<Self>>> {
Ok(Ref::pinned(kernel::new_ref!(SharedState {
[condvar] state_changed: (),
[mutex] inner: SharedStateInner { token_count: 0 },
})?))
}
} One problem that isn't solved by this yet is |
This is definitely going in the right direction IMHO. But I'm not sure if this is broad enough: it would only solve our This goes for my PR #376 as well, by the way: it's not a good solution, because it's not broad enough. I've had to change my opinion there. @nbdd0121 I still don't quite understand how your |
Looks great but as @TheSven73 said this is a little bit limited. Two major limitations that I can current think of:
This is actually a significant issue. The macro assumes everything is structurally pinned, but structural pinning need safe guarding to prevent arbitrary implementation
As I said earlier, they solve totally different problems and are designed to be used together. @wedsonaf just in case you haven't seen my comment in #376 (proof of concept commit at nbdd0121@549b8e3), here's how you create impl SharedState {
fn try_new() -> Result<Pin<Arc<Self>>> {
Ok(Arc::try_pin_with(init_pin!(Self {
state_changed: kernel::condvar_new!("SharedState::state_changed"),
inner: kernel::mutex_new!("SharedState::inner", SharedStateInner { token_count: 0 }),
}))?)
}
} The POC is written before |
Thanks for explaining. |
Most users don't actually create pinned types. Most just write As for the drop problem, #[pin_project(PinnedDrop)]
struct PrintOnDrop {
#[pin]
field: u8,
}
#[pinned_drop]
impl PinnedDrop for PrintOnDrop {
fn drop(self: Pin<&mut Self>) {
println!("Dropping: {}", self.field);
}
} |
Pinning is introduced to Rust to solve self-references in async functions. async fn foo() {
let x = 1;
let y = &x; // <- This `y` is used across `.await` so it needs to be stored in the future, but it references `x` so it's a self reference.
bar().await;
let z = y;
} Async functions, however, will not do anything unless polled for the first time. So when a Future is created, nothing is executed yet, so there are not yet any self-references. It can therefore be moved freely. When it's polled for the first time, Our use case, however, is different from async fn; we require the struct to be pinned at creation rather than pinned after creation. |
Stale issue: we have the |
The kernel has a lot of data structures that cannot be freely moved, e.g.
Mutex
andCondVar
. Currently what we do is to firstunsafe
ly create instance of them, pin them, and thenunsafe
ly initialize them after having them pinned (currently the code does not requireunsafe
to initializeMutex
, which I believe is a bug, as it could be initialized while locked, which is definitely unsound).For example, this snippet (related #286) from miscdev needs 3 unsafes (5 if we count both inits):
linux/samples/rust/rust_miscdev.rs
Lines 35 to 59 in 1a73789
And this is a very common pattern. In the current state, essentially any use of
CondVar
,Mutex
,Spinlock
or any pinned types require a lot ofunsafe
to initialize. Of course we can have these typesBox
-ed internally but that's many unnecessary allocations.So I spent the past few days designing and implementing a safe way to pin-initialized struct,
pin-init
(doc, repo). This design allows safe initialization and use of pthread mutex without any boxing: https://github.com/nbdd0121/pin-init/blob/trunk/examples/pthread_mutex.rs.Kernel
CondVar
&Mutex
could be implemented in a similar way. Withpin-init
, the above snippet could be written like this:Approaches like this make code more readable and safer; but it requires procedural macro (and parsing capability of
syn
) so it might not be easy to integrate at current stage.The text was updated successfully, but these errors were encountered: