1//! Multi - initiating multiple requests simultaneously
2
3use std::fmt;
4use std::marker;
5use std::ptr;
6use std::sync::Arc;
7use std::time::Duration;
8
9use libc::{c_char, c_int, c_long, c_short, c_void};
10
11#[cfg(unix)]
12use libc::{pollfd, POLLIN, POLLOUT, POLLPRI};
13
14use crate::easy::{Easy, Easy2, List};
15use crate::panic;
16use crate::{Error, MultiError};
17
18/// A multi handle for initiating multiple connections simultaneously.
19///
20/// This structure corresponds to `CURLM` in libcurl and provides the ability to
21/// have multiple transfers in flight simultaneously. This handle is then used
22/// to manage each transfer. The main purpose of a `CURLM` is for the
23/// *application* to drive the I/O rather than libcurl itself doing all the
24/// blocking. Methods like `action` allow the application to inform libcurl of
25/// when events have happened.
26///
27/// Lots more documentation can be found on the libcurl [multi tutorial] where
28/// the APIs correspond pretty closely with this crate.
29///
30/// [multi tutorial]: https://curl.haxx.se/libcurl/c/libcurl-multi.html
31pub struct Multi {
32 raw: Arc<RawMulti>,
33 data: Box<MultiData>,
34}
35
36#[derive(Debug)]
37struct RawMulti {
38 handle: *mut curl_sys::CURLM,
39}
40
41struct MultiData {
42 socket: Box<dyn FnMut(Socket, SocketEvents, usize) + Send>,
43 timer: Box<dyn FnMut(Option<Duration>) -> bool + Send>,
44}
45
46/// Message from the `messages` function of a multi handle.
47///
48/// Currently only indicates whether a transfer is done.
49pub struct Message<'multi> {
50 ptr: *mut curl_sys::CURLMsg,
51 _multi: &'multi Multi,
52}
53
54/// Wrapper around an easy handle while it's owned by a multi handle.
55///
56/// Once an easy handle has been added to a multi handle then it can no longer
57/// be used via `perform`. This handle is also used to remove the easy handle
58/// from the multi handle when desired.
59pub struct EasyHandle {
60 // Safety: This *must* be before `easy` as it must be dropped first.
61 guard: DetachGuard,
62 easy: Easy,
63 // This is now effectively bound to a `Multi`, so it is no longer sendable.
64 _marker: marker::PhantomData<&'static Multi>,
65}
66
67/// Wrapper around an easy handle while it's owned by a multi handle.
68///
69/// Once an easy handle has been added to a multi handle then it can no longer
70/// be used via `perform`. This handle is also used to remove the easy handle
71/// from the multi handle when desired.
72pub struct Easy2Handle<H> {
73 // Safety: This *must* be before `easy` as it must be dropped first.
74 guard: DetachGuard,
75 easy: Easy2<H>,
76 // This is now effectively bound to a `Multi`, so it is no longer sendable.
77 _marker: marker::PhantomData<&'static Multi>,
78}
79
80/// A guard struct which guarantees that `curl_multi_remove_handle` will be
81/// called on an easy handle, either manually or on drop.
82struct DetachGuard {
83 multi: Arc<RawMulti>,
84 easy: *mut curl_sys::CURL,
85}
86
87/// Notification of the events that have happened on a socket.
88///
89/// This type is passed as an argument to the `action` method on a multi handle
90/// to indicate what events have occurred on a socket.
91pub struct Events {
92 bits: c_int,
93}
94
95/// Notification of events that are requested on a socket.
96///
97/// This type is yielded to the `socket_function` callback to indicate what
98/// events are requested on a socket.
99pub struct SocketEvents {
100 bits: c_int,
101}
102
103/// Raw underlying socket type that the multi handles use
104pub type Socket = curl_sys::curl_socket_t;
105
106/// File descriptor to wait on for use with the `wait` method on a multi handle.
107pub struct WaitFd {
108 inner: curl_sys::curl_waitfd,
109}
110
111/// A handle that can be used to wake up a thread that's blocked in [Multi::poll].
112/// The handle can be passed to and used from any thread.
113#[cfg(feature = "poll_7_68_0")]
114#[derive(Debug, Clone)]
115pub struct MultiWaker {
116 raw: std::sync::Weak<RawMulti>,
117}
118
119#[cfg(feature = "poll_7_68_0")]
120unsafe impl Send for MultiWaker {}
121
122#[cfg(feature = "poll_7_68_0")]
123unsafe impl Sync for MultiWaker {}
124
125impl Multi {
126 /// Creates a new multi session through which multiple HTTP transfers can be
127 /// initiated.
128 pub fn new() -> Multi {
129 unsafe {
130 crate::init();
131 let ptr = curl_sys::curl_multi_init();
132 assert!(!ptr.is_null());
133 Multi {
134 raw: Arc::new(RawMulti { handle: ptr }),
135 data: Box::new(MultiData {
136 socket: Box::new(|_, _, _| ()),
137 timer: Box::new(|_| true),
138 }),
139 }
140 }
141 }
142
143 /// Set the callback informed about what to wait for
144 ///
145 /// When the `action` function runs, it informs the application about
146 /// updates in the socket (file descriptor) status by doing none, one, or
147 /// multiple calls to the socket callback. The callback gets status updates
148 /// with changes since the previous time the callback was called. See
149 /// `action` for more details on how the callback is used and should work.
150 ///
151 /// The `SocketEvents` parameter informs the callback on the status of the
152 /// given socket, and the methods on that type can be used to learn about
153 /// what's going on with the socket.
154 ///
155 /// The third `usize` parameter is a custom value set by the `assign` method
156 /// below.
157 pub fn socket_function<F>(&mut self, f: F) -> Result<(), MultiError>
158 where
159 F: FnMut(Socket, SocketEvents, usize) + Send + 'static,
160 {
161 self._socket_function(Box::new(f))
162 }
163
164 fn _socket_function(
165 &mut self,
166 f: Box<dyn FnMut(Socket, SocketEvents, usize) + Send>,
167 ) -> Result<(), MultiError> {
168 self.data.socket = f;
169 let cb: curl_sys::curl_socket_callback = cb;
170 self.setopt_ptr(
171 curl_sys::CURLMOPT_SOCKETFUNCTION,
172 cb as usize as *const c_char,
173 )?;
174 let ptr = &*self.data as *const _;
175 self.setopt_ptr(curl_sys::CURLMOPT_SOCKETDATA, ptr as *const c_char)?;
176 return Ok(());
177
178 // TODO: figure out how to expose `_easy`
179 extern "C" fn cb(
180 _easy: *mut curl_sys::CURL,
181 socket: curl_sys::curl_socket_t,
182 what: c_int,
183 userptr: *mut c_void,
184 socketp: *mut c_void,
185 ) -> c_int {
186 panic::catch(|| unsafe {
187 let f = &mut (*(userptr as *mut MultiData)).socket;
188 f(socket, SocketEvents { bits: what }, socketp as usize)
189 });
190 0
191 }
192 }
193
194 /// Set data to associate with an internal socket
195 ///
196 /// This function creates an association in the multi handle between the
197 /// given socket and a private token of the application. This is designed
198 /// for `action` uses.
199 ///
200 /// When set, the token will be passed to all future socket callbacks for
201 /// the specified socket.
202 ///
203 /// If the given socket isn't already in use by libcurl, this function will
204 /// return an error.
205 ///
206 /// libcurl only keeps one single token associated with a socket, so
207 /// calling this function several times for the same socket will make the
208 /// last set token get used.
209 ///
210 /// The idea here being that this association (socket to token) is something
211 /// that just about every application that uses this API will need and then
212 /// libcurl can just as well do it since it already has an internal hash
213 /// table lookup for this.
214 ///
215 /// # Typical Usage
216 ///
217 /// In a typical application you allocate a struct or at least use some kind
218 /// of semi-dynamic data for each socket that we must wait for action on
219 /// when using the `action` approach.
220 ///
221 /// When our socket-callback gets called by libcurl and we get to know about
222 /// yet another socket to wait for, we can use `assign` to point out the
223 /// particular data so that when we get updates about this same socket
224 /// again, we don't have to find the struct associated with this socket by
225 /// ourselves.
226 pub fn assign(&self, socket: Socket, token: usize) -> Result<(), MultiError> {
227 unsafe {
228 cvt(curl_sys::curl_multi_assign(
229 self.raw.handle,
230 socket,
231 token as *mut _,
232 ))?;
233 Ok(())
234 }
235 }
236
237 /// Set callback to receive timeout values
238 ///
239 /// Certain features, such as timeouts and retries, require you to call
240 /// libcurl even when there is no activity on the file descriptors.
241 ///
242 /// Your callback function should install a non-repeating timer with the
243 /// interval specified. Each time that timer fires, call either `action` or
244 /// `perform`, depending on which interface you use.
245 ///
246 /// A timeout value of `None` means you should delete your timer.
247 ///
248 /// A timeout value of 0 means you should call `action` or `perform` (once)
249 /// as soon as possible.
250 ///
251 /// This callback will only be called when the timeout changes.
252 ///
253 /// The timer callback should return `true` on success, and `false` on
254 /// error. This callback can be used instead of, or in addition to,
255 /// `get_timeout`.
256 pub fn timer_function<F>(&mut self, f: F) -> Result<(), MultiError>
257 where
258 F: FnMut(Option<Duration>) -> bool + Send + 'static,
259 {
260 self._timer_function(Box::new(f))
261 }
262
263 fn _timer_function(
264 &mut self,
265 f: Box<dyn FnMut(Option<Duration>) -> bool + Send>,
266 ) -> Result<(), MultiError> {
267 self.data.timer = f;
268 let cb: curl_sys::curl_multi_timer_callback = cb;
269 self.setopt_ptr(
270 curl_sys::CURLMOPT_TIMERFUNCTION,
271 cb as usize as *const c_char,
272 )?;
273 let ptr = &*self.data as *const _;
274 self.setopt_ptr(curl_sys::CURLMOPT_TIMERDATA, ptr as *const c_char)?;
275 return Ok(());
276
277 // TODO: figure out how to expose `_multi`
278 extern "C" fn cb(
279 _multi: *mut curl_sys::CURLM,
280 timeout_ms: c_long,
281 user: *mut c_void,
282 ) -> c_int {
283 let keep_going = panic::catch(|| unsafe {
284 let f = &mut (*(user as *mut MultiData)).timer;
285 if timeout_ms == -1 {
286 f(None)
287 } else {
288 f(Some(Duration::from_millis(timeout_ms as u64)))
289 }
290 })
291 .unwrap_or(false);
292 if keep_going {
293 0
294 } else {
295 -1
296 }
297 }
298 }
299
300 /// Enable or disable HTTP pipelining and multiplexing.
301 ///
302 /// When http_1 is true, enable HTTP/1.1 pipelining, which means that if
303 /// you add a second request that can use an already existing connection,
304 /// the second request will be "piped" on the same connection rather than
305 /// being executed in parallel.
306 ///
307 /// When multiplex is true, enable HTTP/2 multiplexing, which means that
308 /// follow-up requests can re-use an existing connection and send the new
309 /// request multiplexed over that at the same time as other transfers are
310 /// already using that single connection.
311 pub fn pipelining(&mut self, http_1: bool, multiplex: bool) -> Result<(), MultiError> {
312 let bitmask = if http_1 { curl_sys::CURLPIPE_HTTP1 } else { 0 }
313 | if multiplex {
314 curl_sys::CURLPIPE_MULTIPLEX
315 } else {
316 0
317 };
318 self.setopt_long(curl_sys::CURLMOPT_PIPELINING, bitmask)
319 }
320
321 /// Sets the max number of connections to a single host.
322 ///
323 /// Pass a long to indicate the max number of simultaneously open connections
324 /// to a single host (a host being the same as a host name + port number pair).
325 /// For each new session to a host, libcurl will open up a new connection up to the
326 /// limit set by the provided value. When the limit is reached, the sessions will
327 /// be pending until a connection becomes available. If pipelining is enabled,
328 /// libcurl will try to pipeline if the host is capable of it.
329 pub fn set_max_host_connections(&mut self, val: usize) -> Result<(), MultiError> {
330 self.setopt_long(curl_sys::CURLMOPT_MAX_HOST_CONNECTIONS, val as c_long)
331 }
332
333 /// Sets the max simultaneously open connections.
334 ///
335 /// The set number will be used as the maximum number of simultaneously open
336 /// connections in total using this multi handle. For each new session,
337 /// libcurl will open a new connection up to the limit set by the provided
338 /// value. When the limit is reached, the sessions will be pending until
339 /// there are available connections. If pipelining is enabled, libcurl will
340 /// try to pipeline or use multiplexing if the host is capable of it.
341 pub fn set_max_total_connections(&mut self, val: usize) -> Result<(), MultiError> {
342 self.setopt_long(curl_sys::CURLMOPT_MAX_TOTAL_CONNECTIONS, val as c_long)
343 }
344
345 /// Set size of connection cache.
346 ///
347 /// The set number will be used as the maximum amount of simultaneously open
348 /// connections that libcurl may keep in its connection cache after
349 /// completed use. By default libcurl will enlarge the size for each added
350 /// easy handle to make it fit 4 times the number of added easy handles.
351 ///
352 /// By setting this option, you can prevent the cache size from growing
353 /// beyond the limit set by you.
354 ///
355 /// When the cache is full, curl closes the oldest one in the cache to
356 /// prevent the number of open connections from increasing.
357 ///
358 /// See [`set_max_total_connections`](#method.set_max_total_connections) for
359 /// limiting the number of active connections.
360 pub fn set_max_connects(&mut self, val: usize) -> Result<(), MultiError> {
361 self.setopt_long(curl_sys::CURLMOPT_MAXCONNECTS, val as c_long)
362 }
363
364 /// Sets the pipeline length.
365 ///
366 /// This sets the max number that will be used as the maximum amount of
367 /// outstanding requests in an HTTP/1.1 pipelined connection. This option
368 /// is only used for HTTP/1.1 pipelining, and not HTTP/2 multiplexing.
369 pub fn set_pipeline_length(&mut self, val: usize) -> Result<(), MultiError> {
370 self.setopt_long(curl_sys::CURLMOPT_MAX_PIPELINE_LENGTH, val as c_long)
371 }
372
373 /// Sets the number of max concurrent streams for http2.
374 ///
375 /// This sets the max number will be used as the maximum number of
376 /// concurrent streams for a connections that libcurl should support on
377 /// connections done using HTTP/2. Defaults to 100.
378 pub fn set_max_concurrent_streams(&mut self, val: usize) -> Result<(), MultiError> {
379 self.setopt_long(curl_sys::CURLMOPT_MAX_CONCURRENT_STREAMS, val as c_long)
380 }
381
382 fn setopt_long(&mut self, opt: curl_sys::CURLMoption, val: c_long) -> Result<(), MultiError> {
383 unsafe { cvt(curl_sys::curl_multi_setopt(self.raw.handle, opt, val)) }
384 }
385
386 fn setopt_ptr(
387 &mut self,
388 opt: curl_sys::CURLMoption,
389 val: *const c_char,
390 ) -> Result<(), MultiError> {
391 unsafe { cvt(curl_sys::curl_multi_setopt(self.raw.handle, opt, val)) }
392 }
393
394 /// Add an easy handle to a multi session
395 ///
396 /// Adds a standard easy handle to the multi stack. This function call will
397 /// make this multi handle control the specified easy handle.
398 ///
399 /// When an easy interface is added to a multi handle, it will use a shared
400 /// connection cache owned by the multi handle. Removing and adding new easy
401 /// handles will not affect the pool of connections or the ability to do
402 /// connection re-use.
403 ///
404 /// If you have `timer_function` set in the multi handle (and you really
405 /// should if you're working event-based with `action` and friends), that
406 /// callback will be called from within this function to ask for an updated
407 /// timer so that your main event loop will get the activity on this handle
408 /// to get started.
409 ///
410 /// The easy handle will remain added to the multi handle until you remove
411 /// it again with `remove` on the returned handle - even when a transfer
412 /// with that specific easy handle is completed.
413 pub fn add(&self, mut easy: Easy) -> Result<EasyHandle, MultiError> {
414 // Clear any configuration set by previous transfers because we're
415 // moving this into a `Send+'static` situation now basically.
416 easy.transfer();
417
418 unsafe {
419 cvt(curl_sys::curl_multi_add_handle(self.raw.handle, easy.raw()))?;
420 }
421 Ok(EasyHandle {
422 guard: DetachGuard {
423 multi: self.raw.clone(),
424 easy: easy.raw(),
425 },
426 easy,
427 _marker: marker::PhantomData,
428 })
429 }
430
431 /// Same as `add`, but works with the `Easy2` type.
432 pub fn add2<H>(&self, easy: Easy2<H>) -> Result<Easy2Handle<H>, MultiError> {
433 unsafe {
434 cvt(curl_sys::curl_multi_add_handle(self.raw.handle, easy.raw()))?;
435 }
436 Ok(Easy2Handle {
437 guard: DetachGuard {
438 multi: self.raw.clone(),
439 easy: easy.raw(),
440 },
441 easy,
442 _marker: marker::PhantomData,
443 })
444 }
445
446 /// Remove an easy handle from this multi session
447 ///
448 /// Removes the easy handle from this multi handle. This will make the
449 /// returned easy handle be removed from this multi handle's control.
450 ///
451 /// When the easy handle has been removed from a multi stack, it is again
452 /// perfectly legal to invoke `perform` on it.
453 ///
454 /// Removing an easy handle while being used is perfectly legal and will
455 /// effectively halt the transfer in progress involving that easy handle.
456 /// All other easy handles and transfers will remain unaffected.
457 pub fn remove(&self, mut easy: EasyHandle) -> Result<Easy, MultiError> {
458 easy.guard.detach()?;
459 Ok(easy.easy)
460 }
461
462 /// Same as `remove`, but for `Easy2Handle`.
463 pub fn remove2<H>(&self, mut easy: Easy2Handle<H>) -> Result<Easy2<H>, MultiError> {
464 easy.guard.detach()?;
465 Ok(easy.easy)
466 }
467
468 /// Read multi stack informationals
469 ///
470 /// Ask the multi handle if there are any messages/informationals from the
471 /// individual transfers. Messages may include informationals such as an
472 /// error code from the transfer or just the fact that a transfer is
473 /// completed. More details on these should be written down as well.
474 pub fn messages<F>(&self, mut f: F)
475 where
476 F: FnMut(Message),
477 {
478 self._messages(&mut f)
479 }
480
481 fn _messages(&self, f: &mut dyn FnMut(Message)) {
482 let mut queue = 0;
483 unsafe {
484 loop {
485 let ptr = curl_sys::curl_multi_info_read(self.raw.handle, &mut queue);
486 if ptr.is_null() {
487 break;
488 }
489 f(Message { ptr, _multi: self })
490 }
491 }
492 }
493
494 /// Inform of reads/writes available data given an action
495 ///
496 /// When the application has detected action on a socket handled by libcurl,
497 /// it should call this function with the sockfd argument set to
498 /// the socket with the action. When the events on a socket are known, they
499 /// can be passed `events`. When the events on a socket are unknown, pass
500 /// `Events::new()` instead, and libcurl will test the descriptor
501 /// internally.
502 ///
503 /// The returned integer will contain the number of running easy handles
504 /// within the multi handle. When this number reaches zero, all transfers
505 /// are complete/done. When you call `action` on a specific socket and the
506 /// counter decreases by one, it DOES NOT necessarily mean that this exact
507 /// socket/transfer is the one that completed. Use `messages` to figure out
508 /// which easy handle that completed.
509 ///
510 /// The `action` function informs the application about updates in the
511 /// socket (file descriptor) status by doing none, one, or multiple calls to
512 /// the socket callback function set with the `socket_function` method. They
513 /// update the status with changes since the previous time the callback was
514 /// called.
515 pub fn action(&self, socket: Socket, events: &Events) -> Result<u32, MultiError> {
516 let mut remaining = 0;
517 unsafe {
518 cvt(curl_sys::curl_multi_socket_action(
519 self.raw.handle,
520 socket,
521 events.bits,
522 &mut remaining,
523 ))?;
524 Ok(remaining as u32)
525 }
526 }
527
528 /// Inform libcurl that a timeout has expired and sockets should be tested.
529 ///
530 /// The returned integer will contain the number of running easy handles
531 /// within the multi handle. When this number reaches zero, all transfers
532 /// are complete/done. When you call `action` on a specific socket and the
533 /// counter decreases by one, it DOES NOT necessarily mean that this exact
534 /// socket/transfer is the one that completed. Use `messages` to figure out
535 /// which easy handle that completed.
536 ///
537 /// Get the timeout time by calling the `timer_function` method. Your
538 /// application will then get called with information on how long to wait
539 /// for socket actions at most before doing the timeout action: call the
540 /// `timeout` method. You can also use the `get_timeout` function to
541 /// poll the value at any given time, but for an event-based system using
542 /// the callback is far better than relying on polling the timeout value.
543 pub fn timeout(&self) -> Result<u32, MultiError> {
544 let mut remaining = 0;
545 unsafe {
546 cvt(curl_sys::curl_multi_socket_action(
547 self.raw.handle,
548 curl_sys::CURL_SOCKET_BAD,
549 0,
550 &mut remaining,
551 ))?;
552 Ok(remaining as u32)
553 }
554 }
555
556 /// Get how long to wait for action before proceeding
557 ///
558 /// An application using the libcurl multi interface should call
559 /// `get_timeout` to figure out how long it should wait for socket actions -
560 /// at most - before proceeding.
561 ///
562 /// Proceeding means either doing the socket-style timeout action: call the
563 /// `timeout` function, or call `perform` if you're using the simpler and
564 /// older multi interface approach.
565 ///
566 /// The timeout value returned is the duration at this very moment. If 0, it
567 /// means you should proceed immediately without waiting for anything. If it
568 /// returns `None`, there's no timeout at all set.
569 ///
570 /// Note: if libcurl returns a `None` timeout here, it just means that
571 /// libcurl currently has no stored timeout value. You must not wait too
572 /// long (more than a few seconds perhaps) before you call `perform` again.
573 pub fn get_timeout(&self) -> Result<Option<Duration>, MultiError> {
574 let mut ms = 0;
575 unsafe {
576 cvt(curl_sys::curl_multi_timeout(self.raw.handle, &mut ms))?;
577 if ms == -1 {
578 Ok(None)
579 } else {
580 Ok(Some(Duration::from_millis(ms as u64)))
581 }
582 }
583 }
584
585 /// Block until activity is detected or a timeout passes.
586 ///
587 /// The timeout is used in millisecond-precision. Large durations are
588 /// clamped at the maximum value curl accepts.
589 ///
590 /// The returned integer will contain the number of internal file
591 /// descriptors on which interesting events occured.
592 ///
593 /// This function is a simpler alternative to using `fdset()` and `select()`
594 /// and does not suffer from file descriptor limits.
595 ///
596 /// # Example
597 ///
598 /// ```
599 /// use curl::multi::Multi;
600 /// use std::time::Duration;
601 ///
602 /// let m = Multi::new();
603 ///
604 /// // Add some Easy handles...
605 ///
606 /// while m.perform().unwrap() > 0 {
607 /// m.wait(&mut [], Duration::from_secs(1)).unwrap();
608 /// }
609 /// ```
610 pub fn wait(&self, waitfds: &mut [WaitFd], timeout: Duration) -> Result<u32, MultiError> {
611 let timeout_ms = Multi::timeout_i32(timeout);
612 unsafe {
613 let mut ret = 0;
614 cvt(curl_sys::curl_multi_wait(
615 self.raw.handle,
616 waitfds.as_mut_ptr() as *mut _,
617 waitfds.len() as u32,
618 timeout_ms,
619 &mut ret,
620 ))?;
621 Ok(ret as u32)
622 }
623 }
624
625 fn timeout_i32(timeout: Duration) -> i32 {
626 let secs = timeout.as_secs();
627 if secs > (i32::MAX / 1000) as u64 {
628 // Duration too large, clamp at maximum value.
629 i32::MAX
630 } else {
631 secs as i32 * 1000 + timeout.subsec_nanos() as i32 / 1_000_000
632 }
633 }
634
635 /// Block until activity is detected or a timeout passes.
636 ///
637 /// The timeout is used in millisecond-precision. Large durations are
638 /// clamped at the maximum value curl accepts.
639 ///
640 /// The returned integer will contain the number of internal file
641 /// descriptors on which interesting events occurred.
642 ///
643 /// This function is a simpler alternative to using `fdset()` and `select()`
644 /// and does not suffer from file descriptor limits.
645 ///
646 /// While this method is similar to [Multi::wait], with the following
647 /// distinctions:
648 /// * If there are no handles added to the multi, poll will honor the
649 /// provided timeout, while [Multi::wait] returns immediately.
650 /// * If poll has blocked due to there being no activity on the handles in
651 /// the Multi, it can be woken up from any thread and at any time before
652 /// the timeout expires.
653 ///
654 /// Requires libcurl 7.66.0 or later.
655 ///
656 /// # Example
657 ///
658 /// ```
659 /// use curl::multi::Multi;
660 /// use std::time::Duration;
661 ///
662 /// let m = Multi::new();
663 ///
664 /// // Add some Easy handles...
665 ///
666 /// while m.perform().unwrap() > 0 {
667 /// m.poll(&mut [], Duration::from_secs(1)).unwrap();
668 /// }
669 /// ```
670 #[cfg(feature = "poll_7_68_0")]
671 pub fn poll(&self, waitfds: &mut [WaitFd], timeout: Duration) -> Result<u32, MultiError> {
672 let timeout_ms = Multi::timeout_i32(timeout);
673 unsafe {
674 let mut ret = 0;
675 cvt(curl_sys::curl_multi_poll(
676 self.raw.handle,
677 waitfds.as_mut_ptr() as *mut _,
678 waitfds.len() as u32,
679 timeout_ms,
680 &mut ret,
681 ))?;
682 Ok(ret as u32)
683 }
684 }
685
686 /// Returns a new [MultiWaker] that can be used to wake up a thread that's
687 /// currently blocked in [Multi::poll].
688 #[cfg(feature = "poll_7_68_0")]
689 pub fn waker(&self) -> MultiWaker {
690 MultiWaker::new(Arc::downgrade(&self.raw))
691 }
692
693 /// Reads/writes available data from each easy handle.
694 ///
695 /// This function handles transfers on all the added handles that need
696 /// attention in an non-blocking fashion.
697 ///
698 /// When an application has found out there's data available for this handle
699 /// or a timeout has elapsed, the application should call this function to
700 /// read/write whatever there is to read or write right now etc. This
701 /// method returns as soon as the reads/writes are done. This function does
702 /// not require that there actually is any data available for reading or
703 /// that data can be written, it can be called just in case. It will return
704 /// the number of handles that still transfer data.
705 ///
706 /// If the amount of running handles is changed from the previous call (or
707 /// is less than the amount of easy handles you've added to the multi
708 /// handle), you know that there is one or more transfers less "running".
709 /// You can then call `info` to get information about each individual
710 /// completed transfer, and that returned info includes `Error` and more.
711 /// If an added handle fails very quickly, it may never be counted as a
712 /// running handle.
713 ///
714 /// When running_handles is set to zero (0) on the return of this function,
715 /// there is no longer any transfers in progress.
716 ///
717 /// # Return
718 ///
719 /// Before libcurl version 7.20.0: If you receive `is_call_perform`, this
720 /// basically means that you should call `perform` again, before you select
721 /// on more actions. You don't have to do it immediately, but the return
722 /// code means that libcurl may have more data available to return or that
723 /// there may be more data to send off before it is "satisfied". Do note
724 /// that `perform` will return `is_call_perform` only when it wants to be
725 /// called again immediately. When things are fine and there is nothing
726 /// immediate it wants done, it'll return `Ok` and you need to wait for
727 /// "action" and then call this function again.
728 ///
729 /// This function only returns errors etc regarding the whole multi stack.
730 /// Problems still might have occurred on individual transfers even when
731 /// this function returns `Ok`. Use `info` to figure out how individual
732 /// transfers did.
733 pub fn perform(&self) -> Result<u32, MultiError> {
734 unsafe {
735 let mut ret = 0;
736 cvt(curl_sys::curl_multi_perform(self.raw.handle, &mut ret))?;
737 Ok(ret as u32)
738 }
739 }
740
741 /// Extracts file descriptor information from a multi handle
742 ///
743 /// This function extracts file descriptor information from a given
744 /// handle, and libcurl returns its `fd_set` sets. The application can use
745 /// these to `select()` on, but be sure to `FD_ZERO` them before calling
746 /// this function as curl_multi_fdset only adds its own descriptors, it
747 /// doesn't zero or otherwise remove any others. The curl_multi_perform
748 /// function should be called as soon as one of them is ready to be read
749 /// from or written to.
750 ///
751 /// If no file descriptors are set by libcurl, this function will return
752 /// `Ok(None)`. Otherwise `Ok(Some(n))` will be returned where `n` the
753 /// highest descriptor number libcurl set. When `Ok(None)` is returned it
754 /// is because libcurl currently does something that isn't possible for
755 /// your application to monitor with a socket and unfortunately you can
756 /// then not know exactly when the current action is completed using
757 /// `select()`. You then need to wait a while before you proceed and call
758 /// `perform` anyway.
759 ///
760 /// When doing `select()`, you should use `get_timeout` to figure out
761 /// how long to wait for action. Call `perform` even if no activity has
762 /// been seen on the `fd_set`s after the timeout expires as otherwise
763 /// internal retries and timeouts may not work as you'd think and want.
764 ///
765 /// If one of the sockets used by libcurl happens to be larger than what
766 /// can be set in an `fd_set`, which on POSIX systems means that the file
767 /// descriptor is larger than `FD_SETSIZE`, then libcurl will try to not
768 /// set it. Setting a too large file descriptor in an `fd_set` implies an out
769 /// of bounds write which can cause crashes, or worse. The effect of NOT
770 /// storing it will possibly save you from the crash, but will make your
771 /// program NOT wait for sockets it should wait for...
772 pub fn fdset2(
773 &self,
774 read: Option<&mut curl_sys::fd_set>,
775 write: Option<&mut curl_sys::fd_set>,
776 except: Option<&mut curl_sys::fd_set>,
777 ) -> Result<Option<i32>, MultiError> {
778 unsafe {
779 let mut ret = 0;
780 let read = read.map(|r| r as *mut _).unwrap_or(ptr::null_mut());
781 let write = write.map(|r| r as *mut _).unwrap_or(ptr::null_mut());
782 let except = except.map(|r| r as *mut _).unwrap_or(ptr::null_mut());
783 cvt(curl_sys::curl_multi_fdset(
784 self.raw.handle,
785 read,
786 write,
787 except,
788 &mut ret,
789 ))?;
790 if ret == -1 {
791 Ok(None)
792 } else {
793 Ok(Some(ret))
794 }
795 }
796 }
797
798 /// Does nothing and returns `Ok(())`. This method remains for backwards
799 /// compatibility.
800 ///
801 /// This method will be changed to take `self` in a future release.
802 #[doc(hidden)]
803 #[deprecated(
804 since = "0.4.30",
805 note = "cannot close safely without consuming self; \
806 will be changed or removed in a future release"
807 )]
808 pub fn close(&self) -> Result<(), MultiError> {
809 Ok(())
810 }
811
812 /// Get a pointer to the raw underlying CURLM handle.
813 pub fn raw(&self) -> *mut curl_sys::CURLM {
814 self.raw.handle
815 }
816}
817
818impl Drop for RawMulti {
819 fn drop(&mut self) {
820 unsafe {
821 let _ = cvt(code:curl_sys::curl_multi_cleanup(self.handle));
822 }
823 }
824}
825
826#[cfg(feature = "poll_7_68_0")]
827impl MultiWaker {
828 /// Creates a new MultiWaker handle.
829 fn new(raw: std::sync::Weak<RawMulti>) -> Self {
830 Self { raw }
831 }
832
833 /// Wakes up a thread that is blocked in [Multi::poll]. This method can be
834 /// invoked from any thread.
835 ///
836 /// Will return an error if the RawMulti has already been dropped.
837 ///
838 /// Requires libcurl 7.68.0 or later.
839 pub fn wakeup(&self) -> Result<(), MultiError> {
840 if let Some(raw) = self.raw.upgrade() {
841 unsafe { cvt(curl_sys::curl_multi_wakeup(raw.handle)) }
842 } else {
843 // This happens if the RawMulti has already been dropped:
844 Err(MultiError::new(curl_sys::CURLM_BAD_HANDLE))
845 }
846 }
847}
848
849fn cvt(code: curl_sys::CURLMcode) -> Result<(), MultiError> {
850 if code == curl_sys::CURLM_OK {
851 Ok(())
852 } else {
853 Err(MultiError::new(code))
854 }
855}
856
857impl fmt::Debug for Multi {
858 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
859 f.debug_struct("Multi").field(name:"raw", &self.raw).finish()
860 }
861}
862
863macro_rules! impl_easy_getters {
864 () => {
865 impl_easy_getters! {
866 time_condition_unmet -> bool,
867 effective_url -> Option<&str>,
868 effective_url_bytes -> Option<&[u8]>,
869 response_code -> u32,
870 http_connectcode -> u32,
871 filetime -> Option<i64>,
872 download_size -> f64,
873 content_length_download -> f64,
874 total_time -> Duration,
875 namelookup_time -> Duration,
876 connect_time -> Duration,
877 appconnect_time -> Duration,
878 pretransfer_time -> Duration,
879 starttransfer_time -> Duration,
880 redirect_time -> Duration,
881 redirect_count -> u32,
882 redirect_url -> Option<&str>,
883 redirect_url_bytes -> Option<&[u8]>,
884 header_size -> u64,
885 request_size -> u64,
886 content_type -> Option<&str>,
887 content_type_bytes -> Option<&[u8]>,
888 os_errno -> i32,
889 primary_ip -> Option<&str>,
890 primary_port -> u16,
891 local_ip -> Option<&str>,
892 local_port -> u16,
893 cookies -> List,
894 }
895 };
896
897 ($($name:ident -> $ret:ty,)*) => {
898 $(
899 impl_easy_getters!($name, $ret, concat!(
900 "Same as [`Easy2::",
901 stringify!($name),
902 "`](../easy/struct.Easy2.html#method.",
903 stringify!($name),
904 ")."
905 ));
906 )*
907 };
908
909 ($name:ident, $ret:ty, $doc:expr) => {
910 #[doc = $doc]
911 pub fn $name(&mut self) -> Result<$ret, Error> {
912 self.easy.$name()
913 }
914 };
915}
916
917impl EasyHandle {
918 /// Sets an internal private token for this `EasyHandle`.
919 ///
920 /// This function will set the `CURLOPT_PRIVATE` field on the underlying
921 /// easy handle.
922 pub fn set_token(&mut self, token: usize) -> Result<(), Error> {
923 unsafe {
924 crate::cvt(curl_sys::curl_easy_setopt(
925 self.easy.raw(),
926 curl_sys::CURLOPT_PRIVATE,
927 token,
928 ))
929 }
930 }
931
932 impl_easy_getters!();
933
934 /// Unpause reading on a connection.
935 ///
936 /// Using this function, you can explicitly unpause a connection that was
937 /// previously paused.
938 ///
939 /// A connection can be paused by letting the read or the write callbacks
940 /// return `ReadError::Pause` or `WriteError::Pause`.
941 ///
942 /// The chance is high that you will get your write callback called before
943 /// this function returns.
944 pub fn unpause_read(&self) -> Result<(), Error> {
945 self.easy.unpause_read()
946 }
947
948 /// Unpause writing on a connection.
949 ///
950 /// Using this function, you can explicitly unpause a connection that was
951 /// previously paused.
952 ///
953 /// A connection can be paused by letting the read or the write callbacks
954 /// return `ReadError::Pause` or `WriteError::Pause`. A write callback that
955 /// returns pause signals to the library that it couldn't take care of any
956 /// data at all, and that data will then be delivered again to the callback
957 /// when the writing is later unpaused.
958 pub fn unpause_write(&self) -> Result<(), Error> {
959 self.easy.unpause_write()
960 }
961
962 /// Get a pointer to the raw underlying CURL handle.
963 pub fn raw(&self) -> *mut curl_sys::CURL {
964 self.easy.raw()
965 }
966}
967
968impl fmt::Debug for EasyHandle {
969 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
970 self.easy.fmt(f)
971 }
972}
973
974impl<H> Easy2Handle<H> {
975 /// Acquires a reference to the underlying handler for events.
976 pub fn get_ref(&self) -> &H {
977 self.easy.get_ref()
978 }
979
980 /// Acquires a reference to the underlying handler for events.
981 pub fn get_mut(&mut self) -> &mut H {
982 self.easy.get_mut()
983 }
984
985 /// Same as `EasyHandle::set_token`
986 pub fn set_token(&mut self, token: usize) -> Result<(), Error> {
987 unsafe {
988 crate::cvt(curl_sys::curl_easy_setopt(
989 self.easy.raw(),
990 curl_sys::CURLOPT_PRIVATE,
991 token,
992 ))
993 }
994 }
995
996 impl_easy_getters!();
997
998 /// Unpause reading on a connection.
999 ///
1000 /// Using this function, you can explicitly unpause a connection that was
1001 /// previously paused.
1002 ///
1003 /// A connection can be paused by letting the read or the write callbacks
1004 /// return `ReadError::Pause` or `WriteError::Pause`.
1005 ///
1006 /// The chance is high that you will get your write callback called before
1007 /// this function returns.
1008 pub fn unpause_read(&self) -> Result<(), Error> {
1009 self.easy.unpause_read()
1010 }
1011
1012 /// Unpause writing on a connection.
1013 ///
1014 /// Using this function, you can explicitly unpause a connection that was
1015 /// previously paused.
1016 ///
1017 /// A connection can be paused by letting the read or the write callbacks
1018 /// return `ReadError::Pause` or `WriteError::Pause`. A write callback that
1019 /// returns pause signals to the library that it couldn't take care of any
1020 /// data at all, and that data will then be delivered again to the callback
1021 /// when the writing is later unpaused.
1022 pub fn unpause_write(&self) -> Result<(), Error> {
1023 self.easy.unpause_write()
1024 }
1025
1026 /// Get a pointer to the raw underlying CURL handle.
1027 pub fn raw(&self) -> *mut curl_sys::CURL {
1028 self.easy.raw()
1029 }
1030}
1031
1032impl<H: fmt::Debug> fmt::Debug for Easy2Handle<H> {
1033 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1034 self.easy.fmt(f)
1035 }
1036}
1037
1038impl DetachGuard {
1039 /// Detach the referenced easy handle from its multi handle manually.
1040 /// Subsequent calls to this method will have no effect.
1041 fn detach(&mut self) -> Result<(), MultiError> {
1042 if !self.easy.is_null() {
1043 unsafe {
1044 cvt(code:curl_sys::curl_multi_remove_handle(
1045 self.multi.handle,
1046 self.easy,
1047 ))?
1048 }
1049
1050 // Set easy to null to signify that the handle was removed.
1051 self.easy = ptr::null_mut();
1052 }
1053
1054 Ok(())
1055 }
1056}
1057
1058impl Drop for DetachGuard {
1059 fn drop(&mut self) {
1060 let _ = self.detach();
1061 }
1062}
1063
1064impl<'multi> Message<'multi> {
1065 /// If this message indicates that a transfer has finished, returns the
1066 /// result of the transfer in `Some`.
1067 ///
1068 /// If the message doesn't indicate that a transfer has finished, then
1069 /// `None` is returned.
1070 ///
1071 /// Note that the `result*_for` methods below should be preferred as they
1072 /// provide better error messages as the associated error data on the
1073 /// handle can be associated with the error type.
1074 pub fn result(&self) -> Option<Result<(), Error>> {
1075 unsafe {
1076 if (*self.ptr).msg == curl_sys::CURLMSG_DONE {
1077 Some(crate::cvt((*self.ptr).data as curl_sys::CURLcode))
1078 } else {
1079 None
1080 }
1081 }
1082 }
1083
1084 /// Same as `result`, except only returns `Some` for the specified handle.
1085 ///
1086 /// Note that this function produces better error messages than `result` as
1087 /// it uses `take_error_buf` to associate error information with the
1088 /// returned error.
1089 pub fn result_for(&self, handle: &EasyHandle) -> Option<Result<(), Error>> {
1090 if !self.is_for(handle) {
1091 return None;
1092 }
1093 let mut err = self.result();
1094 if let Some(Err(e)) = &mut err {
1095 if let Some(s) = handle.easy.take_error_buf() {
1096 e.set_extra(s);
1097 }
1098 }
1099 err
1100 }
1101
1102 /// Same as `result`, except only returns `Some` for the specified handle.
1103 ///
1104 /// Note that this function produces better error messages than `result` as
1105 /// it uses `take_error_buf` to associate error information with the
1106 /// returned error.
1107 pub fn result_for2<H>(&self, handle: &Easy2Handle<H>) -> Option<Result<(), Error>> {
1108 if !self.is_for2(handle) {
1109 return None;
1110 }
1111 let mut err = self.result();
1112 if let Some(Err(e)) = &mut err {
1113 if let Some(s) = handle.easy.take_error_buf() {
1114 e.set_extra(s);
1115 }
1116 }
1117 err
1118 }
1119
1120 /// Returns whether this easy message was for the specified easy handle or
1121 /// not.
1122 pub fn is_for(&self, handle: &EasyHandle) -> bool {
1123 unsafe { (*self.ptr).easy_handle == handle.easy.raw() }
1124 }
1125
1126 /// Same as `is_for`, but for `Easy2Handle`.
1127 pub fn is_for2<H>(&self, handle: &Easy2Handle<H>) -> bool {
1128 unsafe { (*self.ptr).easy_handle == handle.easy.raw() }
1129 }
1130
1131 /// Returns the token associated with the easy handle that this message
1132 /// represents a completion for.
1133 ///
1134 /// This function will return the token assigned with
1135 /// `EasyHandle::set_token`. This reads the `CURLINFO_PRIVATE` field of the
1136 /// underlying `*mut CURL`.
1137 pub fn token(&self) -> Result<usize, Error> {
1138 unsafe {
1139 let mut p = 0usize;
1140 crate::cvt(curl_sys::curl_easy_getinfo(
1141 (*self.ptr).easy_handle,
1142 curl_sys::CURLINFO_PRIVATE,
1143 &mut p,
1144 ))?;
1145 Ok(p)
1146 }
1147 }
1148}
1149
1150impl<'a> fmt::Debug for Message<'a> {
1151 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1152 f.debug_struct("Message").field(name:"ptr", &self.ptr).finish()
1153 }
1154}
1155
1156impl Events {
1157 /// Creates a new blank event bit mask.
1158 pub fn new() -> Events {
1159 Events { bits: 0 }
1160 }
1161
1162 /// Set or unset the whether these events indicate that input is ready.
1163 pub fn input(&mut self, val: bool) -> &mut Events {
1164 self.flag(curl_sys::CURL_CSELECT_IN, val)
1165 }
1166
1167 /// Set or unset the whether these events indicate that output is ready.
1168 pub fn output(&mut self, val: bool) -> &mut Events {
1169 self.flag(curl_sys::CURL_CSELECT_OUT, val)
1170 }
1171
1172 /// Set or unset the whether these events indicate that an error has
1173 /// happened.
1174 pub fn error(&mut self, val: bool) -> &mut Events {
1175 self.flag(curl_sys::CURL_CSELECT_ERR, val)
1176 }
1177
1178 fn flag(&mut self, flag: c_int, val: bool) -> &mut Events {
1179 if val {
1180 self.bits |= flag;
1181 } else {
1182 self.bits &= !flag;
1183 }
1184 self
1185 }
1186}
1187
1188impl fmt::Debug for Events {
1189 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1190 f&mut DebugStruct<'_, '_>.debug_struct("Events")
1191 .field("input", &(self.bits & curl_sys::CURL_CSELECT_IN != 0))
1192 .field("output", &(self.bits & curl_sys::CURL_CSELECT_OUT != 0))
1193 .field(name:"error", &(self.bits & curl_sys::CURL_CSELECT_ERR != 0))
1194 .finish()
1195 }
1196}
1197
1198impl SocketEvents {
1199 /// Wait for incoming data. For the socket to become readable.
1200 pub fn input(&self) -> bool {
1201 self.bits & curl_sys::CURL_POLL_IN == curl_sys::CURL_POLL_IN
1202 }
1203
1204 /// Wait for outgoing data. For the socket to become writable.
1205 pub fn output(&self) -> bool {
1206 self.bits & curl_sys::CURL_POLL_OUT == curl_sys::CURL_POLL_OUT
1207 }
1208
1209 /// Wait for incoming and outgoing data. For the socket to become readable
1210 /// or writable.
1211 pub fn input_and_output(&self) -> bool {
1212 self.bits & curl_sys::CURL_POLL_INOUT == curl_sys::CURL_POLL_INOUT
1213 }
1214
1215 /// The specified socket/file descriptor is no longer used by libcurl.
1216 pub fn remove(&self) -> bool {
1217 self.bits & curl_sys::CURL_POLL_REMOVE == curl_sys::CURL_POLL_REMOVE
1218 }
1219}
1220
1221impl fmt::Debug for SocketEvents {
1222 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1223 f&mut DebugStruct<'_, '_>.debug_struct("Events")
1224 .field("input", &self.input())
1225 .field("output", &self.output())
1226 .field(name:"remove", &self.remove())
1227 .finish()
1228 }
1229}
1230
1231impl WaitFd {
1232 /// Constructs an empty (invalid) WaitFd.
1233 pub fn new() -> WaitFd {
1234 WaitFd {
1235 inner: curl_sys::curl_waitfd {
1236 fd: 0,
1237 events: 0,
1238 revents: 0,
1239 },
1240 }
1241 }
1242
1243 /// Set the file descriptor to wait for.
1244 pub fn set_fd(&mut self, fd: Socket) {
1245 self.inner.fd = fd;
1246 }
1247
1248 /// Indicate that the socket should poll on read events such as new data
1249 /// received.
1250 ///
1251 /// Corresponds to `CURL_WAIT_POLLIN`.
1252 pub fn poll_on_read(&mut self, val: bool) -> &mut WaitFd {
1253 self.flag(curl_sys::CURL_WAIT_POLLIN, val)
1254 }
1255
1256 /// Indicate that the socket should poll on high priority read events such
1257 /// as out of band data.
1258 ///
1259 /// Corresponds to `CURL_WAIT_POLLPRI`.
1260 pub fn poll_on_priority_read(&mut self, val: bool) -> &mut WaitFd {
1261 self.flag(curl_sys::CURL_WAIT_POLLPRI, val)
1262 }
1263
1264 /// Indicate that the socket should poll on write events such as the socket
1265 /// being clear to write without blocking.
1266 ///
1267 /// Corresponds to `CURL_WAIT_POLLOUT`.
1268 pub fn poll_on_write(&mut self, val: bool) -> &mut WaitFd {
1269 self.flag(curl_sys::CURL_WAIT_POLLOUT, val)
1270 }
1271
1272 fn flag(&mut self, flag: c_short, val: bool) -> &mut WaitFd {
1273 if val {
1274 self.inner.events |= flag;
1275 } else {
1276 self.inner.events &= !flag;
1277 }
1278 self
1279 }
1280
1281 /// After a call to `wait`, returns `true` if `poll_on_read` was set and a
1282 /// read event occured.
1283 pub fn received_read(&self) -> bool {
1284 self.inner.revents & curl_sys::CURL_WAIT_POLLIN == curl_sys::CURL_WAIT_POLLIN
1285 }
1286
1287 /// After a call to `wait`, returns `true` if `poll_on_priority_read` was set and a
1288 /// priority read event occured.
1289 pub fn received_priority_read(&self) -> bool {
1290 self.inner.revents & curl_sys::CURL_WAIT_POLLPRI == curl_sys::CURL_WAIT_POLLPRI
1291 }
1292
1293 /// After a call to `wait`, returns `true` if `poll_on_write` was set and a
1294 /// write event occured.
1295 pub fn received_write(&self) -> bool {
1296 self.inner.revents & curl_sys::CURL_WAIT_POLLOUT == curl_sys::CURL_WAIT_POLLOUT
1297 }
1298}
1299
1300#[cfg(unix)]
1301impl From<pollfd> for WaitFd {
1302 fn from(pfd: pollfd) -> WaitFd {
1303 let mut events: i16 = 0;
1304 if pfd.events & POLLIN == POLLIN {
1305 events |= curl_sys::CURL_WAIT_POLLIN;
1306 }
1307 if pfd.events & POLLPRI == POLLPRI {
1308 events |= curl_sys::CURL_WAIT_POLLPRI;
1309 }
1310 if pfd.events & POLLOUT == POLLOUT {
1311 events |= curl_sys::CURL_WAIT_POLLOUT;
1312 }
1313 WaitFd {
1314 inner: curl_sys::curl_waitfd {
1315 fd: pfd.fd,
1316 events,
1317 revents: 0,
1318 },
1319 }
1320 }
1321}
1322
1323impl fmt::Debug for WaitFd {
1324 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1325 f&mut DebugStruct<'_, '_>.debug_struct("WaitFd")
1326 .field("fd", &self.inner.fd)
1327 .field("events", &self.inner.fd)
1328 .field(name:"revents", &self.inner.fd)
1329 .finish()
1330 }
1331}
1332

Provided by KDAB

Privacy Policy
Learn Rust with the experts
Find out more