#shared-memory #api-bindings #shmem

shmem-bind

A safe and idiomatic wrapper over shared memory APIs in rust with proper cleanups

4 releases

0.1.3 Mar 20, 2024
0.1.2 Mar 19, 2024
0.1.1 Mar 19, 2024
0.1.0 Mar 18, 2024

#318 in Unix APIs

MIT/Apache

20KB
186 lines

shmem-bind

A safe and idiomatic wrapper over shared memory APIs in rust with proper cleanups.

Quick start:

check the message-passing example for better understanding

cargo run --example message-passing

Semantics:

Allocation:

import shared memory abstractions:

use shmem_bind::{ShmemBox, self as shmem};

in order to create new shared memory, use the following builder snippet:

let shared_mem = shmem::Builder::new("<FLINK_FILE_HANDLE>")
    .with_size(mem::size_of::<MyType>() as i64)
    .open()?;

this will allocate a shared memory file with the specified size if the shared memory is not present on the machine. the handle, here shared_mem, would claim ownership of the shared memory if the shared memory is not present and created via call to open function. this is useful information for cleanup process since there is only one owner for each shared memory and only the owner can and will unlink the shared memory.

you can wrap the shared memory configuration into a ShmemBox<T> via call to boxed function.

let boxed_val = unsafe { shared_mem.boxed::<MyType>() };

call to this function is inherently unsafe since there is no guarantee that the memory behind the pointer is initialized or valid.

type NotZeroI32 = i32;

let boxed_val = unsafe { 
  // the allocated shared_memory is zeroed and thus, not a valid `NotZeroI32`
  let mut boxed_val = shared_mem.boxed::<NotZeroI32>();
  *boxed_val = 5;
  boxed_val
  };

the ShmemBox type implements Deref and DerefMut so you can use all the rust semantics and guarantee of T in your code

Cleanup:

When the variable goes out of scope, the drop implementation is called. if the shared memory is owned, i.e. shared memory is created by this handle, the shared memory would unlink. in order to prevent this, you can use the ShmemBox::leak method:


struct MyType;

impl Drop for MyType{
    fn drop(&mut self) {
        println!("my type is dropped");
    }
}

{
  let shared_mem = shmem::Builder::new("<FLINK_FILE_HANDLE>")
      .with_size(mem::size_of::<Message>() as i64)
      .open()?;
  let mut boxed_val = unsafe {shared_mem.boxed::<MyType>()};

  // boxed_val goes out of scope and the underlying MyType is dropped
  // output:
  // my type is dropped
}
{
  let shared_mem = shmem::Builder::new("<FLINK_FILE_HANDLE>")
      .with_size(mem::size_of::<Message>() as i64)
      .open()?;
  let mut boxed_val = unsafe {shared_mem.boxed::<MyType>()};
  ShmemBox::leak(boxed_val);

  // boxed_val leaks and the underlying MyType is not dropped. the shared memory stays linked to the os
  // output is empty
}

you can also use the ShmemBox::own to ensure cleanup of the shared memory:

struct MyType;

impl Drop for MyType{
    fn drop(&mut self) {
        println!("my type is dropped");
    }
}

{
  let shared_mem = shmem::Builder::new("<FLINK_FILE_HANDLE>")
      .with_size(mem::size_of::<Message>() as i64)
      .open()?;
  let mut boxed_val = unsafe {shared_mem.boxed::<MyType>()};
  
  // boxed_val was owner, but it got leaked
  ShmemBox::leak(boxed_val);
}
{
  let shared_mem = shmem::Builder::new("<FLINK_FILE_HANDLE>")
      .with_size(mem::size_of::<Message>() as i64)
      .open()?;
  let mut boxed_val = unsafe {shared_mem.boxed::<MyType>()};
  
  // shared memory is already created, so the boxed_val is not the owner.
  let boxed_val = ShmemBox::own(boxed_val);

  // boxed_val goes out of scope, MyType is dropped. the shared memory is unliked.
  // output:
  // my type is dropped
}

ShmemBox<T> implements Sync and Send if the underlying T implements Sync and Send respectively.

Dependencies

~44KB