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

Relax Allocator bounds into pin-safe trait #94114

Closed
wants to merge 2 commits into from

Conversation

djkoloski
Copy link
Contributor

Because Allocator requires that returned memory blocks remain valid
until the instance and all clones are dropped, some types of allocators
cannot fulfill the safety requirements for the trait. This change
relaxes the safety requirements of Allocator to blocks remaining valid
until the allocator becomes unreachable and moves the bound into a
separate PinSafeAllocator trait.

It also relaxes some overly cautious bounds on the Unpin impls for
Box since unpinning a box doesn't require any special consideration.

@rust-highfive
Copy link
Collaborator

r? @m-ou-se

(rust-highfive has picked a reviewer for you, use r? to override)

@rustbot rustbot added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Feb 18, 2022
@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Feb 18, 2022
@djkoloski
Copy link
Contributor Author

r? @yaahc

Relevant discussion:

@TimDiekmann and @CAD97 for wg-allocators visibility.

@rust-highfive rust-highfive assigned yaahc and unassigned m-ou-se Feb 18, 2022
@rust-log-analyzer

This comment has been minimized.

Copy link
Contributor

@CAD97 CAD97 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe Box::leak also needs to be bound by A: PinSafeAllocator. Otherwise, this matches what I was looking for with extracting the pin safe guarantee into its own extension trait.

@@ -87,7 +87,7 @@ impl fmt::Display for AllocError {
/// # Safety
///
/// * Memory blocks returned from an allocator must point to valid memory and retain their validity
/// until the instance and all of its clones are dropped,
/// until the instance and all of its clones become unreachable,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is, of course, the hard part of this PR.

I'm thinking of the guarantee in a similar way to that of references, in that it's a "while the handle exists." Of course, under the Stacked Borrows model (and, to an extent, NLL), it's not when the borrow exists, but until when the borrow is last used.

So there're two ways I currently see to explain the weaker (not pin safe) guarantee, borrowing language from references:

  • The memory regions are valid for the lifetime of the allocator. This is complicated by the written 'static lifetime not actually being "the lifetime of the allocator" in this fashion, but an upper bound, so probably not a good way to explain it.
  • The memory regions are valid until the point when the last clone is last used.

Effectively, what we're looking for is the dual of the pinning guarantee.

So I think how I'd draft this is

Memory blocks must retain their validity until the allocator instance and all of its clones are dropped, forgotten, leaked, or otherwise never accessed again. (In other words, when a lifetime held by the allocator instance is allowed to expire.)

(And now writing that I'm interested in how the #[may_dangle] borrowck eyepatch interacts with the guarantee, since that (very roughly) allows a lifetime to expire before the container. IIUC it doesn't, since the eyepatch refers to the lifetime within a generic argument, but I'm never certain w.r.t. may_dangle.)

Copy link
Contributor Author

@djkoloski djkoloski Feb 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the wording to be more explicit about these conditions. Does that work?

@rust-log-analyzer

This comment has been minimized.

@djkoloski
Copy link
Contributor Author

Looks like changing leak comes with some baggage. I'll have to take a closer look later on.

@djkoloski djkoloski force-pushed the pin_safe_allocator branch 2 times, most recently from 7f367f3 to 7591909 Compare February 19, 2022 03:15
@djkoloski
Copy link
Contributor Author

I pushed through some changes to Box::leak. There were some comments about MIRI in there, so it might be a good idea to get these tested properly under it.

@JohnCSimon JohnCSimon added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 20, 2022
@bors
Copy link
Contributor

bors commented Mar 23, 2022

☔ The latest upstream changes (presumably #94901) made this pull request unmergeable. Please resolve the merge conflicts.

@djkoloski
Copy link
Contributor Author

djkoloski commented Mar 23, 2022

@rustbot label +T-libs-api -T-compiler
@rustbot ping @rust-lang/wg-allocators

@rustbot rustbot added T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. and removed T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Mar 23, 2022
@rustbot

This comment was marked as resolved.

@tmandry
Copy link
Member

tmandry commented Mar 23, 2022

cc @rust-lang/wg-allocators

@Amanieu
Copy link
Member

Amanieu commented Apr 1, 2022

Another possibility would be to change the safety condition for Allocator to :

Memory blocks returned from an allocator must point to valid memory and retain their validity until the instance and all of its clones are dropped, or for the lifetime of the allocator type, whichever is shorter.

(This could probably be worded better...)

This moves the ball back into Box::pin_in's camp to required A: 'static, but it removes a footgun for implementers of non-'static allocators.

I'm hesitant to add more complexity to the Allocator API just to support pinning, which is a fairly niche operation.

@djkoloski
Copy link
Contributor Author

I think it would be sufficient to say:

Memory blocks returned from an allocator must point to valid memory and retain their validity for the lifetime of the allocator type.

As a few examples:

  • 'static allocators must leak memory indefinitely (for a 'static lifetime) if forgotten.
  • Any allocator that manages backing memory with a non-static lifetime 'a has its lifetime bounded by 'a. Therefore, it's safe for the allocator to guarantee that the memory will not be repurposed during 'a.

I've been prototyping out what this would look like in a crate and it seems like 'static allocators would be compatible with Pin.

As an aside, I've also been trying out changing Pin<P> to only pin for the lifetime of P and that seems promising as well. I think there's a parallel here between generalizing the validity guarantees of Allocator and generalizing the pinning guarantees of Pin.

@CAD97
Copy link
Contributor

CAD97 commented Apr 1, 2022

Personally I think that extracting the pinning guarantees to a separate trait simplifies the main API w.r.t. pinning rather than complicates it for pinning, but if a simple single-trait solution works, then that's better.

Personally, I think I'd word it something along the lines of

Memory blocks allocated by this allocator must remain valid for the lifetime of the allocator or until all clones of the allocator are dropped, whichever is sooner.

In an equivalent formulation, given T: ?Unpin, A: 'a + Allocator, A must allocate blocks compatible with the pinning guarantees of Pin<Box<T, A>>, which can be leaked into Pin<&'a mut T>.

(This is sort of circular, as the purpose of this clarification is to make Pin<Box<_, A>> work, but Box is "how safe allocations work" and clearly communicates the 'a-or-drop-whichever-is-first.)


As an aside, I've also been trying out changing Pin<P> to only pin for the lifetime of P and that seems promising as well.

This sounds sketchy, though, because how do you keep the moral equivalent of the following from blowing up? (Off topic for this thread, feel free to ping me on Zulip)

let mut future: impl Future;
'a: {
    Pin::new(&'a mut future).poll();
}
// 'a is over, future "isn't" pinned
// caveat emptor: yes it is, per current pin documentation
forget(future);
// reuse future's backing memory
// if the async runtime stored a pointer to the future, 💣

@bors
Copy link
Contributor

bors commented Apr 7, 2022

☔ The latest upstream changes (presumably #95678) made this pull request unmergeable. Please resolve the merge conflicts.

@yaahc
Copy link
Member

yaahc commented Apr 29, 2022

r? rust-lang/libs-api

@rust-highfive rust-highfive assigned m-ou-se and unassigned yaahc Apr 29, 2022
@JohnCSimon JohnCSimon added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 20, 2022
@JohnCSimon
Copy link
Member

Ping from triage:
@djkoloski
Can you please address the merge conflict?

Because `Allocator` requires that returned memory blocks remain valid
until the instance and all clones are dropped, some types of allocators
cannot fulfill the safety requirements for the trait. This change
relaxes the safety requirements of `Allocator` to blocks remaining valid
until the allocator becomes unreachable and moves the bound into a
separate `PinSafeAllocator` trait.

It also relaxes some overly cautious bounds on the `Unpin` impls for
`Box` since unpinning a box doesn't require any special consideration.
@rustbot
Copy link
Collaborator

rustbot commented Jul 24, 2022

Hey! It looks like you've submitted a new PR for the library teams!

If this PR contains changes to any rust-lang/rust public library APIs then please comment with @rustbot label +T-libs-api -T-libs to tag it appropriately. If this PR contains changes to any unstable APIs please edit the PR description to add a link to the relevant API Change Proposal or create one if you haven't already. If you're unsure where your change falls no worries, just leave it as is and the reviewer will take a look and make a decision to forward on if necessary.

Examples of T-libs-api changes:

  • Stabilizing library features
  • Introducing insta-stable changes such as new implementations of existing stable traits on existing stable types
  • Introducing new or changing existing unstable library APIs (excluding permanently unstable features / features without a tracking issue)
  • Changing public documentation in ways that create new stability guarantees
  • Changing observable runtime behavior of library APIs

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

The `BTreeMap` implementation was updated with support for custom
allocators, but many of the methods that take allocators do not require
that the provided allocator is the same allocator that is used for the
owning `BTreeMap`. This could lead to situations where nodes are created
with a different allocator, which would violate the safety conditions of
`deallocate` on drop. This was discovered because the changes to
`Box::leak` make it invalid to call without upgrading the allocator to
`A: PinSafeAllocator`.

This also updates `Box::pin_in` with the new `PinSafeAllocator` trait.
@djkoloski
Copy link
Contributor Author

@JohnCSimon PR has been fixed up.

@bors
Copy link
Contributor

bors commented Sep 22, 2022

☔ The latest upstream changes (presumably #100982) made this pull request unmergeable. Please resolve the merge conflicts.

@KittyBorgX
Copy link
Member

@rustbot author
@djkoloski Hey! It looks like there are merge conflicts, so can you sort them out? A reviewer will review the code once you're done!

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 29, 2023
@Dylan-DPC
Copy link
Member

Closing this as inactive

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.