Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ use ic_stable_structures::{
BTreeMap, DefaultMemoryImpl,
};
let mem_mgr = MemoryManager::init(DefaultMemoryImpl::default());
let mut map_a: BTreeMap<u64, u8, _> = BTreeMap::init(mem_mgr.get(MemoryId::new(0)));
let mut map_b: BTreeMap<u64, u8, _> = BTreeMap::init(mem_mgr.get(MemoryId::new(1)));
let (mem_id_a, mem_id_b) = (MemoryId::new(0), MemoryId::new(1));
let mut map_a: BTreeMap<u64, u8, _> = BTreeMap::init(mem_mgr.get(mem_id_a));
let mut map_b: BTreeMap<u64, u8, _> = BTreeMap::init(mem_mgr.get(mem_id_b));

map_a.insert(1, b'A');
map_b.insert(1, b'B');
Expand Down
5 changes: 3 additions & 2 deletions docs/src/concepts/memory-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ use ic_stable_structures::{
let mem_mgr = MemoryManager::init(DefaultMemoryImpl::default());

// Create two separate BTreeMaps, each with its own virtual memory
let mut map_a: BTreeMap<u64, u8, _> = BTreeMap::init(mem_mgr.get(MemoryId::new(0)));
let mut map_b: BTreeMap<u64, u8, _> = BTreeMap::init(mem_mgr.get(MemoryId::new(1)));
let (mem_id_a, mem_id_b) = (MemoryId::new(0), MemoryId::new(1));
let mut map_a: BTreeMap<u64, u8, _> = BTreeMap::init(mem_mgr.get(mem_id_a));
let mut map_b: BTreeMap<u64, u8, _> = BTreeMap::init(mem_mgr.get(mem_id_b));

// Demonstrate independent operation of the two maps
map_a.insert(1, b'A');
Expand Down
22 changes: 13 additions & 9 deletions docs/src/concepts/memory-trait.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,15 @@ Here's how to initialize a stable `BTreeMap` using `DefaultMemoryImpl`:

```rust
use ic_stable_structures::{BTreeMap, DefaultMemoryImpl};
let mut map: BTreeMap<u64, u64, _> = BTreeMap::init(DefaultMemoryImpl::default());
let mut map: BTreeMap<u64, String, _> = BTreeMap::init(DefaultMemoryImpl::default());

map.insert(1, "hello".to_string());
assert_eq!(map.get(&1), Some("hello".to_string()));
```

```admonish warning ""
**Important**: Stable structures cannot share memories.
Each memory must be dedicated to a single stable structure.
**⚠️ CRITICAL:** Stable structures **MUST NOT** share memories!
Each memory must belong to only one stable structure.
```

While the above example works correctly, it demonstrates a potential issue: the `BTreeMap` will use the entire stable memory.
Expand All @@ -69,15 +72,16 @@ For example, the following code will fail in a canister:

```rust
use ic_stable_structures::{BTreeMap, DefaultMemoryImpl};
let mut map_1: BTreeMap<u64, u64, _> = BTreeMap::init(DefaultMemoryImpl::default());
let mut map_2: BTreeMap<u64, u64, _> = BTreeMap::init(DefaultMemoryImpl::default());
let mut map_a: BTreeMap<u64, u8, _> = BTreeMap::init(DefaultMemoryImpl::default());
let mut map_b: BTreeMap<u64, u8, _> = BTreeMap::init(DefaultMemoryImpl::default());

map_1.insert(1, 2);
map_2.insert(1, 3);
assert_eq!(map_1.get(&1), Some(2)); // This assertion fails.
map_a.insert(1, b'A');
map_b.insert(1, b'B');
assert_eq!(map_a.get(&1), Some(b'A')); // ❌ FAILS: Returns b'B' due to shared memory!
assert_eq!(map_b.get(&1), Some(b'B')); // ✅ Succeeds, but corrupted map_a
```

The code fails because both `map_1` and `map_2` are using the same stable memory.
The code fails because both `map_a` and `map_b` are using the same stable memory.
This causes changes in one map to affect or corrupt the other.

To solve this problem, the library provides the [MemoryManager](./memory-manager.md), which creates up to 255 virtual memories from a single memory instance.
Expand Down
36 changes: 17 additions & 19 deletions src/btreemap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,22 +112,23 @@ const PAGE_SIZE_VALUE_MARKER: u32 = u32::MAX;
///
/// ## Multiple BTreeMaps and Memory Management
///
/// **Important**: Each stable structure requires its own designated memory region. Attempting to
/// initialize multiple structures with the same memory will lead to data corruption.
/// > **⚠️ CRITICAL:** Stable structures **MUST NOT** share memories!
/// > Each memory must belong to only one stable structure.
///
/// ### What NOT to do:
///
/// ```rust,no_run
/// use ic_stable_structures::{BTreeMap, DefaultMemoryImpl};
///
/// // ERROR: Using the same memory for multiple BTreeMaps will corrupt data
/// let mut map_1: BTreeMap<u64, String, _> = BTreeMap::init(DefaultMemoryImpl::default());
/// let mut map_2: BTreeMap<u64, String, _> = BTreeMap::init(DefaultMemoryImpl::default());
/// let mut map_a: BTreeMap<u64, u8, _> = BTreeMap::init(DefaultMemoryImpl::default());
/// let mut map_b: BTreeMap<u64, u8, _> = BTreeMap::init(DefaultMemoryImpl::default());
///
/// map_1.insert(1, "two".to_string());
/// map_2.insert(1, "three".to_string());
/// // This assertion would fail: changes to map_2 corrupt map_1's data
/// assert_eq!(map_1.get(&1), Some("two".to_string()));
/// map_a.insert(1, b'A');
/// map_b.insert(1, b'B');
/// // This assertion would fail: changes to map_b corrupt map_a's data
/// assert_eq!(map_a.get(&1), Some(b'A')); // ❌ FAILS: Returns b'B' due to shared memory!
/// assert_eq!(map_b.get(&1), Some(b'B')); // ✅ Succeeds, but corrupted map_a
/// ```
///
/// ### Correct approach using MemoryManager:
Expand All @@ -137,18 +138,15 @@ const PAGE_SIZE_VALUE_MARKER: u32 = u32::MAX;
/// memory_manager::{MemoryId, MemoryManager},
/// BTreeMap, DefaultMemoryImpl,
/// };
/// let mem_mgr = MemoryManager::init(DefaultMemoryImpl::default());
/// let (mem_id_a, mem_id_b) = (MemoryId::new(0), MemoryId::new(1));
/// let mut map_a: BTreeMap<u64, u8, _> = BTreeMap::init(mem_mgr.get(mem_id_a));
/// let mut map_b: BTreeMap<u64, u8, _> = BTreeMap::init(mem_mgr.get(mem_id_b));
///
/// // Initialize the memory manager with a single memory
/// let memory_manager = MemoryManager::init(DefaultMemoryImpl::default());
///
/// // Get separate virtual memories for each BTreeMap
/// let mut map_1: BTreeMap<u64, String, _> = BTreeMap::init(memory_manager.get(MemoryId::new(0)));
/// let mut map_2: BTreeMap<u64, String, _> = BTreeMap::init(memory_manager.get(MemoryId::new(1)));
///
/// map_1.insert(1, "two".to_string());
/// map_2.insert(1, "three".to_string());
/// // Now this works as expected
/// assert_eq!(map_1.get(&1), Some("two".to_string()));
/// map_a.insert(1, b'A');
/// map_b.insert(1, b'B');
/// assert_eq!(map_a.get(&1), Some(b'A')); // ✅ Succeeds: Each map has its own memory
/// assert_eq!(map_b.get(&1), Some(b'B')); // ✅ Succeeds: No data corruption
/// ```
///
/// The [`MemoryManager`](crate::memory_manager::MemoryManager) creates up to 255 virtual memories
Expand Down
22 changes: 11 additions & 11 deletions src/memory_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,22 @@
//! let mem_mgr = MemoryManager::init(DefaultMemoryImpl::default());
//!
//! // Create different memories, each with a unique ID.
//! let memory_0 = mem_mgr.get(MemoryId::new(0));
//! let memory_1 = mem_mgr.get(MemoryId::new(1));
//! let memory_a = mem_mgr.get(MemoryId::new(0));
//! let memory_b = mem_mgr.get(MemoryId::new(1));
//!
//! // Each memory can be used independently.
//! memory_0.grow(1);
//! memory_0.write(0, &[1, 2, 3]);
//! memory_a.grow(1);
//! memory_a.write(0, &[1, 2, 3]);
//!
//! memory_1.grow(1);
//! memory_1.write(0, &[4, 5, 6]);
//! memory_b.grow(1);
//! memory_b.write(0, &[4, 5, 6]);
//!
//! let mut bytes = vec![0; 3];
//! memory_0.read(0, &mut bytes);
//! memory_a.read(0, &mut bytes);
//! assert_eq!(bytes, vec![1, 2, 3]);
//!
//! let mut bytes = vec![0; 3];
//! memory_1.read(0, &mut bytes);
//! memory_b.read(0, &mut bytes);
//! assert_eq!(bytes, vec![4, 5, 6]);
//! ```
use crate::{
Expand Down Expand Up @@ -170,9 +170,9 @@ impl<M: Memory> MemoryManager<M> {
///
/// **Usage Pattern:**
/// ```rust,ignore
/// drop(map); // 1. Drop the structure object first
/// let pages = memory_manager.reclaim_memory(memory_id); // 2. Reclaim memory
/// let new_map = BTreeMap::new(memory_manager.get(memory_id)); // 3. Create new structure
/// drop(map); // 1. Drop the structure object first
/// let pages = mem_mgr.reclaim_memory(memory_id); // 2. Reclaim memory
/// let new_map = BTreeMap::new(mem_mgr.get(memory_id)); // 3. Create new structure
/// ```
///
/// **DANGER**: Using the original structure after reclamation causes data corruption.
Expand Down