Hone logo
Hone
Problems

Implementing Hazard Pointers for a Concurrent Garbage Collector in Rust

Hazard pointers are a crucial technique in concurrent garbage collection, enabling safe access to memory regions while the garbage collector is running. This challenge asks you to implement a basic hazard pointer system in Rust, allowing you to mark objects as potentially live during a garbage collection cycle and prevent the collector from prematurely freeing them. This is a foundational element for building a robust and efficient concurrent garbage collector.

Problem Description

You are tasked with creating a HazardPointer struct and associated functions to manage hazard pointers. The HazardPointer will wrap a raw pointer (*mut T) and provide methods for marking and unmarking objects as potentially live during a garbage collection cycle. The core functionality revolves around a global hazard bit vector. When a pointer is marked as a hazard pointer, a corresponding bit in the hazard bit vector is set. During garbage collection, the collector will check the hazard bit vector before freeing any object. If the bit is set, the object is considered live and not freed.

Key Requirements:

  • HazardPointer<T> struct: This struct should wrap a raw pointer (*mut T) and store a unique hazard ID.
  • new(ptr: *mut T, hazard_id: usize): Constructor for HazardPointer. Takes a raw pointer and a unique hazard ID.
  • as_ptr() -> *mut T: Returns the raw pointer wrapped by the HazardPointer.
  • mark(hazard_id: usize): Marks the object pointed to by this HazardPointer as potentially live by setting the corresponding bit in the global hazard bit vector.
  • unmark(hazard_id: usize): Unmarks the object pointed to by this HazardPointer as potentially live by clearing the corresponding bit in the global hazard bit vector.
  • Global Hazard Bit Vector: A static Vec<bool> representing the hazard bit vector. Its size should be configurable (see Constraints).
  • Unique Hazard IDs: Each HazardPointer must have a unique ID. You'll need a mechanism to ensure this uniqueness.

Expected Behavior:

  • HazardPointer should allow safe access to the underlying raw pointer.
  • mark() should correctly set the corresponding bit in the hazard bit vector.
  • unmark() should correctly clear the corresponding bit in the hazard bit vector.
  • The hazard bit vector should be initialized with all bits set to false.
  • Hazard IDs should be unique within the lifetime of the program.

Edge Cases to Consider:

  • What happens if mark() or unmark() are called with an invalid hazard_id (one that's out of bounds for the hazard bit vector)? (For simplicity, you can panic in this case).
  • What happens if the raw pointer passed to new() is null? (For simplicity, you can panic in this case).
  • Consider thread safety. While this challenge doesn't explicitly require thread safety, be mindful of potential race conditions if this were to be used in a concurrent environment.

Examples

Example 1:

Input:
hazard_bit_vector_size = 10
ptr = unsafe { std::ptr::null_mut() as *mut i32 }; // Null pointer
hazard_id = 5;

Output:
Panic! (due to null pointer)

Explanation: The constructor should panic if a null pointer is provided.

Example 2:

Input:
hazard_bit_vector_size = 10
ptr = unsafe { std::mem::transmute(5 as usize, *mut i32) }; // Valid pointer
hazard_id = 2;

let hp = HazardPointer::new(ptr, hazard_id);
hp.mark(hazard_id);

Output:
hazard_bit_vector[2] is now true.

Explanation: The HazardPointer is created, and the corresponding bit in the hazard bit vector is set to true when mark() is called.

Example 3:

Input:
hazard_bit_vector_size = 10
ptr = unsafe { std::mem::transmute(5 as usize, *mut i32) }; // Valid pointer
hazard_id = 7;

let hp = HazardPointer::new(ptr, hazard_id);
hp.unmark(hazard_id);

Output:
hazard_bit_vector[7] is now false.

Explanation: The HazardPointer is created, and the corresponding bit in the hazard bit vector is set to false when unmark() is called.

Constraints

  • hazard_bit_vector_size: The size of the hazard bit vector should be configurable and at least 16. You can use a constant for this.
  • Hazard ID Range: Hazard IDs must be within the range 0..hazard_bit_vector_size.
  • Uniqueness of Hazard IDs: Each HazardPointer created must have a unique hazard ID. You can achieve this by maintaining a counter or using a more sophisticated allocation strategy.
  • Performance: While not a primary focus, avoid unnecessary allocations or copies.

Notes

  • This is a simplified implementation of hazard pointers. Real-world implementations are often more complex and optimized.
  • Consider using a Mutex to protect the hazard bit vector if you intend to use this in a multi-threaded environment. This challenge does not explicitly require thread safety, but it's a good practice to keep in mind.
  • The hazard bit vector is a simple Vec<bool>. In a production system, you might use a more efficient bitset implementation.
  • Error handling is simplified for clarity. In a production environment, you would want to handle errors more gracefully.
  • Focus on the core functionality of marking and unmarking objects. Don't worry about the details of the garbage collector itself.
  • The unsafe blocks are necessary due to the use of raw pointers. Be very careful when working with raw pointers in Rust.
Loading editor...
rust