On 7/21/2023 12:05 PM, Richard W.M. Jones wrote:
On Fri, Jul 21, 2023 at 09:58:52AM +0000, Tage Johansson wrote:
On 7/19/2023 4:47 PM, Richard W.M. Jones wrote:

     On Wed, Jul 19, 2023 at 09:09:53AM +0000, Tage Johansson wrote:

         Create another handle type: `AsyncHandle`, which makes use of Rust's
         builtin asynchronous functions (see
         <https://doc.rust-lang.org/std/keyword.async.html>) and runs on top of
         the Tokio runtime (see <https://docs.rs/tokio>). For every asynchronous
         command, like `aio_connect()`, a corresponding `async` method is 
created
         on the handle. In this case it would be:
             async fn connect(...) -> Result<(), ...>
         When called, it will poll the file descriptor until the command is
         complete, and then return with a result. All the synchronous
         counterparts (like `nbd_connect()`) are excluded from this handle type
         as they are unnecessary and since they might interfear with the polling
         made by the Tokio runtime. For more details about how the asynchronous
         commands are executed, please see the comments in
         rust/src/async_handle.rs.

     This is an interesting (and good) approach, layering a more natural
     API for Rust users on top of the "low level" API.


         ---
          generator/Rust.ml        | 232 +++++++++++++++++++++++++++++++++++++++
          generator/Rust.mli       |   2 +
          generator/generator.ml   |   1 +
          rust/Cargo.toml          |   1 +
          rust/Makefile.am         |   1 +
          rust/src/async_handle.rs | 222 +++++++++++++++++++++++++++++++++++++
          rust/src/lib.rs          |   4 +
          7 files changed, 463 insertions(+)
          create mode 100644 rust/src/async_handle.rs

         diff --git a/generator/Rust.ml b/generator/Rust.ml
         index 96095a9..1048831 100644
         --- a/generator/Rust.ml
         +++ b/generator/Rust.ml
         @@ -601,3 +601,235 @@ let generate_rust_bindings () =
            pr "impl Handle {\n";
            List.iter print_rust_handle_method handle_calls;
            pr "}\n\n"
         +
         +(*********************************************************)
         +(* The rest of the file conserns the asynchronous API.   *)
         +(*                                                       *)
         +(* See the comments in rust/src/async_handle.rs for more *)
         +(* information about how it works.                       *)
         +(*********************************************************)
         +
         +let excluded_handle_calls : NameSet.t =
         +  NameSet.of_list
         +    [
         +      "aio_get_fd";
         +      "aio_get_direction";
         +      "aio_notify_read";
         +      "aio_notify_write";
         +      "clear_debug_callback";
         +      "get_debug";
         +      "poll";
         +      "poll2";
         +      "set_debug";
         +      "set_debug_callback";
         +    ]

     This is a "code smell" since all information that is specific to APIs
     should (usually) go in generator/API.ml.

     It's reasonable for aio_get_{fd,direction} aio_notify_{read,write}
     since those are very special APIs, but I don't understand why the poll
     and debug functions are listed here.  What's the real meaning of this
     list of functions, ie. why can each one not be included in the tokio
     bindings?


The debug functions are used for setting up logging with the log crate in rust/
src/handle.rs:
Interesting .. Is this used for all libnbd Rust handles?


Yes, both for `libnbd::Handle` and `libnbd::AsyncHandle`. `libnbd::AsyncHandle` is built ontop of `libnbd::Handle`.


```Rust

impl Handle {
     pub fn new() -> Result<Self> {
         let handle = unsafe { sys::nbd_create() };
         if handle.is_null() {
             Err(unsafe { Error::Fatal(ErrorKind::get_error().into()) })
         } else {
             // Set a debug callback communicating with any logging
             // implementation as defined by the log crate.
             #[allow(unused_mut)]
            let mut nbd = Handle { handle };
             #[cfg(feature = "log")]
             {
                 nbd.set_debug_callback(|func_name, msg| {
                     log::debug!(
                         target: func_name.to_string_lossy().as_ref(),
                         "{}",
                         msg.to_string_lossy()
                     );
                     0
                 })?;
                 nbd.set_debug(true)?;
             }
             Ok(nbd)
         }
     }

```


So if the user would set another debug callback this functionality would stop
working. And since the documentation is automaticly generated for
`nbd_set_debug_callback()`, the user might not expect that logging just stops
working because they set a debug callback. But I guess that I can add a note
about that to the documentation of the `Handle` struct and include the debug
callbacks, if you want?
I'm a bit unclear if the debugging is necessary or not.  In the other
bindings we don't set up a log handler by default, it just works the
same way as the C API.

Regarding the other methods, it's a bit more complicated. The
problem is that calling any `aio_notify_*` method might cause any
ongoing command to finish.  For example, the `connect()` method on
`AsyncHandle` will not return until `aio_is_connecting()` returns
false. In order not to have to check that in a busy loop, the
function is woken up only when the polling task makes a call to any
`aio_notify_*` function. So whenever the polling task calls
`aio_notify_*`, it notifies the waiting command that the handle
might has changed state. But if the user calls `aio_notify_*`
(directly or indirectly) while `aio_connect` is in flight, it might
happen that the connection completes without the call to `connect()`
gets notified about that.

This means that any function that calls any `aio_notify_*` function
should not be implemented on `AsyncHandle`. A better approach than
the current would probably be to add a field to the `call`-structure
in API.ml. Something like `notifies_fd : bool` (sure someone can
come up with a better name). This would be set to true for any
function that calls `aio_notify_*`, including `aio_notify_read()`,
`poll()`, and all synchronous commands like `nbd_connect() `. What
do you think about that?
Yup, it was already clear to me why these wouldn't work after I
thought about it a bit more.

My issue for maintenance is that if we add a new "poll-like" function
to the API then the Rust bindings would need a special change.


Sorry, but what do you mean by "would need a special change"?


Best regards,

Tage


I have written some more explonations about how the asynchronous
handle works in rust/src/async_handle.rs, but it is somewhat
complicated so please ask if there are more unclear things.
Sure, thanks!

Rich.


_______________________________________________
Libguestfs mailing list
Libguestfs@redhat.com
https://listman.redhat.com/mailman/listinfo/libguestfs

Reply via email to