-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
not Send due to await retainment #67611
Comments
triage: P-high. Nominating for discussion. |
self-assigned for investigation in short term (as in, next two or three days). However I suspect someone from @rust-lang/wg-async-await will be a better choice to investigate this. |
We should try to bisect this |
used cargo-bisect-rustc. Determined: searched nightlies: from nightly-2019-11-26 to nightly-2020-01-03 |
9081929 is a rollup commit of the following PR's:
I'm guessing this is due to PR #66793 |
I'm still working on reducing this down to something that I could put into the playpen. However, one interesting thing I've found locally during my reduction: This version of the async fn proxy(buf: &[u8]) -> Result<Vec<u8>> {
let tv = unsafe { TIMEOUT };
let dur = Duration::from_millis(unsafe { TIMEOUT });
timeout(Duration::from_millis(unsafe { TIMEOUT }),
async { Result::Ok(vec![0u8]) })
.await?
;
Result::Ok(vec![0u8])
} but if I lift the first argument out to a separate I'll admit that I don't know all the details of how the fn-transformation works to support |
Okay in the details block is a standalone version suitable for the playground: #![crate_type="lib"]
use std::future::Future;
use std::io::Result;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
pub fn timeout<T>(_duration: Duration, _future: T) -> Timeout<T>
where
T: Future,
{
loop { }
}
#[derive(Debug)]
pub struct Elapsed(());
impl std::fmt::Display for Elapsed {
fn fmt(&self, w: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(w, "Elapsed")
}
}
impl std::error::Error for Elapsed {}
impl From<Elapsed> for std::io::Error {
fn from(_err: Elapsed) -> std::io::Error {
std::io::ErrorKind::TimedOut.into()
}
}
pub struct Timeout<T> {
_value: T,
}
impl<T> Future for Timeout<T>
where
T: Future,
{
type Output = std::result::Result<T::Output, Elapsed>;
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
loop { }
}
}
static mut TIMEOUT: u64 = 2000;
use std::marker::PhantomData;
pub struct JoinHandle<T> {
_p: PhantomData<T>,
}
pub fn spawn<T>(_task: T) -> JoinHandle<T::Output>
where
T: Future + Send + 'static,
T::Output: Send + 'static,
{
loop { }
}
pub async fn my_main() {
spawn(proxy());
}
// This version of `fn proxy` demonstrates the error
pub async fn proxy() -> Result<Vec<u8>> {
timeout
(Duration::from_millis(unsafe { TIMEOUT }),
async { Result::Ok(vec![0u8]) })
.await?
.unwrap()
;
Result::Ok(vec![0u8])
}
// This variant does not generate an error
pub async fn proxy_2() -> Result<Vec<u8>> {
let dur = Duration::from_millis(unsafe { TIMEOUT });
timeout
(dur,
async { Result::Ok(vec![0u8]) })
.await?
.unwrap()
;
Result::Ok(vec![0u8])
}
// This variant does not generate an error
pub async fn proxy_3() -> Result<Vec<u8>> {
let tv = unsafe { TIMEOUT };
timeout
(Duration::from_millis(tv),
async { Result::Ok(vec![0u8]) })
.await?
.unwrap()
;
Result::Ok(vec![0u8])
} and the error it generates:
|
So this does seem like somewhat clear (expected?) fallout from PR #67611: a use of a I believe we can readily update the crate in question to avoid using the But I do wonder whether we can/should improve the diagnostics here to try to guide the user towards such a solution ...? |
I might have worded this too strongly, and/or over-reduced my example. In particular, its easy to move around the access to |
I'll have a look at addressing this with another approach I considered for #66793. If that doesn't work out then I'll have a look at the error message. |
The patch linked is enough to fix the crate in question, but I'm all for @matthewjasper finding an in-compiler solution if we can do it soundly. https://gist.github.com/pnkfelix/4bdef50d0538fee8f6e0cbc4379f6074 |
OK. I'm going to write some things up because I've changed my mind a bunch of times about how/if this can be fixed in MIR building and I don't want to keep forgetting things. Background: auto traits and generatorsGenerators store values that if their value needs to be preserved across a yield. These values need to be visible for auto-traits, for example a generator may be resumed on a different thread after it yields, and so cannot be There are two passes that determine which values can be included in the generator:
Background: MIR lowering of statics.After #66587 statics no longer have a special representation in MIR. They are instead lowered to a dereference of a pointer to the given static: static X: T = ...;
static mut Y: S = ...;
// ...
use(X);
use(Y); is lowered as if it were: static X: T = ...;
static mut Y: S = ...;
const X_PTR: &T = &X; // Imagine that this could compile
const Y_PTR: *mut T = &raw mut Y; // Imagine that this doesn't need an unsafe block
// ...
use(*X_PTR);
use(*Y_PTR); Which becomes:
The first problemThe example above is somewhat deceptive in that the temporary that holds the pointer is used immediately after it's assigned. In this case it cannot end up in the generator since it's not live across any user code. However this isn't true in all cases, index and match expressions allow running user code while the pointer is live: X[EXPR]
// Becomes
_tmp1 = const &X;
_tmp2 = EXPR;
// bounds check
(*_tmp1)[_tmp2]
match Y { a if GUARD => { /* ... */ }
// Becomes
_tmp3 = const &raw mut Y;
// check guard
a = *_tmp3; In these cases MIR has to put the temporary pointer into the generator state and promptly ICEs. Fixing the ICEThe obvious solution now is make type checking record the new pointer types in the generator witness in these cases. Unfortunately HIR is not a great tool for specifying "these cases". The methods that the region scope tree provides in HIR are either usually far too large (the "temporary scope") or occasionally too small (the "enclosing scope"). #66793 went for the sound but rather approximate approach, leaving us here. Other potential fixesChange MIR buildingOne idea might be to try changing MIR building so that the temporary is never live across user code. For example, one might change the lowering as follows X[EXPR]
// Becomes
_tmp2 = EXPR;
// bounds check
_tmp1 = const &X;
(*_tmp1)[_tmp2] However working out how to do this for InternalMIR actually already contains an escape hatch for values that are in the MIR but aren't in the HIR in the form of a flag (called static Y = // ...
let x = &Y;
yield;
use(x);
// and
let x = &Y;
yield;
use(&Y); is. I guess one could argue that auto traits are for layout introspection and this would be further breaking that. I think that's everything for now. |
Visiting from triage. @matthewjasper were you planning to work on this further? |
It's definitely tempting to take advantage of the constraints around statics to just declare these equivalent, and use the
Can you expand on this? I'm not sure I follow. |
Another solution that comes to mind relies on the fact that these pointers never need to be stored in a generator. They are consts, and the temporaries containing them shouldn't ever be mutated. We could add semantics for "const temporaries" to MIR, and never store them inside generators. The semantics would (hopefully) be simple: defined once and assigned a value that doesn't depend on any other temporaries; never mutated or borrowed mutably. We can move the statements defining them to the top of the But that would be adding more semantics and special casing to MIR to fix a change which removed extra semantics and special casing, so I'm not sure if that's what we want :) |
The argument is that the given the following auto trait we guarantee that if auto trait DoesntContainRawMutPtr {}
impl<T: ?Sized> !DoesntContainRawMutPtr for *mut T {}
impl<T: ?Sized> DoesntContainRawMutPtr for *const T {}
impl<T: ?Sized> DoesntContainRawMutPtr for &T {}
impl<T: ?Sized> DoesntContainRawMutPtr for &mut T {} If we make that guarantee then using internal in generators would not be OK. I would argue that we're not making such (or really any) guarantees with user defined auto traits. |
Yeah, I don't think auto traits should expose layout implementation details of generators. And this really does seem like a pure implementation detail -- it doesn't have any bearing on what kinds of (unsafe) operations are allowed on a generator, like a self-reference would. cc @rust-lang/lang |
So, I've been putting off commenting here to let these ideas sit, but let me leave a comment. I think I've reached a conclusion. TL;DR. I think we should mark these Auto traits weren't really intended as a tool for probing layout, but rather semantic considerations. This is why traits like Rather, this question is about is simply "what are the constituent types exposed by a generator", and this should correspond to the types that user code can directly access. The raw pointers here are not directly accessible. The values that are accessible are the "live values" that the user's code may later use -- and of course the code currently makes the constituent types be a superset of those, based on the AST structure. Of course, the real root cause of this error is a simplifying change to the definition of
but now we changed As we've been talking about this change, I've been saying that we should be considering the older definition of In particular, compiling We could fix this in two ways. We could write some code motion / duplication that takes temporaries like Secondly, some of the proposals we've been kicking around for going further with simplifying Having written out both of those options, though, I sort of lean towards the first, at least for this specific issue. |
I think these references and raw pointers created by MIR construction should be marked as Drop flags are ignored for similar reasons. You cannot use an auto trait like this to detect them:
So I think that #66793 should be reverted and the temporary static references should be marked as |
Note: @matthewjasper opened #68494 instituting this change and I r+'d it, since
In particular, there were some suggestions that this might be a lang team issue; I think there is some 'intersection' and I don't want the perception that I'm trying to shortcircuit discussion. That said, I also think this is primarily a compiler team matter, since it's a deviation from the old behavior, and further this deviation was caused by altering the MIR away from the "natural, Rust-based" definition and towards a lower-level definition. |
The text was updated successfully, but these errors were encountered: