Skip to content

Commit b39d15a

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 b39d15a

File tree

6 files changed

+56
-82
lines changed

6 files changed

+56
-82
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: 39 additions & 46 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)?,
@@ -839,10 +833,6 @@ impl<T> Async<T> {
839833

840834
/// Gets a mutable reference to the inner I/O handle.
841835
///
842-
/// # Safety
843-
///
844-
/// The underlying I/O source must not be dropped using this function.
845-
///
846836
/// # Examples
847837
///
848838
/// ```
@@ -851,10 +841,10 @@ impl<T> Async<T> {
851841
///
852842
/// # futures_lite::future::block_on(async {
853843
/// let mut listener = Async::<TcpListener>::bind(([127, 0, 0, 1], 0))?;
854-
/// let inner = unsafe { listener.get_mut() };
844+
/// let inner = listener.get_mut();
855845
/// # std::io::Result::Ok(()) });
856846
/// ```
857-
pub unsafe fn get_mut(&mut self) -> &mut T {
847+
pub fn get_mut(&mut self) -> &mut T {
858848
self.io.as_mut().unwrap()
859849
}
860850

@@ -1044,10 +1034,6 @@ impl<T> Async<T> {
10441034
///
10451035
/// The closure receives a mutable reference to the I/O handle.
10461036
///
1047-
/// # Safety
1048-
///
1049-
/// In the closure, the underlying I/O source must not be dropped.
1050-
///
10511037
/// # Examples
10521038
///
10531039
/// ```no_run
@@ -1058,10 +1044,10 @@ impl<T> Async<T> {
10581044
/// let mut listener = Async::<TcpListener>::bind(([127, 0, 0, 1], 0))?;
10591045
///
10601046
/// // Accept a new client asynchronously.
1061-
/// let (stream, addr) = unsafe { listener.read_with_mut(|l| l.accept()).await? };
1047+
/// let (stream, addr) = listener.read_with_mut(|l| l.accept()).await?;
10621048
/// # std::io::Result::Ok(()) });
10631049
/// ```
1064-
pub async unsafe fn read_with_mut<R>(
1050+
pub async fn read_with_mut<R>(
10651051
&mut self,
10661052
op: impl FnMut(&mut T) -> io::Result<R>,
10671053
) -> io::Result<R> {
@@ -1116,11 +1102,6 @@ impl<T> Async<T> {
11161102
/// [`io::ErrorKind::WouldBlock`]. In between iterations of the loop, it waits until the OS
11171103
/// sends a notification that the I/O handle is writable.
11181104
///
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-
///
11241105
/// # Examples
11251106
///
11261107
/// ```no_run
@@ -1132,10 +1113,10 @@ impl<T> Async<T> {
11321113
/// socket.get_ref().connect("127.0.0.1:9000")?;
11331114
///
11341115
/// let msg = b"hello";
1135-
/// let len = unsafe { socket.write_with_mut(|s| s.send(msg)).await? };
1116+
/// let len = socket.write_with_mut(|s| s.send(msg)).await?;
11361117
/// # std::io::Result::Ok(()) });
11371118
/// ```
1138-
pub async unsafe fn write_with_mut<R>(
1119+
pub async fn write_with_mut<R>(
11391120
&mut self,
11401121
op: impl FnMut(&mut T) -> io::Result<R>,
11411122
) -> io::Result<R> {
@@ -1170,19 +1151,9 @@ impl<T> Drop for Async<T> {
11701151

11711152
/// Types whose I/O trait implementations do not drop the underlying I/O source.
11721153
///
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`].
1154+
/// Previously, this constraint was used to ensure only certain I/O sources could
1155+
/// be used with [`Async`]. But this constraint no longer applies, so this trait is
1156+
/// now deprecated.
11861157
///
11871158
/// # Safety
11881159
///
@@ -1201,6 +1172,7 @@ impl<T> Drop for Async<T> {
12011172
///
12021173
/// [`AsyncRead`]: https://docs.rs/futures-io/latest/futures_io/trait.AsyncRead.html
12031174
/// [`AsyncWrite`]: https://docs.rs/futures-io/latest/futures_io/trait.AsyncWrite.html
1175+
#[deprecated = "this trait is now no longer required"]
12041176
pub unsafe trait IoSafe {}
12051177

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

12391212
// Can be implemented on top of libstd types.
1213+
#[allow(deprecated)]
12401214
unsafe impl IoSafe for std::fs::File {}
1215+
#[allow(deprecated)]
12411216
unsafe impl IoSafe for std::io::Stderr {}
1217+
#[allow(deprecated)]
12421218
unsafe impl IoSafe for std::io::Stdin {}
1219+
#[allow(deprecated)]
12431220
unsafe impl IoSafe for std::io::Stdout {}
1221+
#[allow(deprecated)]
12441222
unsafe impl IoSafe for std::io::StderrLock<'_> {}
1223+
#[allow(deprecated)]
12451224
unsafe impl IoSafe for std::io::StdinLock<'_> {}
1225+
#[allow(deprecated)]
12461226
unsafe impl IoSafe for std::io::StdoutLock<'_> {}
1227+
#[allow(deprecated)]
12471228
unsafe impl IoSafe for std::net::TcpStream {}
1229+
#[allow(deprecated)]
12481230
unsafe impl IoSafe for std::process::ChildStdin {}
1231+
#[allow(deprecated)]
12491232
unsafe impl IoSafe for std::process::ChildStdout {}
1233+
#[allow(deprecated)]
12501234
unsafe impl IoSafe for std::process::ChildStderr {}
12511235

12521236
#[cfg(unix)]
1237+
#[allow(deprecated)]
12531238
unsafe impl IoSafe for std::os::unix::net::UnixStream {}
12541239

12551240
// PipeReader & PipeWriter require std >= 1.87, our MSRV is 1.71, hence
12561241
// conditional on cfg()s, generated from build.rs
12571242
#[cfg(not(async_io_no_pipe))]
1243+
#[allow(deprecated)]
12581244
unsafe impl IoSafe for std::io::PipeReader {}
12591245
#[cfg(not(async_io_no_pipe))]
1246+
#[allow(deprecated)]
12601247
unsafe impl IoSafe for std::io::PipeWriter {}
12611248

1249+
#[allow(deprecated)]
12621250
unsafe impl<T: IoSafe + Read> IoSafe for std::io::BufReader<T> {}
1251+
#[allow(deprecated)]
12631252
unsafe impl<T: IoSafe + Write> IoSafe for std::io::BufWriter<T> {}
1253+
#[allow(deprecated)]
12641254
unsafe impl<T: IoSafe + Write> IoSafe for std::io::LineWriter<T> {}
1255+
#[allow(deprecated)]
12651256
unsafe impl<T: IoSafe + ?Sized> IoSafe for &mut T {}
1257+
#[allow(deprecated)]
12661258
unsafe impl<T: IoSafe + ?Sized> IoSafe for Box<T> {}
1259+
#[allow(deprecated)]
12671260
unsafe impl<T: Clone + IoSafe> IoSafe for std::borrow::Cow<'_, T> {}
12681261

1269-
impl<T: IoSafe + Read> AsyncRead for Async<T> {
1262+
impl<T: Read> AsyncRead for Async<T> {
12701263
fn poll_read(
12711264
mut self: Pin<&mut Self>,
12721265
cx: &mut Context<'_>,
12731266
buf: &mut [u8],
12741267
) -> Poll<io::Result<usize>> {
12751268
loop {
1276-
match unsafe { (*self).get_mut() }.read(buf) {
1269+
match (*self).get_mut().read(buf) {
12771270
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
12781271
res => return Poll::Ready(res),
12791272
}
@@ -1287,7 +1280,7 @@ impl<T: IoSafe + Read> AsyncRead for Async<T> {
12871280
bufs: &mut [IoSliceMut<'_>],
12881281
) -> Poll<io::Result<usize>> {
12891282
loop {
1290-
match unsafe { (*self).get_mut() }.read_vectored(bufs) {
1283+
match (*self).get_mut().read_vectored(bufs) {
12911284
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
12921285
res => return Poll::Ready(res),
12931286
}
@@ -1331,14 +1324,14 @@ where
13311324
}
13321325
}
13331326

1334-
impl<T: IoSafe + Write> AsyncWrite for Async<T> {
1327+
impl<T: Write> AsyncWrite for Async<T> {
13351328
fn poll_write(
13361329
mut self: Pin<&mut Self>,
13371330
cx: &mut Context<'_>,
13381331
buf: &[u8],
13391332
) -> Poll<io::Result<usize>> {
13401333
loop {
1341-
match unsafe { (*self).get_mut() }.write(buf) {
1334+
match (*self).get_mut().write(buf) {
13421335
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
13431336
res => return Poll::Ready(res),
13441337
}
@@ -1352,7 +1345,7 @@ impl<T: IoSafe + Write> AsyncWrite for Async<T> {
13521345
bufs: &[IoSlice<'_>],
13531346
) -> Poll<io::Result<usize>> {
13541347
loop {
1355-
match unsafe { (*self).get_mut() }.write_vectored(bufs) {
1348+
match (*self).get_mut().write_vectored(bufs) {
13561349
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
13571350
res => return Poll::Ready(res),
13581351
}
@@ -1362,7 +1355,7 @@ impl<T: IoSafe + Write> AsyncWrite for Async<T> {
13621355

13631356
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
13641357
loop {
1365-
match unsafe { (*self).get_mut() }.flush() {
1358+
match (*self).get_mut().flush() {
13661359
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
13671360
res => return Poll::Ready(res),
13681361
}

src/reactor/kqueue.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,7 @@ 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

@@ -56,8 +52,7 @@ impl Registration {
5652
pub(crate) fn add(&self, poller: &Poller, token: usize) -> Result<()> {
5753
match self {
5854
Self::Fd(raw) => {
59-
// SAFETY: This object's existence validates the invariants of Poller::add
60-
unsafe { poller.add(*raw, Event::none(token)) }
55+
poller.add(*raw, Event::none(token))
6156
}
6257
Self::Signal(signal) => {
6358
poller.add_filter(PollSignal(signal.0), token, PollMode::Oneshot)

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.

src/reactor/windows.rs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,7 @@ impl fmt::Debug for Registration {
4242

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

@@ -55,20 +51,17 @@ impl Registration {
5551
/// # Safety
5652
///
5753
/// The provided handle must be valid and not be closed while this object is alive.
58-
pub(crate) unsafe fn new_waitable(f: BorrowedHandle<'_>) -> Self {
54+
pub(crate) fn new_waitable(f: BorrowedHandle<'_>) -> Self {
5955
Self::Handle(f.as_raw_handle())
6056
}
6157

6258
/// Registers the object into the reactor.
6359
#[inline]
6460
pub(crate) fn add(&self, poller: &Poller, token: usize) -> Result<()> {
65-
// SAFETY: This object's existence validates the invariants of Poller::add
66-
unsafe {
67-
match self {
68-
Self::Socket(raw) => poller.add(*raw, Event::none(token)),
69-
Self::Handle(handle) => {
70-
poller.add_waitable(*handle, Event::none(token), PollMode::Oneshot)
71-
}
61+
match self {
62+
Self::Socket(raw) => poller.add(*raw, Event::none(token)),
63+
Self::Handle(handle) => {
64+
poller.add_waitable(*handle, Event::none(token), PollMode::Oneshot)
7265
}
7366
}
7467
}

0 commit comments

Comments
 (0)