-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
Poll evented mutability #37
Conversation
Generally speaking, it is unsafe to access to perform asynchronous operations using `&self`. Taking `&self` allows usage from a `Sync` context, which has unexpected results. Taking `&mut self` to perform these operations prevents using these asynchronous values from across tasks (unless they are wrapped in `RefCell` or `Mutex`.
34086f0
to
2b9d2e9
Compare
I believe the original goal here was to surface the thread safety provided by the underlying OS primitives. But it's true that it globally imposes some coordination for us, and in practice it's essentially always a bug to race on these operations, so requiring that you've mediated To be clear, when you talk about punting the problem (and splitting the read/write state), is the idea that we could offer a This is one where I'd like input from @alexcrichton as well. |
Can you say more? I don't understand why it is unsafe to have two writers concurrently accessing the same Edit; oh, I see, you just mean that reads and writes intermixing is probably wrong, not that it's "unsafe" as in memory unsafety. |
@carllerche on further reflection, I'd like to have a bit more confidence that we can allow you to split up the read/write halves without imposing any coordination. Can we spike that out? |
@aturon there is an entire gradient of safety with regards to splitting the read / write halves. The option that has the least overhead involves exposing some |
@aturon I guess, the question is, what do you see the goal of the spike being? a) To see if it is technically possible. |
@cramertj Yes, I mean unsafe from a usage P.O.V. (trampling the task being registered to receive notifications). |
@carllerche I'm confused about your use of "unsafe" terminology here. Can you expand on that? |
ContextAt a high level, By async operation, I am referring to the contract described by If a If a GoalSince a When the underlying I/O resource is A "hard to misuse" API should be provided to the end user. ProblemBy taking On top of that, the implementation of This PRAs of now, this PR switched The issue now is that there is no "low coordination" way to perform the read and write operations on separate tasks with a single Where to go now.Well, the real question is, how should a Since the goal is to leverage the inherent The last question that remains is, how does one get down to the lowest level and use One option to deal with this would be to bump down a level and provide the necessary building blocks for users to achieve the desired behavior. For example, instead of having This would be an advanced API as there would be no protection against accidentally reading from the TcpStream concurrently. |
@carllerche Ok, thanks -- that all matches my understanding. But just to be 100% clear, you use of the term "unsafe" before was loose; there's no risk of memory safety violations here, "just" bugs. The splitting strategy sounds fine to me. |
Yes, no memory unsafety. Only misuse bugs. |
Hm I don't think I'd personally be in favor of this change. 100% of the time in the past we've regretted adding anything on top of the OS that wasn't already there, such as the "implicit synchronization" here of using I think it's definitely possible to misuse the shared reference impls, but I don't think the solution is to just make all those use cases impossible without extra synchronization? We could perhaps explore something like dynamic detection of misuse of tasks to be enabled in debug mode or something like that. |
@alexcrichton Could you expand more on your reservations? "100% of the time in the past we've regretted adding anything on top of the OS that wasn't already there" doesn't really address the proposal.
I am not proposing to make it impossible. In fact, I personally care to be able to do it. What I am trying to achieve is:
Point 3) could be addressed by something like: impl<T> PollEvented<T> {
/// Requires that the caller guarantees that calls to this function are coordinated.
unsafe fn uncoordinated_reader(&self) -> UncoordinatedReader
where for<'a> &'a T: io::Read
{
// ...
}
} To be clear, the current PR would need to change. Specifically, the
This would be interesting to explore regardless, but making it hard to make the mistake in the first place is ideal. |
@alexcrichton is out sick right now, but he and I discussed this some offline, and while he still has hesitations, I think we can go forward with it. |
@aturon There is no urgency to get this merged. We can wait until next week. |
Just to clarify, the |
@cramertj It is unsafe because the caller needs to ensure to coordinate calls to |
Right, I'm trying to clarify why failing to coordinate calls would cause memory unsafety or data races. If there's not an underlying justification in terms of memory unsafety or data races, I don't think that |
Because it would be possible to mutate the same data from across multiple threads (it is unsafe in the rust sense). |
Generally speaking, it is unsafe to access to perform asynchronous
operations using
&self
. Taking&self
allows usage from aSync
context, which has unexpected results.
Taking
&mut self
to perform these operations prevents using theseasynchronous values from across tasks (unless they are wrapped in
RefCell
orMutex
. This also allows removing synchronization thatreally shouldn't be there in the first place. Specifically, an atomic is no
longer used to cache the readiness state. This means that the majority
of interactions with a
PollEvented
can happen with no coordination.However, implementing this change re-raises the question of how to
read and write from separate tasks.
AsyncRead::split
in tokio-io offersone solution. But that introduces coordination that isn't required in all
cases.
Specifically,
mio::TcpStream
is fullySync
. A "read" and a "write"handle should require zero additional coordination. The
PollEvented
read and write state can be split into separate fields.
A complete answer to this problem can be punted and
split
fromtokio-io
provides an escape hatch.Depends on #35.