Skip to content

Commit 88f12fd

Browse files
committed
feat: Make get_mut() et al safe
Since Poller::add() is now safe as of smol-rs/polling#256, we are now no longer bound by I/O safety restrictions. This PR makes the following changes: - get_mut(), read_with_mut() and write_with_mut() are now safe. - The AsyncRead, AsyncWrite and AsyncSeek implementations for Async<> no longer have the `IoSafe` bound. - The `IoSafe` bound is marked as deprecated. It should be removed entirely in the next major release. Signed-off-by: John Nunley <dev@notgull.net>
1 parent 576b470 commit 88f12fd

File tree

8 files changed

+59
-94
lines changed

8 files changed

+59
-94
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ concurrent-queue = "2.2.0"
3131
futures-io = { version = "0.3.28", default-features = false, features = ["std"] }
3232
futures-lite = { version = "2.0.0", default-features = false }
3333
parking = "2.0.0"
34-
polling = "3.4.0"
34+
polling = { git = "https://github.com/smol-rs/polling.git", branch = "notgull/safe" }
3535
rustix = { version = "1.0.7", default-features = false, features = ["fs", "net", "std"] }
3636
slab = "0.4.2"
3737
tracing = { version = "0.1.37", default-features = false, optional = true }

examples/linux-inotify.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,18 @@ fn main() -> std::io::Result<()> {
3838
// Watch events in the current directory.
3939
let mut inotify = Async::new(Inotify::init()?)?;
4040

41-
// SAFETY: We do not move the inner file descriptor out.
42-
unsafe {
43-
inotify
44-
.get_mut()
45-
.watches()
46-
.add(".", WatchMask::ALL_EVENTS)?;
47-
}
41+
inotify
42+
.get_mut()
43+
.watches()
44+
.add(".", WatchMask::ALL_EVENTS)?;
45+
4846
println!("Watching for filesystem events in the current directory...");
4947
println!("Try opening a file to trigger some events.");
5048
println!();
5149

5250
// Wait for events in a loop and print them on the screen.
5351
loop {
54-
for event in unsafe { inotify.read_with_mut(read_op).await? } {
52+
for event in inotify.read_with_mut(read_op).await? {
5553
println!("{event:?}");
5654
}
5755
}

src/lib.rs

Lines changed: 40 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -537,10 +537,6 @@ impl Stream for Timer {
537537
/// For higher-level primitives built on top of [`Async`], look into [`async-net`] or
538538
/// [`async-process`] (on Unix).
539539
///
540-
/// The most notable caveat is that it is unsafe to access the inner I/O source mutably
541-
/// using this primitive. Traits likes [`AsyncRead`] and [`AsyncWrite`] are not implemented by
542-
/// default unless it is guaranteed that the resource won't be invalidated by reading or writing.
543-
/// See the [`IoSafe`] trait for more information.
544540
///
545541
/// [`async-net`]: https://github.com/smol-rs/async-net
546542
/// [`async-process`]: https://github.com/smol-rs/async-process
@@ -680,9 +676,7 @@ impl<T: AsFd> Async<T> {
680676
/// it is not set. If not set to non-blocking mode, I/O operations may block the current thread
681677
/// and cause a deadlock in an asynchronous context.
682678
pub fn new_nonblocking(io: T) -> io::Result<Async<T>> {
683-
// SAFETY: It is impossible to drop the I/O source while it is registered through
684-
// this type.
685-
let registration = unsafe { Registration::new(io.as_fd()) };
679+
let registration = Registration::new(io.as_fd());
686680

687681
Ok(Async {
688682
source: Reactor::get().insert_io(registration)?,
@@ -775,10 +769,7 @@ impl<T: AsSocket> Async<T> {
775769
/// and cause a deadlock in an asynchronous context.
776770
pub fn new_nonblocking(io: T) -> io::Result<Async<T>> {
777771
// Create the registration.
778-
//
779-
// SAFETY: It is impossible to drop the I/O source while it is registered through
780-
// this type.
781-
let registration = unsafe { Registration::new(io.as_socket()) };
772+
let registration = Registration::new(io.as_socket());
782773

783774
Ok(Async {
784775
source: Reactor::get().insert_io(registration)?,
@@ -839,10 +830,6 @@ impl<T> Async<T> {
839830

840831
/// Gets a mutable reference to the inner I/O handle.
841832
///
842-
/// # Safety
843-
///
844-
/// The underlying I/O source must not be dropped using this function.
845-
///
846833
/// # Examples
847834
///
848835
/// ```
@@ -851,10 +838,10 @@ impl<T> Async<T> {
851838
///
852839
/// # futures_lite::future::block_on(async {
853840
/// let mut listener = Async::<TcpListener>::bind(([127, 0, 0, 1], 0))?;
854-
/// let inner = unsafe { listener.get_mut() };
841+
/// let inner = listener.get_mut();
855842
/// # std::io::Result::Ok(()) });
856843
/// ```
857-
pub unsafe fn get_mut(&mut self) -> &mut T {
844+
pub fn get_mut(&mut self) -> &mut T {
858845
self.io.as_mut().unwrap()
859846
}
860847

@@ -1044,10 +1031,6 @@ impl<T> Async<T> {
10441031
///
10451032
/// The closure receives a mutable reference to the I/O handle.
10461033
///
1047-
/// # Safety
1048-
///
1049-
/// In the closure, the underlying I/O source must not be dropped.
1050-
///
10511034
/// # Examples
10521035
///
10531036
/// ```no_run
@@ -1058,10 +1041,10 @@ impl<T> Async<T> {
10581041
/// let mut listener = Async::<TcpListener>::bind(([127, 0, 0, 1], 0))?;
10591042
///
10601043
/// // Accept a new client asynchronously.
1061-
/// let (stream, addr) = unsafe { listener.read_with_mut(|l| l.accept()).await? };
1044+
/// let (stream, addr) = listener.read_with_mut(|l| l.accept()).await?;
10621045
/// # std::io::Result::Ok(()) });
10631046
/// ```
1064-
pub async unsafe fn read_with_mut<R>(
1047+
pub async fn read_with_mut<R>(
10651048
&mut self,
10661049
op: impl FnMut(&mut T) -> io::Result<R>,
10671050
) -> io::Result<R> {
@@ -1116,11 +1099,6 @@ impl<T> Async<T> {
11161099
/// [`io::ErrorKind::WouldBlock`]. In between iterations of the loop, it waits until the OS
11171100
/// sends a notification that the I/O handle is writable.
11181101
///
1119-
/// # Safety
1120-
///
1121-
/// The closure receives a mutable reference to the I/O handle. In the closure, the underlying
1122-
/// I/O source must not be dropped.
1123-
///
11241102
/// # Examples
11251103
///
11261104
/// ```no_run
@@ -1132,10 +1110,10 @@ impl<T> Async<T> {
11321110
/// socket.get_ref().connect("127.0.0.1:9000")?;
11331111
///
11341112
/// let msg = b"hello";
1135-
/// let len = unsafe { socket.write_with_mut(|s| s.send(msg)).await? };
1113+
/// let len = socket.write_with_mut(|s| s.send(msg)).await?;
11361114
/// # std::io::Result::Ok(()) });
11371115
/// ```
1138-
pub async unsafe fn write_with_mut<R>(
1116+
pub async fn write_with_mut<R>(
11391117
&mut self,
11401118
op: impl FnMut(&mut T) -> io::Result<R>,
11411119
) -> io::Result<R> {
@@ -1170,19 +1148,9 @@ impl<T> Drop for Async<T> {
11701148

11711149
/// Types whose I/O trait implementations do not drop the underlying I/O source.
11721150
///
1173-
/// The resource contained inside of the [`Async`] cannot be invalidated. This invalidation can
1174-
/// happen if the inner resource (the [`TcpStream`], [`UnixListener`] or other `T`) is moved out
1175-
/// and dropped before the [`Async`]. Because of this, functions that grant mutable access to
1176-
/// the inner type are unsafe, as there is no way to guarantee that the source won't be dropped
1177-
/// and a dangling handle won't be left behind.
1178-
///
1179-
/// Unfortunately this extends to implementations of [`Read`] and [`Write`]. Since methods on those
1180-
/// traits take `&mut`, there is no guarantee that the implementor of those traits won't move the
1181-
/// source out while the method is being run.
1182-
///
1183-
/// This trait is an antidote to this predicament. By implementing this trait, the user pledges
1184-
/// that using any I/O traits won't destroy the source. This way, [`Async`] can implement the
1185-
/// `async` version of these I/O traits, like [`AsyncRead`] and [`AsyncWrite`].
1151+
/// Previously, this constraint was used to ensure only certain I/O sources could
1152+
/// be used with [`Async`]. But this constraint no longer applies, so this trait is
1153+
/// now deprecated.
11861154
///
11871155
/// # Safety
11881156
///
@@ -1201,6 +1169,7 @@ impl<T> Drop for Async<T> {
12011169
///
12021170
/// [`AsyncRead`]: https://docs.rs/futures-io/latest/futures_io/trait.AsyncRead.html
12031171
/// [`AsyncWrite`]: https://docs.rs/futures-io/latest/futures_io/trait.AsyncWrite.html
1172+
#[deprecated = "this trait is now no longer required"]
12041173
pub unsafe trait IoSafe {}
12051174

12061175
/// Reference types can't be mutated.
@@ -1234,46 +1203,67 @@ pub unsafe trait IoSafe {}
12341203
///
12351204
/// We solve this problem by only calling `as_fd()` once to get the original source. Implementations
12361205
/// like this are considered buggy (but not unsound) and are thus not really supported by `async-io`.
1206+
#[allow(deprecated)]
12371207
unsafe impl<T: ?Sized> IoSafe for &T {}
12381208

12391209
// Can be implemented on top of libstd types.
1210+
#[allow(deprecated)]
12401211
unsafe impl IoSafe for std::fs::File {}
1212+
#[allow(deprecated)]
12411213
unsafe impl IoSafe for std::io::Stderr {}
1214+
#[allow(deprecated)]
12421215
unsafe impl IoSafe for std::io::Stdin {}
1216+
#[allow(deprecated)]
12431217
unsafe impl IoSafe for std::io::Stdout {}
1218+
#[allow(deprecated)]
12441219
unsafe impl IoSafe for std::io::StderrLock<'_> {}
1220+
#[allow(deprecated)]
12451221
unsafe impl IoSafe for std::io::StdinLock<'_> {}
1222+
#[allow(deprecated)]
12461223
unsafe impl IoSafe for std::io::StdoutLock<'_> {}
1224+
#[allow(deprecated)]
12471225
unsafe impl IoSafe for std::net::TcpStream {}
1226+
#[allow(deprecated)]
12481227
unsafe impl IoSafe for std::process::ChildStdin {}
1228+
#[allow(deprecated)]
12491229
unsafe impl IoSafe for std::process::ChildStdout {}
1230+
#[allow(deprecated)]
12501231
unsafe impl IoSafe for std::process::ChildStderr {}
12511232

12521233
#[cfg(unix)]
1234+
#[allow(deprecated)]
12531235
unsafe impl IoSafe for std::os::unix::net::UnixStream {}
12541236

12551237
// PipeReader & PipeWriter require std >= 1.87, our MSRV is 1.71, hence
12561238
// conditional on cfg()s, generated from build.rs
12571239
#[cfg(not(async_io_no_pipe))]
1240+
#[allow(deprecated, clippy::incompatible_msrv)]
12581241
unsafe impl IoSafe for std::io::PipeReader {}
12591242
#[cfg(not(async_io_no_pipe))]
1243+
#[allow(deprecated, clippy::incompatible_msrv)]
12601244
unsafe impl IoSafe for std::io::PipeWriter {}
12611245

1246+
#[allow(deprecated)]
12621247
unsafe impl<T: IoSafe + Read> IoSafe for std::io::BufReader<T> {}
1248+
#[allow(deprecated)]
12631249
unsafe impl<T: IoSafe + Write> IoSafe for std::io::BufWriter<T> {}
1250+
#[allow(deprecated)]
12641251
unsafe impl<T: IoSafe + Write> IoSafe for std::io::LineWriter<T> {}
1252+
#[allow(deprecated)]
12651253
unsafe impl<T: IoSafe + ?Sized> IoSafe for &mut T {}
1254+
#[allow(deprecated)]
12661255
unsafe impl<T: IoSafe + ?Sized> IoSafe for Box<T> {}
1256+
#[allow(deprecated)]
12671257
unsafe impl<T: Clone + IoSafe> IoSafe for std::borrow::Cow<'_, T> {}
12681258

1269-
impl<T: IoSafe + Read> AsyncRead for Async<T> {
1259+
impl<T: Read> AsyncRead for Async<T> {
12701260
fn poll_read(
12711261
mut self: Pin<&mut Self>,
12721262
cx: &mut Context<'_>,
12731263
buf: &mut [u8],
12741264
) -> Poll<io::Result<usize>> {
12751265
loop {
1276-
match unsafe { (*self).get_mut() }.read(buf) {
1266+
match (*self).get_mut().read(buf) {
12771267
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
12781268
res => return Poll::Ready(res),
12791269
}
@@ -1287,7 +1277,7 @@ impl<T: IoSafe + Read> AsyncRead for Async<T> {
12871277
bufs: &mut [IoSliceMut<'_>],
12881278
) -> Poll<io::Result<usize>> {
12891279
loop {
1290-
match unsafe { (*self).get_mut() }.read_vectored(bufs) {
1280+
match (*self).get_mut().read_vectored(bufs) {
12911281
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
12921282
res => return Poll::Ready(res),
12931283
}
@@ -1331,14 +1321,14 @@ where
13311321
}
13321322
}
13331323

1334-
impl<T: IoSafe + Write> AsyncWrite for Async<T> {
1324+
impl<T: Write> AsyncWrite for Async<T> {
13351325
fn poll_write(
13361326
mut self: Pin<&mut Self>,
13371327
cx: &mut Context<'_>,
13381328
buf: &[u8],
13391329
) -> Poll<io::Result<usize>> {
13401330
loop {
1341-
match unsafe { (*self).get_mut() }.write(buf) {
1331+
match (*self).get_mut().write(buf) {
13421332
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
13431333
res => return Poll::Ready(res),
13441334
}
@@ -1352,7 +1342,7 @@ impl<T: IoSafe + Write> AsyncWrite for Async<T> {
13521342
bufs: &[IoSlice<'_>],
13531343
) -> Poll<io::Result<usize>> {
13541344
loop {
1355-
match unsafe { (*self).get_mut() }.write_vectored(bufs) {
1345+
match (*self).get_mut().write_vectored(bufs) {
13561346
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
13571347
res => return Poll::Ready(res),
13581348
}
@@ -1362,7 +1352,7 @@ impl<T: IoSafe + Write> AsyncWrite for Async<T> {
13621352

13631353
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
13641354
loop {
1365-
match unsafe { (*self).get_mut() }.flush() {
1355+
match (*self).get_mut().flush() {
13661356
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
13671357
res => return Poll::Ready(res),
13681358
}

src/os/kqueue.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,6 @@ impl<T> Filter<T> {
111111

112112
/// Gets a mutable reference to the underlying [`Queueable`] object.
113113
///
114-
/// Unlike in [`Async`], this method is safe to call, since dropping the [`Filter`] will
115-
/// not cause any undefined behavior.
116-
///
117114
/// # Examples
118115
///
119116
/// ```
@@ -126,7 +123,7 @@ impl<T> Filter<T> {
126123
/// # });
127124
/// ```
128125
pub fn get_mut(&mut self) -> &mut T {
129-
unsafe { self.0.get_mut() }
126+
self.0.get_mut()
130127
}
131128

132129
/// Unwraps the inner [`Queueable`] object.

src/os/windows.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,7 @@ impl<T: AsHandle> Waitable<T> {
6868
/// ```
6969
pub fn new(handle: T) -> Result<Self> {
7070
Ok(Self(Async {
71-
source: Reactor::get()
72-
.insert_io(unsafe { Registration::new_waitable(handle.as_handle()) })?,
71+
source: Reactor::get().insert_io(Registration::new_waitable(handle.as_handle()))?,
7372
io: Some(handle),
7473
}))
7574
}

src/reactor/kqueue.rs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,15 @@ impl fmt::Debug for Registration {
4343

4444
impl Registration {
4545
/// Add this file descriptor into the reactor.
46-
///
47-
/// # Safety
48-
///
49-
/// The provided file descriptor must be valid and not be closed while this object is alive.
50-
pub(crate) unsafe fn new(f: BorrowedFd<'_>) -> Self {
46+
pub(crate) fn new(f: BorrowedFd<'_>) -> Self {
5147
Self::Fd(f.as_raw_fd())
5248
}
5349

5450
/// Registers the object into the reactor.
5551
#[inline]
5652
pub(crate) fn add(&self, poller: &Poller, token: usize) -> Result<()> {
5753
match self {
58-
Self::Fd(raw) => {
59-
// SAFETY: This object's existence validates the invariants of Poller::add
60-
unsafe { poller.add(*raw, Event::none(token)) }
61-
}
54+
Self::Fd(raw) => poller.add(*raw, Event::none(token)),
6255
Self::Signal(signal) => {
6356
poller.add_filter(PollSignal(signal.0), token, PollMode::Oneshot)
6457
}

src/reactor/unix.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,14 @@ impl fmt::Debug for Registration {
2626

2727
impl Registration {
2828
/// Add this file descriptor into the reactor.
29-
///
30-
/// # Safety
31-
///
32-
/// The provided file descriptor must be valid and not be closed while this object is alive.
33-
pub(crate) unsafe fn new(f: BorrowedFd<'_>) -> Self {
29+
pub(crate) fn new(f: BorrowedFd<'_>) -> Self {
3430
Self { raw: f.as_raw_fd() }
3531
}
3632

3733
/// Registers the object into the reactor.
3834
#[inline]
3935
pub(crate) fn add(&self, poller: &Poller, token: usize) -> Result<()> {
40-
// SAFETY: This object's existence validates the invariants of Poller::add
41-
unsafe { poller.add(self.raw, Event::none(token)) }
36+
poller.add(self.raw, Event::none(token))
4237
}
4338

4439
/// Re-registers the object into the reactor.

0 commit comments

Comments
 (0)