Skip to content

Commit 88b5171

Browse files
authored
Guarantee that curl_multi_remove_handle is called (#423)
Add a drop handler that guarantees that `curl_multi_remove_handle` is always called on easy handles added to a multi handle before the easy handle is dropped. Based on curl/curl#7982, we should not be allowing users to drop a previously attached handle without first calling `curl_multi_remove_handle`. In curl versions between 7.77 and 7.80 this could cause a crash under certain circumstances. While this will likely be fixed in the next curl version, it is still recommended to not allow this, plus we must still handle this case for users who may not have updated to a patched libcurl. I'm not totally happy with how this is implemented as it adds an `Arc::clone` call every time an easy handle is added to a multi handle, but I couldn't think of a better way of guaranteeing this behavior without breaking API changes. Fixes #421.
1 parent 7c111ba commit 88b5171

File tree

1 file changed

+49
-14
lines changed

1 file changed

+49
-14
lines changed

src/multi.rs

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ pub struct Message<'multi> {
5858
/// be used via `perform`. This handle is also used to remove the easy handle
5959
/// from the multi handle when desired.
6060
pub struct EasyHandle {
61+
// Safety: This *must* be before `easy` as it must be dropped first.
62+
guard: DetachGuard,
6163
easy: Easy,
6264
// This is now effectively bound to a `Multi`, so it is no longer sendable.
6365
_marker: marker::PhantomData<&'static Multi>,
@@ -69,11 +71,20 @@ pub struct EasyHandle {
6971
/// be used via `perform`. This handle is also used to remove the easy handle
7072
/// from the multi handle when desired.
7173
pub struct Easy2Handle<H> {
74+
// Safety: This *must* be before `easy` as it must be dropped first.
75+
guard: DetachGuard,
7276
easy: Easy2<H>,
7377
// This is now effectively bound to a `Multi`, so it is no longer sendable.
7478
_marker: marker::PhantomData<&'static Multi>,
7579
}
7680

81+
/// A guard struct which guarantees that `curl_multi_remove_handle` will be
82+
/// called on an easy handle, either manually or on drop.
83+
struct DetachGuard {
84+
multi: Arc<RawMulti>,
85+
easy: *mut curl_sys::CURL,
86+
}
87+
7788
/// Notification of the events that have happened on a socket.
7889
///
7990
/// This type is passed as an argument to the `action` method on a multi handle
@@ -400,6 +411,10 @@ impl Multi {
400411
cvt(curl_sys::curl_multi_add_handle(self.raw.handle, easy.raw()))?;
401412
}
402413
Ok(EasyHandle {
414+
guard: DetachGuard {
415+
multi: self.raw.clone(),
416+
easy: easy.raw(),
417+
},
403418
easy,
404419
_marker: marker::PhantomData,
405420
})
@@ -411,6 +426,10 @@ impl Multi {
411426
cvt(curl_sys::curl_multi_add_handle(self.raw.handle, easy.raw()))?;
412427
}
413428
Ok(Easy2Handle {
429+
guard: DetachGuard {
430+
multi: self.raw.clone(),
431+
easy: easy.raw(),
432+
},
414433
easy,
415434
_marker: marker::PhantomData,
416435
})
@@ -427,24 +446,14 @@ impl Multi {
427446
/// Removing an easy handle while being used is perfectly legal and will
428447
/// effectively halt the transfer in progress involving that easy handle.
429448
/// All other easy handles and transfers will remain unaffected.
430-
pub fn remove(&self, easy: EasyHandle) -> Result<Easy, MultiError> {
431-
unsafe {
432-
cvt(curl_sys::curl_multi_remove_handle(
433-
self.raw.handle,
434-
easy.easy.raw(),
435-
))?;
436-
}
449+
pub fn remove(&self, mut easy: EasyHandle) -> Result<Easy, MultiError> {
450+
easy.guard.detach()?;
437451
Ok(easy.easy)
438452
}
439453

440454
/// Same as `remove`, but for `Easy2Handle`.
441-
pub fn remove2<H>(&self, easy: Easy2Handle<H>) -> Result<Easy2<H>, MultiError> {
442-
unsafe {
443-
cvt(curl_sys::curl_multi_remove_handle(
444-
self.raw.handle,
445-
easy.easy.raw(),
446-
))?;
447-
}
455+
pub fn remove2<H>(&self, mut easy: Easy2Handle<H>) -> Result<Easy2<H>, MultiError> {
456+
easy.guard.detach()?;
448457
Ok(easy.easy)
449458
}
450459

@@ -1018,6 +1027,32 @@ impl<H: fmt::Debug> fmt::Debug for Easy2Handle<H> {
10181027
}
10191028
}
10201029

1030+
impl DetachGuard {
1031+
/// Detach the referenced easy handle from its multi handle manually.
1032+
/// Subsequent calls to this method will have no effect.
1033+
fn detach(&mut self) -> Result<(), MultiError> {
1034+
if !self.easy.is_null() {
1035+
unsafe {
1036+
cvt(curl_sys::curl_multi_remove_handle(
1037+
self.multi.handle,
1038+
self.easy,
1039+
))?
1040+
}
1041+
1042+
// Set easy to null to signify that the handle was removed.
1043+
self.easy = ptr::null_mut();
1044+
}
1045+
1046+
Ok(())
1047+
}
1048+
}
1049+
1050+
impl Drop for DetachGuard {
1051+
fn drop(&mut self) {
1052+
let _ = self.detach();
1053+
}
1054+
}
1055+
10211056
impl<'multi> Message<'multi> {
10221057
/// If this message indicates that a transfer has finished, returns the
10231058
/// result of the transfer in `Some`.

0 commit comments

Comments
 (0)