diff --git a/README.md b/README.md index b3e1a92..2350f2e 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ New examples for different hardware are also requested. ## Latest News -Work has started on v0.5. This will loosen restrictions on threading, allowing devices to be moved ot different threads, and share an IIO context across threads. +Work has started on v0.5. This will loosen restrictions on threading, allowing devices to be moved to different threads, and share an IIO context across threads. Overall, an effort is underway to get this crate to production quality. It includes: @@ -52,7 +52,10 @@ To keep up with the latest announcements for this project, follow: - The `Device` is now `Send`. - For high performance with multiple device, though, it's still recommended to used fully-cloned contexts for each device - For now, `Channel` and `Buffer` objects are still `!Send` and `!Sync`. So they should live in the same thread as their channel. - +- New functions to manipulate `Context` and `InnerContext` objects: + - `Context::try_release_inner()` attempts to get the inner context out of the context wrapper. + - `Context::try_deep_clone()` to make a new context around a deep copy of the inner context (and thus a copy of the C lib context). + - `From for Context` ### New in Version 0.4.0 - [#12](https://github.com/fpagliughi/rust-industrial-io/pull/12) Context construction now takes a `Backend` enumeration type. @@ -96,7 +99,7 @@ The Rust Industrial I/O library is a fairly thin wrapper around the C _libiio_, ### Library Wrapper -To do anything with _libiio_, the applcation must first create a `Context`to either maniplate the hardware on the local device (i.e. a _local_ context), or to communicate with hardware on a remote device such as over a network connection. Creating a context is a fairly heavyweight operation compared to other library operations in that it will scan the hardware and build up a local representation in memory. +To do anything with _libiio_, the applcation must first create a `Context`to either maniplate the hardware on the local device (i.e. a _local_ context), or to communicate with hardware on a remote device such as over a network connection. Creating a local context is a fairly heavyweight operation compared to other library operations in that it will scan the hardware and build up a local representation in memory. The context is thus a snapshot of the hardware at the time at which it was created. Any hardware that is added outside of the context - such as another process creating a new _hrtimer_, will not be reflected in it. A new context would need to be created to re-scan the hardware. @@ -104,40 +107,55 @@ But then, finding hardware is very efficient in that it just searches through th Nothing is created or destrioyed when new Rust hardware structures are declared, such as `Device` or `Channel`. Therefore the Rust structures can easily be cloned by copying the pointer to the library structure. -Note that there is a slight confusion around "cloning" a Context. In the Rust library, the C context is wrapped by an `InnerContext` object. The `Context` is just a thread-safe, reference counted smart pointer to that inner context. Thus, cloning a Rust `Context` just creates a new, shared pointer to the existing C conext. This makes it easy to share the context and guarantee it's lifetime between multiple `Device` objects created from it. The C context will automatically be destroyed when the last device or other context reference goes out of scope. The application does not need to manually track and delete the context. +The Rust `Context` object is just a reference-counted smart pointer to an `InnerContext`. This makes it easy to share the C context between different objects (devices, channels, etc), and to manage its lifetime. The `InnerContext` actually wraps the C context. When it goes out of scope, typically when the last reference disappears, the C context is destroyed. Cloning the `InnerContext` creates a full copy of the C library's context. -Often, however, when using separate threads to manage each device, it can be more efficient to create a separate C context for each thread. To do this, a "deep" clone of the `Context` is necessary. This is simply a clone of the `InnerContext`, and creating new smart pointers around that inner context. So it is a clone of the `InnerContext` which actually makes a copy of the C library context. +This creates some confusion around "cloning" a Context. Since a `Context` is just a thread-safe, reference counted smart pointer to that inner context, cloning it just creates a new, shared pointer to the existing inner/C context. This makes it easy to share the context and guarantee it's lifetime between multiple `Device` objects created from it. Cloning a `Context` and sending the clone to another thread will actually then _share_ the `InnerContext` (and thus C context) between the two threads. + +Often, however, when using separate threads to manage each device, it can be more efficient to create a fully separate C context for each thread. To do this, a "deep" clone of the `Context` is necessary. This is simply a clone of the `InnerContext`, and creating new smart pointers around that inner context. So it is a clone of the `InnerContext` which actually makes a copy of the C library context. See the next section for details. ### Thread Safety -Early versions of this library (v0.4.x and before) were written with the belief that the underling _libiio_ was not thread-safe. Some public information about the library was a little misleading. With input from some of the maintainers of the library and additional information, thread restrictions are slowly being lifted from this library. +Early versions of this library (v0.4.x and before) were written with the mistaken belief that the underling _libiio_ was not thread-safe. Some public information about the library was a little misleading, but with input from a maintainers of the library and additional published information, thread restrictions are slowly being lifted from this library. Starting in v0.5, the following is now possible: - `InnerContext`, which actually wraps the C library context, is now `Sync` in addition to being `Send`. It can be shared between threads. - `Context` is now implemented with an `Arc` to point to its `InnerContext`. So these references to the inner context can be sent to differet threads and those threads can share the same context. -- The `Device` objects, which hold a `Context` reference, are now `Send`. They can now be moved to a different thread than the one that holds the context. -- For now, the `Channel` and `Buffer` objects are still `!Send` and `!Sync`, and need to live in the same thread with the `Device`, but these restrictions may be loosened as we figure out what specific operations are not thread safe. +- The `Device` objects, which hold a `Context` reference, are now `Send`. They can be moved to a different thread than the one that created the context. +- For now, the `Channel` and `Buffer` objects are still `!Send` and `!Sync`, and need to live in the same thread with the `Device`, but these restrictions may be loosened as we figure out which specific operations are not thread safe. - The `Buffer::refill()` function now take a mutable reference to self, `&mut self`, in preparation of loosening thread restrictions on the buffer. The buffer definitely can not be filled by two different threads at the same time. Even with these new thread capabilities, when the _physical_ devices described by an IIO context can be manipulated by different threads, it is often still desirable to use a separate `Context` instance for each thread. There are two ways to do this: -1. Simply create a new `Context` object in each thread using the same URI, etc. +1. Simply create a new `Context` object in each thread using the same URI, etc. These might not be exactly the same if some new hardware was added or removed between the time any two contexts were created. But this is perhaps rare. 2. Create a context and then make clones of its `InnerContext` object and send those to other threads. Each one can then be used to create a new `Context` instance in that thread. -This second option can be done like this: +This second option is considerably more efficient and can be done in several ways. + +One way is to do a "deep" clone of a context and send it to the other thread: + + let ctx = Context::new()?; + let thr_ctx = ctx.try_deep_clone()?; + + thread::spawn(move || { + let dev = thr_ctx.find_device("somedevice")? + // ... + }); + +This makes a copy of the inner context which clones the C library context. It then sends the one-and-only reference to the other thread, giving it exclusive access to that C context. + +Alternately, to be explicit about cloning the inner context, this can be done: let ctx = Context::new()?; let cti = ctx.try_clone_inner()?; - let thr = thread::spawn(move || { - let thr_ctx = Context::from_inner(cti); + thread::spawn(move || { + let thr_ctx = Context::from(cti); + let dev = thr_ctx.find_device("somedevice")? // ... }); -Here the inner context is cloned to the `cti` object which is moved into the thread and consumed to create a new context object, `thr_ctx`. - -_Note that this procedure was required in the earler versions of library. Prior to the release of 0.5.0, we'll make it easier to make a "deep" clone of the context to send across threads._ +Here the inner context is cloned to the `cti` object which is moved into the thread and consumed to create a new context object, `thr_ctx`. This procedure was required in the earler versions of library, prior to v0.5.0. An alternate way to share devices across threads and processes is to run the IIO network daemon on the local machine and allow it to control the local context. Then multiple client applications can access it from _localhost_ using a network context. The daemon will serialize access to the device and let multiple clients share it. Each thread in the client would still need a separate network context. diff --git a/src/buffer.rs b/src/buffer.rs index 1d52bdd..e722462 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -100,7 +100,8 @@ impl Buffer { sys_result(i32::from(ret), ret) } - /// Make calls to [`Buffer::push()`] or [`Buffer::refill()`] blocking or not. + /// Make calls to [`push()`](Buffer::push) or [`refill()`](Buffer::refill) + /// blocking or not. /// /// A [`Device`] is blocking by default. pub fn set_blocking_mode(&self, blocking: bool) -> Result<()> { @@ -136,20 +137,19 @@ impl Buffer { /// Cancel all buffer operations. /// - /// This function cancels all outstanding [`Buffer`] operations previously - /// scheduled. This means any pending [`Buffer::push()`] or - /// [`Buffer::refill()`] operation will abort and return immediately, any - /// further invocations of these functions on the same buffer will return - /// immediately with an error. + /// This function cancels all outstanding [`Buffer`] operations + /// previously scheduled. This means any pending [`push()`](Buffer::push) + /// or [`refill()`](Buffer::refill) operation will abort and return + /// immediately, any further invocations of these functions on the same + /// buffer will return immediately with an error. /// - /// Usually [`Buffer::push()`] and [`Buffer::refill()`] will block until - /// either all data has been transferred or a timeout occurs. This can, - /// depending on the configuration, take a significant amount of time. - /// [`Buffer::cancel()`] is useful to bypass these conditions if the - /// [`Buffer`] operation is supposed to be stopped in response to an - /// external event (e.g. user input). + /// Usually [`push()`](Buffer::push) and [`refill()`](Buffer::refill) + /// will block until either all data has been transferred or a timeout + /// occurs. This can, depending on the configuration, take a significant + /// amount of time. [`cancel()`](Buffer::cancel) is useful to bypass these + /// conditions if the [`Buffer`] operation is supposed to be stopped in + /// response to an external event (e.g. user input). /// - /// TODO: @fpagliughi, is this true for the rust binding, too? /// To be able to capture additional data after calling this function the /// buffer should be destroyed and then re-created. /// diff --git a/src/context.rs b/src/context.rs index b2bf964..1f7d41c 100644 --- a/src/context.rs +++ b/src/context.rs @@ -15,9 +15,8 @@ use nix::errno::{self, Errno}; use std::{ ffi::{CStr, CString}, os::raw::{c_char, c_uint}, - ptr, + ptr, slice, str, sync::Arc, - slice, str, time::Duration, }; @@ -112,7 +111,8 @@ pub struct InnerContext { } impl InnerContext { - /// Tries to create the inner context from a C pointer. + /// Tries to create the inner context from a C context pointer. + /// /// This should be called _right after_ creating the C context as it /// will use the last error on failure. fn new(ctx: *mut ffi::iio_context) -> Result { @@ -122,18 +122,21 @@ impl InnerContext { } } - /// Create a clone of the underlying context that can be used in another thread. + /// Tries to create a full, deep, copy of the underlying context. + /// + /// This creates a full copy of the actual context held in the underlying + /// C library. This is useful if you want to give a separate copy to each + /// thread in an application, which could help performance. pub fn try_clone(&self) -> Result { - let ctx = unsafe { ffi::iio_context_clone(self.ctx) }; - if ctx.is_null() { - Err(Error::from(Errno::last()))?; - } - Ok(InnerContext { ctx }) + Self::new(unsafe { ffi::iio_context_clone(self.ctx) }) } } impl Drop for InnerContext { - /// Dropping destroys the underlying context. + /// Dropping destroys the underlying C context. + /// + /// When held by [`Context`] references, this should happen when the last + /// context referring to it goes out of scope. fn drop(&mut self) { unsafe { ffi::iio_context_destroy(self.ctx) }; } @@ -239,9 +242,7 @@ impl Context { /// Creates a context from an existing "inner" object. pub fn from_inner(inner: InnerContext) -> Self { - Self { - inner: Arc::new(inner), - } + Self::from(inner) } /// Creates a Rust Context object from a C context pointer. @@ -250,13 +251,34 @@ impl Context { Ok(Self::from_inner(inner)) } - /// Try to create a clone of the inner underlying context that can be - /// used to create a new context object even in a different thread, - /// remembering that `InnerContext` implements the `Send` trait. + /// Try to create a clone of the inner underlying context. + /// + /// The inner context wraps the C library context. Cloning it makes + /// a full copy of the C context. pub fn try_clone_inner(&self) -> Result { self.inner.try_clone() } + /// Tries to release the inner context. + /// + /// This attempts to release and return the [`InnerContext`], which + /// succeeds if this is the only [`Context`] referring to it. If there are + /// other references, an error is returned with a [`Context`]. + pub fn try_release_inner(self) -> std::result::Result { + match Arc::try_unwrap(self.inner) { + Ok(inner) => Ok(inner), + Err(inner_ptr) => Err(Context { inner: inner_ptr }), + } + } + + /// Make a new context based on a full copy of underlying C context. + pub fn try_deep_clone(&self) -> Result { + let inner = self.inner.try_clone()?; + Ok(Self { + inner: Arc::new(inner), + }) + } + /// Get the name of the context. /// This should be "local", "xml", or "network" depending on how the context was created. pub fn name(&self) -> String { @@ -411,6 +433,15 @@ impl PartialEq for Context { } } +impl From for Context { + /// Makes a new [`Context`] from the [`InnerContext`] + fn from(inner: InnerContext) -> Self { + Self { + inner: Arc::new(inner), + } + } +} + /// Iterator over the Devices in a Context #[derive(Debug)] pub struct DeviceIterator<'a> { diff --git a/src/device.rs b/src/device.rs index c873777..74cf7b4 100644 --- a/src/device.rs +++ b/src/device.rs @@ -424,7 +424,6 @@ mod tests { let name = dev.name().unwrap(); assert_eq!(name, "timer0"); - }); let _ = thr.join(); }