Skip to content

Comments

Generic offload#5

Merged
ming1 merged 9 commits intomainfrom
generic-offload
Jul 12, 2025
Merged

Generic offload#5
ming1 merged 9 commits intomainfrom
generic-offload

Conversation

@ming1
Copy link
Collaborator

@ming1 ming1 commented Jul 12, 2025

  • abstract generic offload handling, so far, just for sync io handling

ming1 added 9 commits July 11, 2025 12:17
Move submit_poll_sqe() into OffloadHandler, and make it private
function of OffloadHandler.

Meantime add new() method for calling it when initialize one
OffloadHandler.

Signed-off-by: Ming Lei <tom.leiming@gmail.com>
This commit introduces a generic offload handling mechanism to simplify
the implementation of ublk targets that need to offload I/O operations
to worker threads.

The key changes are:

- **`src/offload/handler.rs`**: A new `OffloadHandler` struct is
  introduced. This struct encapsulates the logic for managing a worker
  thread, an eventfd for signaling, and the communication channels
  (Sender/Receiver) for jobs and completions. It provides a generic
  `setup_worker_thread` function that can be used by any target.

- **`src/offload/mod.rs`**: The `OffloadTargetLogic` trait is defined to
  provide a common interface for targets that use the offload
  mechanism. It requires the implementation of `setup_read_worker`,
  `setup_flush_worker`, and `handle_io`.

- **`src/compress.rs`**: The `compress` target is refactored to use the
  new offload handler. The `QueueHandler` is simplified, and the logic
  for setting up worker threads and handling offloaded I/O is now
  delegated to the `OffloadHandler`. This makes the `compress` target's
  code cleaner and more focused on its specific logic (i.e., RocksDB
  operations).

In Rust, this is implemented by:

- Using generic types (`<Job, C, F>`) in `setup_worker_thread` to make
  it adaptable to different types of jobs and completion data.
- Leveraging traits (`OffloadTargetLogic`) to define a contract for
  offloadable targets, promoting polymorphism and code reuse.
- Using `mpsc::channel` for communication between the main thread and
  the worker threads, which is a standard and safe way to handle
  concurrent operations in Rust.
- The `nix` crate is used for creating and managing the `eventfd`, which
  is a low-level Linux feature for event notification.

This refactoring improves the overall design by separating the generic
offload mechanism from the specific implementation details of each
target, leading to more maintainable and reusable code.

Signed-off-by: Ming Lei <tom.leiming@gmail.com>
This commit refactors the offload handling mechanism to use a single,
generic `OffloadJob` struct instead of separate structs for each type
of I/O operation (e.g., `ReadJob`, `FlushJob`). This simplifies the
offload handler and makes it more extensible for future I/O types.

The key changes are:

- **`src/offload/handler.rs`**:
  - The `ReadJob` and `FlushJob` structs have been removed and replaced
    with a single `OffloadJob` struct. This new struct is designed to be
    a superset of the previous structs, containing all necessary fields
    for different I/O operations.
  - The `OffloadHandler` is now generic over a single `OffloadJob` type,
    simplifying its definition.
  - The `send_read_job` and `send_flush_job` functions have been moved
    into `OffloadHandler` as methods, making the API more cohesive.

- **`src/offload/mod.rs`**:
  - The `OffloadTargetLogic` trait is updated to use `OffloadJob` in the
    signatures of `setup_read_worker` and `setup_flush_worker`.

- **`src/compress.rs`**:
  - The `compress` target is updated to use the new `OffloadJob` struct
    when setting up its worker threads.

How this is implemented in Rust:

- The `OffloadJob` struct is defined with `#[derive(Debug, Default)]`.
  The `Default` trait is used in the `send_flush_job` method to easily
  create an `OffloadJob` for a flush operation, which doesn't require
  all the fields of the struct. This is a convenient way to handle
  structs with many optional fields.

- The `OffloadTargetLogic` trait is updated to use the unified
  `OffloadJob` type. This demonstrates how traits in Rust can be used to
  define a generic interface that can be adapted to different underlying
  data structures.

- The `OffloadHandler` is no longer generic over the job type, which
  simplifies its implementation and usage. This is a good example of
  how refactoring can lead to simpler, more maintainable code.

This change improves the design by reducing code duplication and making
the offload handler more generic and easier to extend with new I/O
operation types in the future.

Signed-off-by: Ming Lei <tom.leiming@gmail.com>
This commit introduces a more abstract and simplified way to set up
offload handlers, further improving the generic offload mechanism.
Instead of requiring the target logic to implement separate setup
functions for each type of worker (e.g., read, flush), this change
unifies the worker setup and allows the target to register handlers
dynamically.

The key changes are:

- **`src/offload/handler.rs`**:
  - `QueueHandler::new` now initializes an empty `Vec<Option<OffloadHandler>>`
    with a pre-defined capacity (`NR_OFFLOAD_HANDLERS`).
  - It then calls a new `setup_offload_handlers` method on the target
    logic, passing a mutable reference to itself. This allows the target
    to register its specific handlers in the `offload_handlers` vector.

- **`src/offload/mod.rs`**:
  - The `OffloadTargetLogic` trait is simplified. The specific
    `setup_read_worker` and `setup_flush_worker` methods are removed.
  - They are replaced by a single `setup_offload_handlers` method that
    takes `&mut QueueHandler`.

- **`src/compress.rs`**:
  - A new private helper function, `setup_one_handler`, is introduced
    within the `CompressTarget` implementation. This function encapsulates
    the logic for creating a single offload handler, including setting
    up the worker thread and its communication channel.
  - The `setup_offload_handlers` implementation now simply calls
    `setup_one_handler` for each required handler (read and flush),
    making the setup process cleaner and more declarative.
  - The `handle_io` function is updated to use the registered handlers
    from the `offload_handlers` vector.

How this is implemented in Rust:

- The most significant change is the inversion of control in the setup
  process. Instead of the `QueueHandler` pulling sender/receiver pairs
  from the target logic, it now passes a mutable reference to itself
  (`&mut QueueHandler`) to the target logic's `setup_offload_handlers`
  method. This is a powerful Rust pattern that leverages the borrow
  checker to allow the target to safely modify the `QueueHandler`'s
  state (i.e., register its handlers) during initialization.

- The `offload_handlers` vector is now a `Vec<Option<OffloadHandler>>`.
  Using `Option` here is idiomatic Rust for representing a collection
  where some slots may be empty. It provides a safe way to handle
  optional values, preventing null pointer-like errors.

- The `setup_one_handler` function in `compress.rs` demonstrates how to
  encapsulate repeated logic within a private helper method in an `impl`
  block, improving code organization and readability.

This refactoring results in a more flexible and decoupled design. The
`QueueHandler` is now more generic, and the target implementation has
full control over how its offload handlers are created and registered.

Signed-off-by: Ming Lei <tom.leiming@gmail.com>
This commit improves the offload handling mechanism by addressing two
key shortcomings in the previous implementation: the use of "magic
numbers" for handler indexing and the potential for panics due to
out-of-bounds access.

The key changes are:

- **`src/offload/mod.rs`**:
  - An `OffloadType` enum is introduced to represent the different
    types of offload handlers (e.g., `Read`, `Flush`). This replaces
    the integer constants, providing type safety and making the code
    more self-documenting.
  - A `From<OffloadType> for usize` implementation is provided to easily
    convert the enum variants into vector indices.

- **`src/compress.rs`**:
  - The `setup_one_handler` function is updated to accept an
    `OffloadType` enum variant instead of a `u32` index. This makes
    the code more readable and less error-prone.
  - The `handle_io` function is refactored to use `get_mut()` and `get()`
    for accessing the `offload_handlers` vector. This is a key change
    for improving safety. Instead of panicking on an out-of-bounds
    index, `get()` and `get_mut()` return an `Option`, allowing the code
    to handle invalid indices gracefully. The `if let Some(Some(h))`
    pattern is used to safely unwrap both the result of the `get()`
    call and the `Option` inside the vector.

How this is implemented in Rust:

- The use of an **`enum`** for `OffloadType` is a classic example of
  leveraging Rust's strong type system to create more robust and
  expressive APIs. It prevents a whole class of bugs related to invalid
  handler indices.

- The `From` trait is used to provide a canonical conversion from
  `OffloadType` to `usize`. This is an idiomatic way to handle type
  conversions in Rust.

- The use of **`get()`** and **`get_mut()`** instead of direct indexing
  (`[]`) is a fundamental aspect of safe Rust programming. It forces the
  developer to handle the case where the index might be invalid,
  preventing panics and making the code more resilient. The nested
  `if let Some(Some(...))` pattern is a concise way to handle the two
  levels of `Option` (one from `get()` and one from the `Option` in the
  vector).

Signed-off-by: Ming Lei <tom.leiming@gmail.com>
This commit refactors the offload handling mechanism to improve safety
and clarity by removing the need for raw buffer pointers (`bufs_ptr`)
in the handler structs. The I/O buffer is now passed explicitly through
the call chain, making the code easier to follow and less prone to
`unsafe` errors.

The key changes are:

- **`src/offload/handler.rs`**:
  - The `bufs_ptr` field has been removed from both `OffloadHandler`
    and `QueueHandler`. This eliminates a source of `unsafe` code and
    makes these structs simpler.
  - A `buf_addr: u64` field was added to the `Completion` struct. This
    allows the worker thread to pass the address of the completed I/O
    buffer back to the main thread.
  - `OffloadHandler::handle_completion` now uses the `buf_addr` from the
    `Completion` struct to complete the I/O, rather than relying on a
    stored pointer.
  - `QueueHandler::handle_event` is updated to accept an
    `Option<&mut [u8]>`, making it explicit that a buffer may not always
    be present (e.g., for poll events).

- **`src/offload/mod.rs`**:
  - The `OffloadTargetLogic::handle_io` trait method is updated to
    accept `Option<&mut [u8]>`, propagating the safer buffer handling
    to all target implementations.

- **`src/compress.rs`**:
  - The `q_sync_fn` is updated to pass the buffer to `handle_event` as
    an `Option`. It provides `Some(buf)` for regular I/O and `None` for
    poll events, which is determined by checking the `tag`.
  - The `handle_io` implementation now receives the buffer as an
    `Option` and unwraps it, removing the previous `unsafe` block that
    calculated the buffer address from a raw pointer.
  - The `QueueHandler::new` call is updated to remove the `dev`
    parameter, which is no longer needed.

How this is implemented in Rust:

- The core of this refactoring is the use of **`Option<&mut [u8]>`** to
  represent a buffer that may or may not be present. This is a highly
  idiomatic Rust pattern that leverages the type system to enforce safe
  handling of optional values at compile time, preventing null-pointer
  style errors.

- By passing the buffer as a parameter, the data flow becomes much more
  explicit. This is a key principle of safe and maintainable Rust code:
  making data ownership and borrowing clear through function signatures.

- The removal of the `bufs_ptr` fields from the handler structs reduces
  the amount of `unsafe` code and simplifies the logic, as the handlers
  no longer need to manage this state.

Signed-off-by: Ming Lei <tom.leiming@gmail.com>
…r compress read and flush IO

Signed-off-by: Ming Lei <tom.leiming@gmail.com>
This commit improves the project's encapsulation by changing the
visibility of the offload handling components from `pub` to `pub(crate)`.

Since `rublk` is a binary executable and not a library, its internal
modules are not intended to be used by external crates. Using `pub(crate)`
makes this intent explicit and leverages Rust's type system to enforce
these boundaries.

The key changes are:

- **`src/offload/mod.rs`**: The `OffloadTargetLogic` trait is now
  `pub(crate)`, as it defines a contract for modules *within* this
  crate only.

- **`src/offload/handler.rs`**: All public structs (`OffloadJob`,
  `Completion`, `OffloadHandler`, `QueueHandler`) and the
  `setup_worker_thread` function are now `pub(crate)`. This correctly
  identifies them as internal implementation details of the offload
  framework.

How this is implemented in Rust:

- This change relies on Rust's visibility modifiers. `pub` makes an
  item part of the public API, while `pub(crate)` restricts its
  visibility to the current crate.

- For an application like `rublk`, `pub(crate)` is the idiomatic choice
  for sharing code between modules. It provides the necessary visibility
  for internal use while preventing the components from being exposed
  as a public library API.

Signed-off-by: Ming Lei <tom.leiming@gmail.com>
This commit refactors the offload handling mechanism to improve
encapsulation and simplify the target implementation. The responsibility
for creating the `eventfd` and setting up the worker thread is now moved
entirely inside `OffloadHandler::new`.

The key changes are:

- **`src/offload/handler.rs`**:
  - `OffloadHandler::new` is now a higher-order function that accepts the
    worker logic as a closure (`worker_fn`).
  - Inside `new`, it now creates the `eventfd` and calls the private
    `setup_worker_thread` function, passing it the `eventfd` and the
    closure. This makes the `OffloadHandler` a self-contained component
    that manages its own resources.
  - `setup_worker_thread` is now a private function within the
    `offload::handler` module, as it's an implementation detail that
    should not be called directly from outside.

- **`src/compress.rs`**:
  - The `setup_offload_handlers` function in the `CompressTarget`
    implementation is now much simpler. It no longer needs to create the
    `eventfd` or call `setup_worker_thread`.
  - It now just defines the worker logic in a closure and passes this
    closure directly to `OffloadHandler::new`.

How this is implemented in Rust:

- The use of a **higher-order function** for `OffloadHandler::new` is a
  key aspect of this change. By accepting a closure (`F: Fn(...)`),
  `new` can be generic over the behavior of the worker thread, while
  still encapsulating the boilerplate of thread and `eventfd` creation.
  This is a powerful pattern in Rust for abstracting behavior.

- The use of a **`move` closure** in `compress.rs` is important. It
  allows the closure to take ownership of the captured variables (like
  `db` and `lbs`), which is necessary for the closure to be sent to a
  new thread (i.e., to have a `'static` lifetime).

This refactoring improves the design by making the `OffloadHandler`
responsible for its own setup, which simplifies the target-specific
code and strengthens the encapsulation of the offload module.

Signed-off-by: Ming Lei <tom.leiming@gmail.com>
@ming1 ming1 merged commit 2c078d7 into main Jul 12, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant