├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── benches ├── device.rs └── storage.rs ├── grafana └── cannyls-dashboard.json └── src ├── block ├── aligned_bytes.rs └── mod.rs ├── deadline.rs ├── device ├── builder.rs ├── command.rs ├── long_queue_policy.rs ├── mod.rs ├── probabilistic.rs ├── queue.rs ├── request.rs └── thread.rs ├── error.rs ├── lib.rs ├── lump.rs ├── metrics.rs ├── nvm ├── file.rs ├── memory.rs ├── mod.rs └── shared_memory.rs └── storage ├── address.rs ├── allocator ├── data_portion_allocator.rs ├── free_portion.rs └── mod.rs ├── builder.rs ├── data_region.rs ├── header.rs ├── index.rs ├── journal ├── header.rs ├── mod.rs ├── nvm_buffer.rs ├── options.rs ├── record.rs ├── region.rs └── ring_buffer.rs ├── mod.rs └── portion.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | 8 | before_script: 9 | - rustup component add clippy-preview 10 | 11 | script: 12 | - cargo test 13 | - cargo clippy --lib --tests 14 | 15 | matrix: 16 | allow_failures: 17 | - rust: beta 18 | - rust: nightly 19 | include: 20 | - rust: nightly 21 | script: cargo bench 22 | 23 | env: 24 | global: 25 | - RUSTFLAGS="-D warnings" 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contribution Guide 2 | ================== 3 | 4 | Coding Standards 5 | ----------------- 6 | 7 | You must apply the latest `rustfmt` (formtter) and `clippy` (linter) as follows: 8 | 9 | ```console 10 | $ cargo fmt --all 11 | $ cargo clippy 12 | ``` 13 | 14 | If any warnings are emitted, you need to fix them. 15 | 16 | For other coding styles that can not be covered with the above tools, follow [Rust's Style Guildelines] as much as possible. 17 | 18 | [Rust's Style Guildelines]: https://doc.rust-lang.org/1.0.0/style/README.html 19 | 20 | 21 | Pull Request 22 | ------------ 23 | 24 | Before creating a pull request, please make sure that the unit tests and benchmarks pass in your environment: 25 | 26 | ```console 27 | $ cargo test 28 | $ cargo +nightly bench --features device 29 | ``` 30 | 31 | 32 | Versioning 33 | ---------- 34 | 35 | This project adopts [Semantic Versioning]. 36 | 37 | [Semantic Versioning]: https://semver.org/ 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2018" 3 | name = "cannyls" 4 | version = "0.10.0" 5 | authors = ["The FrugalOS Developers"] 6 | description = "Embedded persistent key-value storage optimized for random-access workload and huge-capacity HDD" 7 | homepage = "https://github.com/frugalos/cannyls" 8 | repository = "https://github.com/frugalos/cannyls" 9 | readme = "README.md" 10 | categories = ["database-implementations"] 11 | license = "MIT" 12 | 13 | [badges] 14 | travis-ci = {repository = "frugalos/cannyls"} 15 | 16 | [features] 17 | default = ["futures", "fibers"] 18 | 19 | device = ["futures", "fibers"] 20 | 21 | [dependencies] 22 | adler32 = "1" 23 | byteorder = { version = "1", features = ["i128"] } 24 | libc = "0.2" 25 | prometrics = "0.1" 26 | trackable = "0.2" 27 | uuid = { version = "0.7", features = ["v4"] } 28 | slog = "2" 29 | 30 | [dependencies.futures] 31 | version = "0.1" 32 | optional = true 33 | 34 | [dependencies.fibers] 35 | version = "0.1" 36 | optional = true 37 | 38 | [dev-dependencies] 39 | fibers_global = "0.1" 40 | tempdir = "0.3" 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 DWANGO Co., Ltd. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cannyls 2 | ======= 3 | 4 | [![Crates.io: cannyls](https://img.shields.io/crates/v/cannyls.svg)](https://crates.io/crates/cannyls) 5 | [![Documentation](https://docs.rs/cannyls/badge.svg)](https://docs.rs/cannyls) 6 | [![Build Status](https://travis-ci.org/frugalos/cannyls.svg?branch=master)](https://travis-ci.org/frugalos/cannyls) 7 | [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 8 | 9 | CannyLS is an embedded and persistent key-value storage optimized for random-access workload and huge-capacity HDD. 10 | 11 | CannyLS mainly has following features: 12 | - A local storage for storing objects that called as ["lump"][lump]: 13 | - Basically, a lump is a simple key-value entry 14 | - The distinctive properties are that the key is **fixed length (128 bits)** and suited for storing a relatively large size value (e.g., several MB) 15 | - Provides simple functionalities: 16 | - Basically, the only operations you need to know are `PUT`, `GET` and `DELETE` 17 | - But it supports [deadline based I/O scheduling] as an advanced feature 18 | - Optimized for random-access workload on huge-capacity HDD (up to 512 TB): 19 | - See [Benchmark Results] for more details about performance 20 | - Aiming to provide predictable and stable read/write latency: 21 | - There are (nearly) strict upper bounds about the number of disk accesses issued when executing operations 22 | - One disk access when `PUT` and `DELETE`, and two when `PUT` 23 | - There are no background processings like compaction and stop-the-world GC which may block normal operations for a long time 24 | - For eliminating overhead and uncertainty, CannyLS has no caching layer: 25 | - It uses [Direct I/O] for bypassing OS layer caching (e.g., page cache) 26 | - If you need any caching layer, it is your responsibility to implement it 27 | - Detailed metrics are exposed using [Prometheus] 28 | 29 | See [Wiki] for more details about CannyLS. 30 | 31 | [lump]: https://github.com/frugalos/cannyls/wiki/Terminology#lump 32 | [Benchmark Results]: https://github.com/frugalos/cannyls/wiki/Benchmark 33 | [Prometheus]: https://prometheus.io/ 34 | [deadline based I/O scheduling]: https://github.com/frugalos/cannyls/wiki/I-O-Scheduling-based-on-Request-Deadlines 35 | [Direct I/O]: https://github.com/frugalos/cannyls/wiki/Terminology#ダイレクトio 36 | [Wiki]: https://github.com/frugalos/cannyls/wiki 37 | 38 | 39 | Documentation 40 | ------------- 41 | 42 | - [Rustdoc](https://docs.rs/cannyls) 43 | - [Wiki (Japanese only)][Wiki] 44 | -------------------------------------------------------------------------------- /benches/device.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | extern crate byteorder; 3 | extern crate cannyls; 4 | extern crate futures; 5 | extern crate test; 6 | #[macro_use] 7 | extern crate trackable; 8 | 9 | use cannyls::device::DeviceBuilder; 10 | use cannyls::lump::{LumpData, LumpId}; 11 | use cannyls::nvm::MemoryNvm; 12 | use cannyls::storage::StorageBuilder; 13 | use cannyls::{Error, Result}; 14 | use futures::Future; 15 | use test::Bencher; 16 | 17 | fn id(id: usize) -> LumpId { 18 | LumpId::new(id as u128) 19 | } 20 | 21 | fn wait>(mut f: F) -> Result<()> { 22 | while !track!(f.poll())?.is_ready() {} 23 | Ok(()) 24 | } 25 | 26 | #[bench] 27 | fn memory_put_small(b: &mut Bencher) { 28 | let nvm = MemoryNvm::new(vec![0; 1024 * 1024 * 1024]); 29 | let storage = track_try_unwrap!(StorageBuilder::new().journal_region_ratio(0.99).create(nvm)); 30 | let device = DeviceBuilder::new().spawn(|| Ok(storage)); 31 | let d = device.handle(); 32 | let _ = wait(d.request().wait_for_running().list()); // デバイスの起動を待機 33 | 34 | let mut i = 0; 35 | let data = LumpData::new_embedded("foo".into()).unwrap(); 36 | b.iter(|| { 37 | track_try_unwrap!(wait(d.request().put(id(i), data.clone()))); 38 | i += 1; 39 | }); 40 | } 41 | 42 | #[bench] 43 | fn memory_put_and_delete_small(b: &mut Bencher) { 44 | let nvm = MemoryNvm::new(vec![0; 1024 * 1024 * 1024]); 45 | let storage = track_try_unwrap!(StorageBuilder::new().journal_region_ratio(0.99).create(nvm)); 46 | let device = DeviceBuilder::new().spawn(|| Ok(storage)); 47 | let d = device.handle(); 48 | let _ = wait(d.request().wait_for_running().list()); // デバイスの起動を待機 49 | 50 | let mut i = 0; 51 | let data = LumpData::new_embedded("foo".into()).unwrap(); 52 | b.iter(|| { 53 | let future0 = d.request().put(id(i), data.clone()); 54 | let future1 = d.request().delete(id(i)); 55 | track_try_unwrap!(wait(future0)); 56 | track_try_unwrap!(wait(future1)); 57 | i += 1; 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /benches/storage.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | extern crate byteorder; 3 | extern crate cannyls; 4 | extern crate tempdir; 5 | extern crate test; 6 | #[macro_use] 7 | extern crate trackable; 8 | 9 | use cannyls::lump::{LumpData, LumpId}; 10 | use cannyls::nvm::{FileNvm, MemoryNvm}; 11 | use cannyls::storage::StorageBuilder; 12 | use tempdir::TempDir; 13 | use test::Bencher; 14 | 15 | fn id(id: usize) -> LumpId { 16 | LumpId::new(id as u128) 17 | } 18 | 19 | #[bench] 20 | fn file_put_small(b: &mut Bencher) { 21 | let dir = TempDir::new("cannyls_bench").unwrap(); 22 | let nvm = FileNvm::create(dir.path().join("bench.lusf"), 1024 * 1024 * 1024).unwrap(); 23 | let mut storage = 24 | track_try_unwrap!(StorageBuilder::new().journal_region_ratio(0.9).create(nvm)); 25 | let mut i = 0; 26 | let data = LumpData::new_embedded("foo".into()).unwrap(); 27 | b.iter(|| { 28 | track_try_unwrap!(storage.put(&id(i), &data)); 29 | i += 1; 30 | }); 31 | } 32 | 33 | #[bench] 34 | fn file_put_small_no_embedded(b: &mut Bencher) { 35 | let dir = TempDir::new("cannyls_bench").unwrap(); 36 | let nvm = FileNvm::create(dir.path().join("bench.lusf"), 1024 * 1024 * 1024).unwrap(); 37 | let mut storage = 38 | track_try_unwrap!(StorageBuilder::new().journal_region_ratio(0.5).create(nvm)); 39 | let mut i = 0; 40 | 41 | let data = storage.allocate_lump_data_with_bytes(b"foo").unwrap(); 42 | b.iter(|| { 43 | track_try_unwrap!(storage.put(&id(i), &data)); 44 | i += 1; 45 | }); 46 | } 47 | 48 | #[bench] 49 | fn memory_put_small(b: &mut Bencher) { 50 | let nvm = MemoryNvm::new(vec![0; 1024 * 1024 * 1024]); 51 | let mut storage = 52 | track_try_unwrap!(StorageBuilder::new().journal_region_ratio(0.99).create(nvm)); 53 | let mut i = 0; 54 | let data = LumpData::new_embedded("foo".into()).unwrap(); 55 | b.iter(|| { 56 | track_try_unwrap!(storage.put(&id(i), &data)); 57 | i += 1; 58 | }); 59 | } 60 | 61 | #[bench] 62 | fn memory_put_and_delete_small(b: &mut Bencher) { 63 | let nvm = MemoryNvm::new(vec![0; 1024 * 1024]); 64 | let mut storage = 65 | track_try_unwrap!(StorageBuilder::new().journal_region_ratio(0.99).create(nvm)); 66 | let mut i = 0; 67 | let data = LumpData::new_embedded("foo".into()).unwrap(); 68 | b.iter(|| { 69 | let id = id(i); 70 | track_try_unwrap!(storage.put(&id, &data)); 71 | track_try_unwrap!(storage.delete(&id)); 72 | i += 1; 73 | }); 74 | } 75 | 76 | #[bench] 77 | fn memory_put_and_delete_small_no_embedded(b: &mut Bencher) { 78 | let nvm = MemoryNvm::new(vec![0; 1024 * 1024]); 79 | let mut storage = 80 | track_try_unwrap!(StorageBuilder::new().journal_region_ratio(0.5).create(nvm)); 81 | let mut i = 0; 82 | let data = storage.allocate_lump_data_with_bytes(b"foo").unwrap(); 83 | b.iter(|| { 84 | let id = id(i); 85 | track_try_unwrap!(storage.put(&id, &data)); 86 | track_try_unwrap!(storage.delete(&id)); 87 | i += 1; 88 | }); 89 | } 90 | 91 | #[bench] 92 | fn memory_put_and_get_and_delete_small(b: &mut Bencher) { 93 | let nvm = MemoryNvm::new(vec![0; 1024 * 1024]); 94 | let mut storage = 95 | track_try_unwrap!(StorageBuilder::new().journal_region_ratio(0.99).create(nvm)); 96 | let mut i = 0; 97 | let data = LumpData::new_embedded("foo".into()).unwrap(); 98 | b.iter(|| { 99 | let id = id(i); 100 | track_try_unwrap!(storage.put(&id, &data)); 101 | track_try_unwrap!(storage.get(&id)); 102 | track_try_unwrap!(storage.delete(&id)); 103 | i += 1; 104 | }); 105 | } 106 | -------------------------------------------------------------------------------- /src/block/aligned_bytes.rs: -------------------------------------------------------------------------------- 1 | use crate::block::BlockSize; 2 | 3 | /// 指定のブロック境界に開始位置および終端位置が揃えられたバイト列. 4 | /// 5 | /// 内部的なメモリ管理の方法が異なるだけで、基本的には通常のバイト列(e.g., `&[u8]`)と同様に扱うことが可能. 6 | #[derive(Debug)] 7 | pub struct AlignedBytes { 8 | buf: Vec, 9 | offset: usize, 10 | len: usize, 11 | block_size: BlockSize, 12 | } 13 | unsafe impl Send for AlignedBytes {} 14 | impl AlignedBytes { 15 | /// 新しい`AlignedBytes`インスタンスを生成する. 16 | /// 17 | /// 結果のバイト列の初期値は未定義. 18 | pub fn new(size: usize, block_size: BlockSize) -> Self { 19 | // バッファの前後をブロック境界に合わせて十分なだけの領域を確保しておく 20 | let capacity = 21 | block_size.ceil_align(size as u64) as usize + block_size.as_u16() as usize - 1; 22 | 23 | // ゼロ埋めのコストを省くためにunsafeを使用 24 | let mut buf = Vec::with_capacity(capacity); 25 | unsafe { 26 | buf.set_len(capacity); 27 | } 28 | 29 | let offset = alignment_offset(&buf, block_size); 30 | AlignedBytes { 31 | buf, 32 | offset, 33 | len: size, 34 | block_size, 35 | } 36 | } 37 | 38 | /// `bytes`と等しい内容を持つ`AlignedBytes`インスタンスを生成する. 39 | pub fn from_bytes(bytes: &[u8], block_size: BlockSize) -> Self { 40 | let mut aligned = Self::new(bytes.len(), block_size); 41 | aligned.as_mut().copy_from_slice(bytes); 42 | aligned 43 | } 44 | 45 | /// このバイト列のブロックサイズを返す. 46 | pub fn block_size(&self) -> BlockSize { 47 | self.block_size 48 | } 49 | 50 | /// 長さを次のブロック境界に揃える. 51 | /// 52 | /// 既に揃っているなら何もしない. 53 | /// 54 | /// なお、このメソッドの呼び出し有無に関わらず、内部的なメモリレイアウト上は、 55 | /// 常に適切なアライメントが行われている. 56 | pub fn align(&mut self) { 57 | self.len = self.block_size.ceil_align(self.len as u64) as usize; 58 | } 59 | 60 | /// 指定サイズに切り詰める. 61 | /// 62 | /// `size`が、現在のサイズを超えている場合には何も行わない. 63 | pub fn truncate(&mut self, size: usize) { 64 | if size < self.len { 65 | self.len = size; 66 | } 67 | } 68 | 69 | /// `new_min_len`の次のブロック境界へのリサイズを行う. 70 | /// 71 | /// サイズ拡大時には、必要に応じて内部バッファの再アロケートが行われる. 72 | pub fn aligned_resize(&mut self, new_min_len: usize) { 73 | self.resize(new_min_len); 74 | self.align(); 75 | } 76 | 77 | /// リサイズを行う. 78 | /// 79 | /// サイズ拡大時には、必要に応じて内部バッファの再アロケートが行われる. 80 | pub fn resize(&mut self, new_len: usize) { 81 | let new_capacity = self.block_size.ceil_align(new_len as u64) as usize; 82 | if new_capacity > self.buf.len() - self.offset { 83 | let mut new_buf = vec![0; new_capacity + self.block_size.as_u16() as usize - 1]; 84 | let new_offset = alignment_offset(&new_buf, self.block_size); 85 | (&mut new_buf[new_offset..][..self.len]).copy_from_slice(self.as_ref()); 86 | 87 | self.buf = new_buf; 88 | self.offset = new_offset; 89 | } 90 | self.len = new_len; 91 | } 92 | 93 | /// バッファのキャパシティを返す. 94 | pub fn capacity(&self) -> usize { 95 | self.block_size.floor_align(self.buf.len() as u64) as usize 96 | } 97 | } 98 | impl std::ops::Deref for AlignedBytes { 99 | type Target = [u8]; 100 | fn deref(&self) -> &[u8] { 101 | &self.buf[self.offset..][..self.len] 102 | } 103 | } 104 | impl std::ops::DerefMut for AlignedBytes { 105 | fn deref_mut(&mut self) -> &mut [u8] { 106 | &mut self.buf[self.offset..][..self.len] 107 | } 108 | } 109 | impl AsRef<[u8]> for AlignedBytes { 110 | fn as_ref(&self) -> &[u8] { 111 | &*self 112 | } 113 | } 114 | impl AsMut<[u8]> for AlignedBytes { 115 | fn as_mut(&mut self) -> &mut [u8] { 116 | &mut *self 117 | } 118 | } 119 | impl Clone for AlignedBytes { 120 | fn clone(&self) -> Self { 121 | AlignedBytes::from_bytes(self.as_ref(), self.block_size) 122 | } 123 | } 124 | 125 | fn alignment_offset(buf: &[u8], block_size: BlockSize) -> usize { 126 | let ptr_usize: usize = buf.as_ptr() as usize; 127 | let aligned_ptr_usize = block_size.ceil_align(ptr_usize as u64) as usize; 128 | aligned_ptr_usize - ptr_usize 129 | } 130 | 131 | #[cfg(test)] 132 | mod tests { 133 | use trackable::result::TestResult; 134 | 135 | use super::super::BlockSize; 136 | use super::*; 137 | 138 | #[test] 139 | fn new_works() -> TestResult { 140 | let bytes = AlignedBytes::new(10, BlockSize::new(512)?); 141 | assert_eq!(bytes.len(), 10); 142 | assert_eq!(bytes.capacity(), 512); 143 | Ok(()) 144 | } 145 | 146 | #[test] 147 | fn from_bytes_works() -> TestResult { 148 | let bytes = AlignedBytes::from_bytes(b"foo", BlockSize::new(512)?); 149 | assert_eq!(bytes.as_ref(), b"foo"); 150 | assert_eq!(bytes.capacity(), 512); 151 | Ok(()) 152 | } 153 | 154 | #[test] 155 | fn align_works() -> TestResult { 156 | let mut bytes = AlignedBytes::new(10, BlockSize::new(512)?); 157 | assert_eq!(bytes.len(), 10); 158 | 159 | bytes.align(); 160 | assert_eq!(bytes.len(), 512); 161 | 162 | bytes.align(); 163 | assert_eq!(bytes.len(), 512); 164 | Ok(()) 165 | } 166 | 167 | #[test] 168 | fn truncate_works() -> TestResult { 169 | let mut bytes = AlignedBytes::new(10, BlockSize::new(512)?); 170 | assert_eq!(bytes.len(), 10); 171 | 172 | bytes.truncate(2); 173 | assert_eq!(bytes.len(), 2); 174 | 175 | bytes.truncate(3); 176 | assert_eq!(bytes.len(), 2); 177 | Ok(()) 178 | } 179 | 180 | #[test] 181 | fn aligned_resize_works() -> TestResult { 182 | let mut bytes = AlignedBytes::new(10, BlockSize::new(512)?); 183 | assert_eq!(bytes.len(), 10); 184 | 185 | bytes.aligned_resize(100); 186 | assert_eq!(bytes.len(), 512); 187 | Ok(()) 188 | } 189 | 190 | #[test] 191 | fn resize_works() -> TestResult { 192 | let mut bytes = AlignedBytes::new(10, BlockSize::new(512)?); 193 | assert_eq!(bytes.len(), 10); 194 | assert_eq!(bytes.capacity(), 512); 195 | 196 | bytes.resize(700); 197 | assert_eq!(bytes.len(), 700); 198 | assert_eq!(bytes.capacity(), 1024); 199 | 200 | bytes.resize(2); 201 | assert_eq!(bytes.len(), 2); 202 | assert_eq!(bytes.capacity(), 1024); 203 | Ok(()) 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/block/mod.rs: -------------------------------------------------------------------------------- 1 | //! ストレージやNVMのブロック(読み書きの際の最小単位)関連の構成要素. 2 | use crate::{ErrorKind, Result}; 3 | 4 | pub(crate) use self::aligned_bytes::AlignedBytes; 5 | 6 | mod aligned_bytes; 7 | 8 | /// [`Storage`]や[`NonVolatileMemory`]のブロックサイズを表現するための構造体. 9 | /// 10 | /// "ブロック"は、I/Oの最小単位であり、読み書き対象の領域およびその際に使用するバッファは、 11 | /// `BlockSize`によって指定された境界にアライメントされている必要がある. 12 | /// 13 | /// 指定されたサイズのブロック境界にアライメントを行うための補助メソッド群も提供している. 14 | /// 15 | /// [`Storage`]: ../storage/struct.Storage.html 16 | /// [`NonVolatileMemory`]: ../nvm/trait.NonVolatileMemory.html 17 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 18 | pub struct BlockSize(u16); 19 | impl BlockSize { 20 | /// 許容されるブロックサイズの最小値. 21 | /// 22 | /// 全てのブロックサイズは、この値の倍数である必要がある. 23 | /// 24 | /// また`BlockSize::default()`で使われる値でもある. 25 | pub const MIN: u16 = 512; 26 | 27 | /// 許容可能な最小のブロックサイズを持つ`BlockSize`インスタンスを返す. 28 | /// 29 | /// # Examples 30 | /// 31 | /// ``` 32 | /// use cannyls::block::BlockSize; 33 | /// 34 | /// assert_eq!(BlockSize::min().as_u16(), BlockSize::MIN); 35 | /// ``` 36 | pub fn min() -> Self { 37 | BlockSize(Self::MIN) 38 | } 39 | 40 | /// 指定された値のブロックサイズを表現する`BlockSize`インスタンスを生成する. 41 | /// 42 | /// # Errors 43 | /// 44 | /// 以下の場合には、種類が`ErrorKind::InvalidInput`のエラーが返される: 45 | /// 46 | /// - `block_size`が`BlockSize::MIN`未満 47 | /// - `block_size`が`BlockSize::MIN`の倍数ではない 48 | /// 49 | /// # Examples 50 | /// 51 | /// ``` 52 | /// use cannyls::ErrorKind; 53 | /// use cannyls::block::BlockSize; 54 | /// 55 | /// assert_eq!(BlockSize::new(512).ok().map(|a| a.as_u16()), Some(512)); 56 | /// assert_eq!(BlockSize::new(4096).ok().map(|a| a.as_u16()), Some(4096)); 57 | /// 58 | /// assert_eq!(BlockSize::new(256).err().map(|e| *e.kind()), Some(ErrorKind::InvalidInput)); 59 | /// assert_eq!(BlockSize::new(513).err().map(|e| *e.kind()), Some(ErrorKind::InvalidInput)); 60 | /// ``` 61 | #[allow(clippy::new_ret_no_self)] 62 | pub fn new(block_size: u16) -> Result { 63 | track_assert!(block_size >= Self::MIN, ErrorKind::InvalidInput); 64 | track_assert_eq!(block_size % Self::MIN, 0, ErrorKind::InvalidInput); 65 | Ok(BlockSize(block_size)) 66 | } 67 | 68 | /// 指定位置より後方の最初のブロックサイズ位置を返す. 69 | /// 70 | /// # Examples 71 | /// 72 | /// ``` 73 | /// use cannyls::block::BlockSize; 74 | /// 75 | /// let block_size = BlockSize::new(512).unwrap(); 76 | /// assert_eq!(block_size.ceil_align(0), 0); 77 | /// assert_eq!(block_size.ceil_align(1), 512); 78 | /// assert_eq!(block_size.ceil_align(512), 512); 79 | /// ``` 80 | pub fn ceil_align(self, position: u64) -> u64 { 81 | let block_size = u64::from(self.0); 82 | (position + block_size - 1) / block_size * block_size 83 | } 84 | 85 | /// 指定位置より前方の最初のブロックサイズ位置を返す. 86 | /// 87 | /// # Examples 88 | /// 89 | /// ``` 90 | /// use cannyls::block::BlockSize; 91 | /// 92 | /// let block_size = BlockSize::new(512).unwrap(); 93 | /// assert_eq!(block_size.floor_align(0), 0); 94 | /// assert_eq!(block_size.floor_align(1), 0); 95 | /// assert_eq!(block_size.floor_align(512), 512); 96 | /// ``` 97 | pub fn floor_align(self, position: u64) -> u64 { 98 | let block_size = u64::from(self.0); 99 | (position / block_size) * block_size 100 | } 101 | 102 | /// ブロックサイズ値を`u16`に変換して返す. 103 | pub fn as_u16(self) -> u16 { 104 | self.0 105 | } 106 | 107 | /// このブロックサイズが`other`を包含しているかを確認する. 108 | /// 109 | /// "包含している"とは「`self`のブロックサイズが`other`のブロックサイズの倍数」であることを意味する. 110 | /// 111 | /// # Examples 112 | /// 113 | /// ``` 114 | /// use cannyls::block::BlockSize; 115 | /// 116 | /// let block_size = BlockSize::new(2048).unwrap(); 117 | /// assert!(block_size.contains(BlockSize::new(512).unwrap())); 118 | /// assert!(block_size.contains(BlockSize::new(1024).unwrap())); 119 | /// assert!(!block_size.contains(BlockSize::new(1536).unwrap())); 120 | /// ``` 121 | pub fn contains(self, other: BlockSize) -> bool { 122 | self.0 >= other.0 && self.0 % other.0 == 0 123 | } 124 | 125 | /// 指定位置がブロックサイズ境界に沿っているかどうかを判定する. 126 | /// 127 | /// # Examples 128 | /// 129 | /// ``` 130 | /// use cannyls::block::BlockSize; 131 | /// 132 | /// let block_size = BlockSize::new(512).unwrap(); 133 | /// assert!(block_size.is_aligned(0)); 134 | /// assert!(block_size.is_aligned(512)); 135 | /// assert!(block_size.is_aligned(1024)); 136 | /// 137 | /// assert!(!block_size.is_aligned(511)); 138 | /// assert!(!block_size.is_aligned(513)); 139 | /// ``` 140 | pub fn is_aligned(self, position: u64) -> bool { 141 | (position % u64::from(self.0)) == 0 142 | } 143 | } 144 | impl Default for BlockSize { 145 | fn default() -> Self { 146 | Self::min() 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/deadline.rs: -------------------------------------------------------------------------------- 1 | //! デバイスに対する各種操作のデッドライン. 2 | use std::time::Duration; 3 | 4 | /// 各種操作のデッドラインを表現するためのオブジェクト. 5 | /// 6 | /// # 注意 7 | /// 8 | /// ここでの"デッドライン"とは、厳密な上限ではなく、 9 | /// 「可能であれば、この時間までに実行してほしい」というヒントに過ぎないことは注意が必要。 10 | /// 11 | /// そのため、ハードリミットというよりは、操作間の優先順位を示すためのもの、という意味合いが強い 12 | /// (e.g., リソースに余裕がない場合には、デッドラインが近いものから優先的に処理される)。 13 | /// 14 | /// なお、デッドラインが等しい場合には、先にデバイスに到着したリクエストの方が優先される. 15 | #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] 16 | pub enum Deadline { 17 | /// 可能な限り早急に処理して欲しいリクエストに指定するデッドライン. 18 | /// 19 | /// `Within(Duration::from_secs(0))`よりも優先度は高い. 20 | Immediate, 21 | 22 | /// 指定された時間以内での実行を期待するリクエストに指定するデッドライン. 23 | /// 24 | /// このデッドライン指定は、デバイスにリクエストが届いたタイミングで、 25 | /// 絶対時刻(i.e., `SystemTime::now() + 指定尺`)に変換され、 26 | /// その値を元にリクエストのスケジューリングが行われる. 27 | /// 28 | /// そのため「14:10に発行された`Within(Duration::from_secs(5))`のリクエスト」と 29 | /// 「14:00に発行された`Within(Duration::from_secs(10)`のリクエスト」では、 30 | /// 後者の方が優先度が高く、より早く実行されることとなる. 31 | Within(Duration), 32 | 33 | /// 実行がいくら遅延されても問題がないようなリクエストに指定するデッドライン(デフォルト値). 34 | /// 35 | /// `Immediate`ないし`Within(_)`が指定されたリクエストが一つでもある間は、そちらが優先される. 36 | Infinity, 37 | } 38 | impl Default for Deadline { 39 | fn default() -> Self { 40 | Deadline::Infinity 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/device/builder.rs: -------------------------------------------------------------------------------- 1 | use prometrics::metrics::MetricBuilder; 2 | use std::time::Duration; 3 | 4 | use super::long_queue_policy::LongQueuePolicy; 5 | use super::thread::DeviceThread; 6 | use super::{Device, DeviceHandle}; 7 | use crate::nvm::NonVolatileMemory; 8 | use slog::{Discard, Logger}; 9 | use crate::storage::Storage; 10 | use crate::Result; 11 | 12 | /// `Device`のビルダ. 13 | #[derive(Debug, Clone)] 14 | pub struct DeviceBuilder { 15 | pub(crate) metrics: MetricBuilder, 16 | pub(crate) idle_threshold: Duration, 17 | pub(crate) max_queue_len: usize, 18 | pub(crate) max_keep_busy_duration: Duration, 19 | pub(crate) busy_threshold: usize, 20 | pub(crate) logger: Logger, 21 | pub(crate) long_queue_policy: LongQueuePolicy, 22 | } 23 | impl DeviceBuilder { 24 | /// デフォルト設定で`DeviceBuilder`インスタンスを生成する. 25 | pub fn new() -> Self { 26 | DeviceBuilder { 27 | metrics: MetricBuilder::new(), 28 | idle_threshold: Duration::from_millis(100), 29 | max_queue_len: 100_000, 30 | max_keep_busy_duration: Duration::from_secs(600), 31 | busy_threshold: 1_000, 32 | logger: Logger::root(Discard, o!()), 33 | long_queue_policy: LongQueuePolicy::default(), 34 | } 35 | } 36 | 37 | /// メトリクス用の共通設定を登録する. 38 | /// 39 | /// デフォルト値は`MetricBuilder::new()`. 40 | pub fn metrics(&mut self, metrics: MetricBuilder) -> &mut Self { 41 | self.metrics = metrics; 42 | self 43 | } 44 | 45 | /// デバイスが暇だと判定するための閾値(時間)を設定する. 46 | /// 47 | /// この値以上、新規コマンドを受信しない期間が続いた場合には、 48 | /// デバイス(用のスレッド)が空いていると判断されて、 49 | /// ストレージの補助タスクが実行されるようになる. 50 | /// 51 | /// デフォルト値は`Duration::from_millis(100)`. 52 | pub fn idle_threshold(&mut self, duration: Duration) -> &mut Self { 53 | self.idle_threshold = duration; 54 | self 55 | } 56 | 57 | /// デバイスの最大キュー長. 58 | /// 59 | /// これを超えた数のコマンドがデバイスのキューに溜まると、 60 | /// そのデバイスは致命的に過負荷であると判断され、 61 | /// `ErrorKind::DeviceBusy`を終了理由として停止する. 62 | /// 63 | /// デフォルト値は`100_000`. 64 | pub fn max_queue_len(&mut self, n: usize) -> &mut Self { 65 | self.max_queue_len = n; 66 | self 67 | } 68 | 69 | /// デバイスが最大継続ビジー時間. 70 | /// 71 | /// これを超えてビジーな状態が続いた場合には、何か異常が発生しているものと判断され、 72 | /// `ErrorKind::DeviceBusy`の終了理由でデバイスが停止する. 73 | /// 74 | /// ビジー状態かどうかの判断には`busy_threshold`の値を用いる. 75 | /// 76 | /// デフォルト値は`Duration::from_secs(600)`. 77 | pub fn max_keep_busy_duration(&mut self, duration: Duration) -> &mut Self { 78 | self.max_keep_busy_duration = duration; 79 | self 80 | } 81 | 82 | /// デバイスがビジー状態かどうかを判定するための閾値. 83 | /// 84 | /// コマンドのキューの長さがこの値を超えている場合には、 85 | /// そのデバイスはビジー状態であるとみなされる. 86 | /// 87 | /// デバイス側は、特定のコマンドの優先度等は分からないため、 88 | /// ビジー状態だからといってコマンドを拒否することはないが、 89 | /// この状態が一定(`max_keep_busy_duration`)以上継続した場合には、 90 | /// そのデバイスが何かしらの異常により過負荷に陥っていると判断して、 91 | /// 停止させられる. 92 | /// 93 | /// デフォルト値は`1_000`. 94 | pub fn busy_threshold(&mut self, n: usize) -> &mut Self { 95 | self.busy_threshold = n; 96 | self 97 | } 98 | 99 | /// デバイススレッド用の logger を登録する 100 | pub fn logger(&mut self, logger: Logger) -> &mut Self { 101 | self.logger = logger; 102 | self 103 | } 104 | 105 | /// LongQueuePolicy を登録する 106 | pub fn long_queue_policy(&mut self, long_queue_policy: LongQueuePolicy) -> &mut Self { 107 | self.long_queue_policy = long_queue_policy; 108 | self 109 | } 110 | 111 | /// 指定されたストレージを扱う`Device`を起動する. 112 | /// 113 | /// 起動したデバイス用に、一つの専用OSスレッドが割り当てられる. 114 | /// 115 | /// なお、スレッド起動後には、まず`init_storage()`が呼び出されて、 116 | /// ストレージインスタンスが生成される. 117 | /// 118 | /// # 注意 119 | /// 120 | /// 返り値の`Device`インスタンスが破棄されると、 121 | /// 起動したデバイススレッドも停止させられるので注意が必要. 122 | pub fn spawn(&self, init_storage: F) -> Device 123 | where 124 | F: FnOnce() -> Result> + Send + 'static, 125 | N: NonVolatileMemory + Send + 'static, 126 | { 127 | let (thread_handle, thread_monitor) = DeviceThread::spawn(self.clone(), init_storage); 128 | Device::new(thread_monitor, DeviceHandle(thread_handle)) 129 | } 130 | } 131 | impl Default for DeviceBuilder { 132 | fn default() -> Self { 133 | Self::new() 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/device/command.rs: -------------------------------------------------------------------------------- 1 | //! デバイスに発行されるコマンド群の定義. 2 | use fibers::sync::oneshot; 3 | use futures::{Future, Poll}; 4 | use std::ops::Range; 5 | use std::sync::mpsc::{Receiver, Sender}; 6 | use trackable::error::ErrorKindExt; 7 | 8 | use crate::deadline::Deadline; 9 | use crate::lump::{LumpData, LumpHeader, LumpId}; 10 | use crate::storage::StorageUsage; 11 | use crate::{Error, ErrorKind, Result}; 12 | 13 | pub type CommandSender = Sender; 14 | pub type CommandReceiver = Receiver; 15 | 16 | #[derive(Debug)] 17 | pub enum Command { 18 | Put(PutLump), 19 | Get(GetLump), 20 | Head(HeadLump), 21 | Delete(DeleteLump), 22 | DeleteRange(DeleteLumpRange), 23 | List(ListLump), 24 | ListRange(ListLumpRange), 25 | UsageRange(UsageLumpRange), 26 | Stop(StopDevice), 27 | } 28 | impl Command { 29 | pub fn deadline(&self) -> Deadline { 30 | match *self { 31 | Command::Put(ref c) => c.deadline, 32 | Command::Get(ref c) => c.deadline, 33 | Command::Head(ref c) => c.deadline, 34 | Command::Delete(ref c) => c.deadline, 35 | Command::DeleteRange(ref c) => c.deadline, 36 | Command::List(ref c) => c.deadline, 37 | Command::ListRange(ref c) => c.deadline, 38 | Command::UsageRange(ref c) => c.deadline, 39 | Command::Stop(ref c) => c.deadline, 40 | } 41 | } 42 | pub fn prioritized(&self) -> bool { 43 | match *self { 44 | Command::Put(ref c) => c.prioritized, 45 | Command::Get(ref c) => c.prioritized, 46 | Command::Head(ref c) => c.prioritized, 47 | Command::Delete(ref c) => c.prioritized, 48 | Command::DeleteRange(ref c) => c.prioritized, 49 | Command::List(ref c) => c.prioritized, 50 | Command::ListRange(ref c) => c.prioritized, 51 | Command::UsageRange(ref c) => c.prioritized, 52 | Command::Stop(ref c) => c.prioritized, 53 | } 54 | } 55 | pub fn failed(self, error: Error) { 56 | match self { 57 | Command::Put(c) => c.reply.send(Err(error)), 58 | Command::Get(c) => c.reply.send(Err(error)), 59 | Command::Head(c) => c.reply.send(Err(error)), 60 | Command::Delete(c) => c.reply.send(Err(error)), 61 | Command::DeleteRange(c) => c.reply.send(Err(error)), 62 | Command::List(c) => c.reply.send(Err(error)), 63 | Command::ListRange(c) => c.reply.send(Err(error)), 64 | Command::UsageRange(c) => c.reply.send(Err(error)), 65 | Command::Stop(_) => {} 66 | } 67 | } 68 | } 69 | 70 | /// `Result`の非同期版. 71 | #[derive(Debug)] 72 | pub struct AsyncResult(oneshot::Monitor); 73 | impl AsyncResult { 74 | #[allow(clippy::new_ret_no_self)] 75 | fn new() -> (AsyncReply, Self) { 76 | let (tx, rx) = oneshot::monitor(); 77 | (AsyncReply(tx), AsyncResult(rx)) 78 | } 79 | } 80 | impl Future for AsyncResult { 81 | type Item = T; 82 | type Error = Error; 83 | fn poll(&mut self) -> Poll { 84 | track!(self 85 | .0 86 | .poll() 87 | .map_err(|e| e.unwrap_or_else(|| ErrorKind::DeviceTerminated 88 | .cause("monitoring channel disconnected") 89 | .into()))) 90 | } 91 | } 92 | 93 | #[derive(Debug)] 94 | struct AsyncReply(oneshot::Monitored); 95 | impl AsyncReply { 96 | fn send(self, result: Result) { 97 | self.0.exit(result); 98 | } 99 | } 100 | 101 | #[derive(Debug)] 102 | pub struct PutLump { 103 | lump_id: LumpId, 104 | lump_data: LumpData, 105 | deadline: Deadline, 106 | prioritized: bool, 107 | journal_sync: bool, 108 | reply: AsyncReply, 109 | } 110 | impl PutLump { 111 | #[allow(clippy::new_ret_no_self)] 112 | pub fn new( 113 | lump_id: LumpId, 114 | lump_data: LumpData, 115 | deadline: Deadline, 116 | prioritized: bool, 117 | journal_sync: bool, 118 | ) -> (Self, AsyncResult) { 119 | let (reply, result) = AsyncResult::new(); 120 | let command = PutLump { 121 | lump_id, 122 | lump_data, 123 | deadline, 124 | prioritized, 125 | journal_sync, 126 | reply, 127 | }; 128 | (command, result) 129 | } 130 | pub fn lump_id(&self) -> &LumpId { 131 | &self.lump_id 132 | } 133 | pub fn lump_data(&self) -> &LumpData { 134 | &self.lump_data 135 | } 136 | pub fn do_sync_journal(&self) -> bool { 137 | self.journal_sync 138 | } 139 | 140 | pub fn reply(self, result: Result) { 141 | self.reply.send(result) 142 | } 143 | } 144 | 145 | #[derive(Debug)] 146 | pub struct GetLump { 147 | lump_id: LumpId, 148 | deadline: Deadline, 149 | prioritized: bool, 150 | reply: AsyncReply>, 151 | } 152 | impl GetLump { 153 | #[allow(clippy::new_ret_no_self)] 154 | pub fn new( 155 | lump_id: LumpId, 156 | deadline: Deadline, 157 | prioritized: bool, 158 | ) -> (Self, AsyncResult>) { 159 | let (reply, result) = AsyncResult::new(); 160 | let command = GetLump { 161 | lump_id, 162 | deadline, 163 | prioritized, 164 | reply, 165 | }; 166 | (command, result) 167 | } 168 | pub fn lump_id(&self) -> &LumpId { 169 | &self.lump_id 170 | } 171 | pub fn reply(self, result: Result>) { 172 | self.reply.send(result); 173 | } 174 | } 175 | 176 | #[derive(Debug)] 177 | pub struct HeadLump { 178 | lump_id: LumpId, 179 | deadline: Deadline, 180 | prioritized: bool, 181 | reply: AsyncReply>, 182 | } 183 | impl HeadLump { 184 | #[allow(clippy::new_ret_no_self)] 185 | pub fn new( 186 | lump_id: LumpId, 187 | deadline: Deadline, 188 | prioritized: bool, 189 | ) -> (Self, AsyncResult>) { 190 | let (reply, result) = AsyncResult::new(); 191 | let command = HeadLump { 192 | lump_id, 193 | deadline, 194 | prioritized, 195 | reply, 196 | }; 197 | (command, result) 198 | } 199 | pub fn lump_id(&self) -> &LumpId { 200 | &self.lump_id 201 | } 202 | pub fn reply(self, result: Result>) { 203 | self.reply.send(result); 204 | } 205 | } 206 | 207 | #[derive(Debug)] 208 | pub struct DeleteLump { 209 | lump_id: LumpId, 210 | deadline: Deadline, 211 | prioritized: bool, 212 | journal_sync: bool, 213 | reply: AsyncReply, 214 | } 215 | impl DeleteLump { 216 | #[allow(clippy::new_ret_no_self)] 217 | pub fn new( 218 | lump_id: LumpId, 219 | deadline: Deadline, 220 | prioritized: bool, 221 | journal_sync: bool, 222 | ) -> (Self, AsyncResult) { 223 | let (reply, result) = AsyncResult::new(); 224 | let command = DeleteLump { 225 | lump_id, 226 | deadline, 227 | prioritized, 228 | journal_sync, 229 | reply, 230 | }; 231 | (command, result) 232 | } 233 | pub fn lump_id(&self) -> &LumpId { 234 | &self.lump_id 235 | } 236 | pub fn do_sync_journal(&self) -> bool { 237 | self.journal_sync 238 | } 239 | pub fn reply(self, result: Result) { 240 | self.reply.send(result); 241 | } 242 | } 243 | 244 | #[derive(Debug)] 245 | pub struct DeleteLumpRange { 246 | range: Range, 247 | deadline: Deadline, 248 | prioritized: bool, 249 | journal_sync: bool, 250 | reply: AsyncReply>, 251 | } 252 | impl DeleteLumpRange { 253 | #[allow(clippy::new_ret_no_self)] 254 | pub fn new( 255 | range: Range, 256 | deadline: Deadline, 257 | prioritized: bool, 258 | journal_sync: bool, 259 | ) -> (Self, AsyncResult>) { 260 | let (reply, result) = AsyncResult::new(); 261 | let command = DeleteLumpRange { 262 | range, 263 | deadline, 264 | prioritized, 265 | journal_sync, 266 | reply, 267 | }; 268 | (command, result) 269 | } 270 | pub fn lump_range(&self) -> Range { 271 | self.range.clone() 272 | } 273 | pub fn do_sync_journal(&self) -> bool { 274 | self.journal_sync 275 | } 276 | pub fn reply(self, result: Result>) { 277 | self.reply.send(result); 278 | } 279 | } 280 | 281 | #[derive(Debug)] 282 | pub struct ListLump { 283 | deadline: Deadline, 284 | prioritized: bool, 285 | reply: AsyncReply>, 286 | } 287 | impl ListLump { 288 | #[allow(clippy::new_ret_no_self)] 289 | pub fn new(deadline: Deadline, prioritized: bool) -> (Self, AsyncResult>) { 290 | let (reply, result) = AsyncResult::new(); 291 | let command = ListLump { 292 | deadline, 293 | prioritized, 294 | reply, 295 | }; 296 | (command, result) 297 | } 298 | pub fn reply(self, result: Result>) { 299 | self.reply.send(result); 300 | } 301 | } 302 | 303 | #[derive(Debug)] 304 | pub struct ListLumpRange { 305 | range: Range, 306 | deadline: Deadline, 307 | prioritized: bool, 308 | reply: AsyncReply>, 309 | } 310 | impl ListLumpRange { 311 | #[allow(clippy::new_ret_no_self)] 312 | pub fn new( 313 | range: Range, 314 | deadline: Deadline, 315 | prioritized: bool, 316 | ) -> (Self, AsyncResult>) { 317 | let (reply, result) = AsyncResult::new(); 318 | let command = ListLumpRange { 319 | range, 320 | deadline, 321 | prioritized, 322 | reply, 323 | }; 324 | (command, result) 325 | } 326 | pub fn lump_range(&self) -> Range { 327 | self.range.clone() 328 | } 329 | pub fn reply(self, result: Result>) { 330 | self.reply.send(result); 331 | } 332 | } 333 | 334 | #[derive(Debug)] 335 | pub struct UsageLumpRange { 336 | range: Range, 337 | deadline: Deadline, 338 | prioritized: bool, 339 | reply: AsyncReply, 340 | } 341 | impl UsageLumpRange { 342 | #[allow(clippy::new_ret_no_self)] 343 | pub fn new( 344 | range: Range, 345 | deadline: Deadline, 346 | prioritized: bool, 347 | ) -> (Self, AsyncResult) { 348 | let (reply, result) = AsyncResult::new(); 349 | let command = UsageLumpRange { 350 | range, 351 | deadline, 352 | prioritized, 353 | reply, 354 | }; 355 | (command, result) 356 | } 357 | pub fn lump_range(&self) -> Range { 358 | self.range.clone() 359 | } 360 | pub fn reply(self, result: Result) { 361 | self.reply.send(result); 362 | } 363 | } 364 | 365 | #[derive(Debug)] 366 | pub struct StopDevice { 367 | deadline: Deadline, 368 | prioritized: bool, 369 | } 370 | impl StopDevice { 371 | pub fn new(deadline: Deadline, prioritized: bool) -> Self { 372 | StopDevice { 373 | deadline, 374 | prioritized, 375 | } 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /src/device/long_queue_policy.rs: -------------------------------------------------------------------------------- 1 | /// デバイスのキューが長い場合にどうするか 2 | /// default は RefuseNewRequests 3 | #[derive(Debug, Clone, PartialEq)] 4 | pub enum LongQueuePolicy { 5 | /// 一定の割合で新しいリクエストを拒否する 6 | /// 7 | /// ratio として拒否率 (0 以上 1 以下) を決める。 8 | /// 本当はもっと柔軟にやったほうがいいかもしれないが、当面固定値で問題ないだろうと思われる。 9 | /// 10 | /// TODO: 拒否率を真面目に計算する 11 | RefuseNewRequests { 12 | /// 拒否率 13 | ratio: f64, 14 | }, 15 | 16 | /// デバイスを止める 17 | Stop, 18 | 19 | /// 一定の割合でリクエストをドロップする。 20 | /// 21 | /// ratio としてドロップ率 (0 以上 1 以下) を決める。 22 | /// 本当はもっと柔軟にやったほうがいいかもしれないが、当面固定値で問題ないだろうと思われる。 23 | /// 24 | /// TODO: ドロップ率を真面目に計算する 25 | Drop { 26 | /// ドロップ率 27 | ratio: f64, 28 | }, 29 | } 30 | 31 | impl Default for LongQueuePolicy { 32 | fn default() -> Self { 33 | LongQueuePolicy::Stop 34 | } 35 | } 36 | 37 | impl LongQueuePolicy { 38 | /// 過負荷時にリクエストが実行されない確率を返す。 39 | /// 「実行されない」は、「拒否される」あるいは「ドロップされる」のいずれかを意味する。 40 | pub fn ratio(&self) -> f64 { 41 | match *self { 42 | LongQueuePolicy::RefuseNewRequests { ratio } => ratio, 43 | LongQueuePolicy::Stop => 0.0, 44 | LongQueuePolicy::Drop { ratio } => ratio, 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/device/probabilistic.rs: -------------------------------------------------------------------------------- 1 | use slog::Logger; 2 | use std::fmt::Debug; 3 | 4 | /// リクエストを落としたり落とさなかったりする決定を下すオブジェクト。 5 | pub(crate) trait Dropper: Debug { 6 | /// 次のリクエストを落とすなら true、落とさないなら false。 7 | /// 内部状態の変更も許されることに注意。 8 | fn will_drop(&mut self) -> bool; 9 | } 10 | 11 | /// あらかじめ指定した確率で落とす判定をする Dropper。 12 | #[derive(Debug)] 13 | pub(crate) struct ProbabilisticDropper { 14 | logger: Logger, 15 | ratio: f64, 16 | counter: f64, 17 | } 18 | 19 | impl ProbabilisticDropper { 20 | pub fn new(logger: Logger, ratio: f64) -> Self { 21 | Self { 22 | logger, 23 | ratio, 24 | counter: 0.0, 25 | } 26 | } 27 | } 28 | 29 | impl Dropper for ProbabilisticDropper { 30 | fn will_drop(&mut self) -> bool { 31 | debug!(self.logger, "old counter = {}",self.counter; "ratio" => self.ratio); 32 | self.counter += self.ratio; 33 | // counter >= 1 であれば、counter -= 1 を行って落とす。 34 | // 毎回 counter が ratio だけ増えて、たまに 1 減るので、counter の大きさがそこまで変わらないため 35 | // 1 減る回数の割合は ratio に収束する。 36 | debug!(self.logger, "new counter = {}", self.counter; "ratio" => self.ratio); 37 | if self.counter >= 1.0 { 38 | self.counter -= 1.0; 39 | return true; 40 | } 41 | false 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | 49 | use slog::Discard; 50 | 51 | #[test] 52 | fn probabilistic_dropper_works() { 53 | let logger = Logger::root(Discard, o!()); 54 | let ratio = 0.3; 55 | let mut dropper = ProbabilisticDropper::new(logger, ratio); 56 | let n = 10000; 57 | // n 回実行しておよそ 3 割のリクエストが drop されることを確かめる。 58 | let mut dropped = 0; 59 | for _ in 0..n { 60 | if dropper.will_drop() { 61 | dropped += 1; 62 | } 63 | } 64 | assert!(2900 < dropped); 65 | assert!(dropped < 3100); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/device/queue.rs: -------------------------------------------------------------------------------- 1 | use std::cmp; 2 | use std::collections::BinaryHeap; 3 | use std::time::Instant; 4 | 5 | use crate::deadline::Deadline; 6 | use crate::device::command::Command; 7 | 8 | /// デバイスに発行されたコマンド群の管理キュー. 9 | /// 10 | /// `LumpDevice`の実装インスタンスは、 各コマンドを同期的に処理するため、 11 | /// 並行的に発行されたコマンド群は、 順番が来るまでは、このキューによって保持されることになる. 12 | /// 13 | /// スケジューリングはデッドラインベースで行われ、 14 | /// デバイスに対して並行的に発行されたコマンド群は、 15 | /// そのデッドラインが近い順に実行される. 16 | /// 17 | /// なお、これが行うのはあくまでも並び替えのみで、 18 | /// デッドラインを過ぎたコマンドの破棄は行わない. 19 | #[derive(Debug)] 20 | pub struct DeadlineQueue { 21 | seqno: u64, 22 | heap: BinaryHeap, 23 | } 24 | impl DeadlineQueue { 25 | /// 新しい`DeadlineQueue`インスタンスを生成する. 26 | pub fn new() -> Self { 27 | DeadlineQueue { 28 | seqno: 0, 29 | heap: BinaryHeap::new(), 30 | } 31 | } 32 | 33 | /// 新しいコマンドをキューに追加する. 34 | pub fn push(&mut self, command: Command) { 35 | let deadline = AbsoluteDeadline::new(command.deadline()); 36 | let item = Item { 37 | seqno: self.seqno, 38 | command, 39 | deadline, 40 | }; 41 | self.heap.push(item); 42 | self.seqno += 1; 43 | } 44 | 45 | /// 次に処理するコマンドを取り出す. 46 | pub fn pop(&mut self) -> Option { 47 | self.heap.pop().map(|t| t.command) 48 | } 49 | 50 | /// キューに格納されている要素数を返す. 51 | pub fn len(&self) -> usize { 52 | self.heap.len() 53 | } 54 | } 55 | 56 | /// ヒープに格納する要素. 57 | #[derive(Debug)] 58 | struct Item { 59 | seqno: u64, // デッドラインが同じ要素をFIFO順で扱うためのシーケンス番号 60 | command: Command, 61 | deadline: AbsoluteDeadline, 62 | } 63 | impl PartialEq for Item { 64 | fn eq(&self, other: &Self) -> bool { 65 | self.seqno == other.seqno 66 | } 67 | } 68 | impl Eq for Item {} 69 | impl PartialOrd for Item { 70 | fn partial_cmp(&self, other: &Self) -> Option { 71 | Some(self.cmp(other)) 72 | } 73 | } 74 | impl Ord for Item { 75 | fn cmp(&self, other: &Self) -> cmp::Ordering { 76 | other 77 | .deadline 78 | .cmp(&self.deadline) 79 | .then_with(|| other.seqno.cmp(&self.seqno)) 80 | } 81 | } 82 | 83 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] 84 | enum AbsoluteDeadline { 85 | Immediate, 86 | Until(Instant), 87 | Infinity, 88 | } 89 | impl AbsoluteDeadline { 90 | fn new(relative: Deadline) -> Self { 91 | match relative { 92 | Deadline::Immediate => AbsoluteDeadline::Immediate, 93 | Deadline::Within(d) => AbsoluteDeadline::Until(Instant::now() + d), 94 | Deadline::Infinity => AbsoluteDeadline::Infinity, 95 | } 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use std::thread; 102 | use std::time::Duration; 103 | 104 | use super::*; 105 | use crate::deadline::Deadline; 106 | use crate::device::command::{Command, GetLump}; 107 | use crate::lump::LumpId; 108 | 109 | #[test] 110 | fn deadline_works() { 111 | let mut queue = DeadlineQueue::new(); 112 | 113 | queue.push(command(0, Deadline::Infinity)); 114 | queue.push(command(1, Deadline::Immediate)); 115 | queue.push(command(2, Deadline::Within(Duration::from_millis(1)))); 116 | thread::sleep(Duration::from_millis(5)); 117 | queue.push(command(3, Deadline::Within(Duration::from_millis(0)))); 118 | queue.push(command(4, Deadline::Immediate)); 119 | 120 | assert_eq!(queue.len(), 5); 121 | assert_eq!(lump_id(queue.pop()), Some(1)); 122 | assert_eq!(lump_id(queue.pop()), Some(4)); // デッドラインが同じならFIFO順 123 | assert_eq!(lump_id(queue.pop()), Some(2)); 124 | assert_eq!(lump_id(queue.pop()), Some(3)); 125 | assert_eq!(lump_id(queue.pop()), Some(0)); 126 | assert_eq!(lump_id(queue.pop()), None); 127 | assert_eq!(queue.len(), 0); 128 | } 129 | 130 | fn command(lump_id: u128, deadline: Deadline) -> Command { 131 | Command::Get(GetLump::new(LumpId::new(lump_id), deadline, false).0) 132 | } 133 | 134 | fn lump_id(command: Option) -> Option { 135 | command.map(|c| { 136 | if let Command::Get(c) = c { 137 | c.lump_id().as_u128() 138 | } else { 139 | unreachable!() 140 | } 141 | }) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/device/request.rs: -------------------------------------------------------------------------------- 1 | use futures::Future; 2 | use std::ops::Range; 3 | use trackable::error::ErrorKindExt; 4 | 5 | use super::thread::DeviceThreadHandle; 6 | use crate::deadline::Deadline; 7 | use crate::device::command::{self, Command}; 8 | use crate::device::DeviceStatus; 9 | use crate::lump::{LumpData, LumpHeader, LumpId}; 10 | use crate::storage::StorageUsage; 11 | use crate::{Error, ErrorKind, Result}; 12 | 13 | /// デバイスに対してリクエストを発行するためのビルダ. 14 | /// 15 | /// # 注意 16 | /// 17 | /// リクエストを発行した結果返される`Future`を効率的にポーリングするためには 18 | /// [`fibers`]を使用する必要がある。 19 | /// 20 | /// [`fibers`]: https://github.com/dwango/fibers-rs 21 | #[derive(Debug)] 22 | pub struct DeviceRequest<'a> { 23 | device: &'a DeviceThreadHandle, 24 | deadline: Option, 25 | max_queue_len: Option, 26 | wait_for_running: bool, 27 | enforce_journal_sync: bool, 28 | prioritized: bool, 29 | } 30 | impl<'a> DeviceRequest<'a> { 31 | pub(crate) fn new(device: &'a DeviceThreadHandle) -> Self { 32 | DeviceRequest { 33 | device, 34 | deadline: None, 35 | max_queue_len: None, 36 | wait_for_running: false, 37 | enforce_journal_sync: false, 38 | prioritized: false, 39 | } 40 | } 41 | 42 | /// Lumpを格納する. 43 | /// 44 | /// 新規追加の場合には`true`が、上書きの場合は`false`が、結果として返される. 45 | /// 46 | /// # 性能上の注意 47 | /// 48 | /// 引数に渡される`LumpData`が、`LumpData::new`関数経由で生成されている場合には、 49 | /// デバイスが管理しているストレージへの書き込み時に、 50 | /// データをストレージのブロック境界にアライメントするためのメモリコピーが余分に発生してしまう. 51 | /// それを避けたい場合には、`DeviceHandle::allocate_lump_data`メソッドを使用して`LumpData`を生成すると良い. 52 | pub fn put( 53 | &self, 54 | lump_id: LumpId, 55 | lump_data: LumpData, 56 | ) -> impl Future { 57 | let deadline = self.deadline.unwrap_or_default(); 58 | let prioritized = self.prioritized; 59 | let (command, response) = command::PutLump::new( 60 | lump_id, 61 | lump_data, 62 | deadline, 63 | prioritized, 64 | self.enforce_journal_sync, 65 | ); 66 | self.send_command(Command::Put(command)); 67 | response 68 | } 69 | 70 | /// Lumpを取得する. 71 | pub fn get(&self, lump_id: LumpId) -> impl Future, Error = Error> { 72 | let deadline = self.deadline.unwrap_or_default(); 73 | let prioritized = self.prioritized; 74 | 75 | let (command, response) = command::GetLump::new(lump_id, deadline, prioritized); 76 | self.send_command(Command::Get(command)); 77 | response 78 | } 79 | 80 | /// Lumpのヘッダを取得する. 81 | pub fn head(&self, lump_id: LumpId) -> impl Future, Error = Error> { 82 | let deadline = self.deadline.unwrap_or_default(); 83 | let prioritized = self.prioritized; 84 | 85 | let (command, response) = command::HeadLump::new(lump_id, deadline, prioritized); 86 | self.send_command(Command::Head(command)); 87 | response 88 | } 89 | 90 | /// Lumpを削除する. 91 | /// 92 | /// 指定されたlumpが存在した場合には`true`が、しなかった場合には`false`が、結果として返される. 93 | pub fn delete(&self, lump_id: LumpId) -> impl Future { 94 | let deadline = self.deadline.unwrap_or_default(); 95 | let prioritized = self.prioritized; 96 | 97 | let (command, response) = 98 | command::DeleteLump::new(lump_id, deadline, prioritized, self.enforce_journal_sync); 99 | self.send_command(Command::Delete(command)); 100 | response 101 | } 102 | 103 | /// Lumpを範囲オブジェクトを用いて削除する. 104 | /// 105 | /// 返り値のvectorは、引数rangeに含まれるlump idのうち、 106 | /// 対応するlump dataが存在して実際に削除されたもの全体を表す。 107 | pub fn delete_range( 108 | &self, 109 | range: Range, 110 | ) -> impl Future, Error = Error> { 111 | let deadline = self.deadline.unwrap_or_default(); 112 | let prioritized = self.prioritized; 113 | 114 | let (command, response) = 115 | command::DeleteLumpRange::new(range, deadline, prioritized, self.enforce_journal_sync); 116 | self.send_command(Command::DeleteRange(command)); 117 | response 118 | } 119 | 120 | /// 保存されているlump一覧を取得する. 121 | /// 122 | /// # 注意 123 | /// 124 | /// 例えば巨大なHDDを使用している場合には、lumpの数が数百万以上になることもあるため、 125 | /// このメソッドは呼び出す際には注意が必要. 126 | pub fn list(&self) -> impl Future, Error = Error> { 127 | let deadline = self.deadline.unwrap_or_default(); 128 | let prioritized = self.prioritized; 129 | 130 | let (command, response) = command::ListLump::new(deadline, prioritized); 131 | self.send_command(Command::List(command)); 132 | response 133 | } 134 | 135 | /// 範囲を指定してlump一覧を取得する. 136 | /// 137 | pub fn list_range( 138 | &self, 139 | range: Range, 140 | ) -> impl Future, Error = Error> { 141 | let deadline = self.deadline.unwrap_or_default(); 142 | let prioritized = self.prioritized; 143 | 144 | let (command, response) = command::ListLumpRange::new(range, deadline, prioritized); 145 | self.send_command(Command::ListRange(command)); 146 | response 147 | } 148 | 149 | /// 範囲を指定してlump数を取得する. 150 | /// 151 | pub fn usage_range( 152 | &self, 153 | range: Range, 154 | ) -> impl Future { 155 | let deadline = self.deadline.unwrap_or_default(); 156 | let prioritized = self.prioritized; 157 | 158 | let (command, response) = command::UsageLumpRange::new(range, deadline, prioritized); 159 | self.send_command(Command::UsageRange(command)); 160 | response 161 | } 162 | 163 | /// デバイスを停止する. 164 | /// 165 | /// 停止は重要な操作であり、実行は`Device`インスタンスの保持者に制限したいので、 166 | /// このメソッドは`crate`のみを公開範囲とする. 167 | pub(crate) fn stop(&self) { 168 | let deadline = self.deadline.unwrap_or_default(); 169 | let prioritized = self.prioritized; 170 | 171 | let command = command::StopDevice::new(deadline, prioritized); 172 | self.send_command(Command::Stop(command)); 173 | } 174 | 175 | /// 要求のデッドラインを設定する. 176 | /// 177 | /// デフォルト値は`Deadline::Infinity`. 178 | pub fn deadline(&mut self, deadline: Deadline) -> &mut Self { 179 | self.deadline = Some(deadline); 180 | self 181 | } 182 | 183 | /// [ジャーナルバッファ]をディスクへ書き出す。 184 | /// 185 | /// [ジャーナルバッファ]は[journal_sync_interval]に基づき 186 | /// 自動でディスク上に書き出されるが、 187 | /// このメソッドを呼ぶことで自動書き出しを待たずに 188 | /// その場での書き出しを強制することができる。 189 | /// 190 | /// [journal_sync_interval]: ../storage/struct.StorageBuilder.html#method.journal_sync_interval 191 | /// [ジャーナルバッファ]: https://github.com/frugalos/cannyls/wiki/Journal-Memory-Buffer 192 | pub fn journal_sync(&mut self) -> &mut Self { 193 | self.enforce_journal_sync = true; 194 | self 195 | } 196 | 197 | /// デバイスのキューの最大長を指定する. 198 | /// 199 | /// もし要求発行時に、デバイスのキューの長さがこの値を超えている場合には、 200 | /// `ErrorKind::DeviceBusy`エラーが返される. 201 | /// 202 | /// デフォルトは無制限. 203 | pub fn max_queue_len(&mut self, max: usize) -> &mut Self { 204 | self.max_queue_len = Some(max); 205 | self 206 | } 207 | 208 | /// デバイスが起動処理中の場合には、その完了を待つように指示する. 209 | /// 210 | /// デフォルトでは、起動処理中にリクエストが発行された場合には、 211 | /// 即座に`ErrorKind::DeviceBusy`エラーが返される. 212 | /// 213 | /// `wait_for_running()`が呼び出された場合には、 214 | /// リクエストはキューに追加され、デバイス起動後に順次処理される. 215 | pub fn wait_for_running(&mut self) -> &mut Self { 216 | self.wait_for_running = true; 217 | self 218 | } 219 | 220 | /// リクエストを優先的に処理する。 221 | /// 222 | /// デフォルトでは、全てのリクエストは、過負荷時に無視される。 223 | /// prioritized が呼び出されたリクエストは、 224 | /// キューの長さが hard limit に達するまでは、たとえ過負荷であっても処理される。 225 | pub fn prioritized(&mut self) -> &mut Self { 226 | self.prioritized = true; 227 | self 228 | } 229 | 230 | fn send_command(&self, command: Command) { 231 | if !self.wait_for_running && self.device.metrics().status() == DeviceStatus::Starting { 232 | let e = track!(ErrorKind::DeviceBusy.cause("The device is starting up")); 233 | command.failed(e.into()); 234 | return; 235 | } 236 | if let Err(e) = track!(self.check_limit()) { 237 | self.device.metrics().busy_commands.increment(&command); 238 | command.failed(e) 239 | } else { 240 | self.device.send_command(command); 241 | } 242 | } 243 | 244 | fn check_limit(&self) -> Result<()> { 245 | let metrics = self.device.metrics(); 246 | if let Some(max) = self.max_queue_len { 247 | track_assert!( 248 | metrics.queue_len() <= max, 249 | ErrorKind::DeviceBusy, 250 | "value={}, max={}", 251 | metrics.queue_len(), 252 | max 253 | ); 254 | } 255 | Ok(()) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/device/thread.rs: -------------------------------------------------------------------------------- 1 | use fibers::sync::oneshot; 2 | use futures::{Future, Poll}; 3 | use slog::Logger; 4 | use std::fmt::Debug; 5 | use std::sync::mpsc as std_mpsc; 6 | use std::sync::mpsc::{RecvTimeoutError, SendError}; 7 | use std::sync::Arc; 8 | use std::thread; 9 | use std::time::{Duration, Instant}; 10 | use trackable::error::ErrorKindExt; 11 | 12 | use crate::device::command::{Command, CommandReceiver, CommandSender}; 13 | use crate::device::long_queue_policy::LongQueuePolicy; 14 | use crate::device::probabilistic::{Dropper, ProbabilisticDropper}; 15 | use crate::device::queue::DeadlineQueue; 16 | use crate::device::{DeviceBuilder, DeviceStatus}; 17 | use crate::metrics::DeviceMetrics; 18 | use crate::nvm::NonVolatileMemory; 19 | use crate::storage::Storage; 20 | use crate::{Error, ErrorKind, Result}; 21 | 22 | /// デバイスの実行スレッド. 23 | #[derive(Debug)] 24 | pub struct DeviceThread 25 | where 26 | N: NonVolatileMemory + Send + 'static, 27 | { 28 | metrics: DeviceMetrics, 29 | queue: DeadlineQueue, 30 | storage: Storage, 31 | idle_threshold: Duration, 32 | max_queue_len: usize, 33 | max_keep_busy_duration: Duration, 34 | busy_threshold: usize, 35 | start_busy_time: Option, 36 | command_tx: CommandSender, 37 | command_rx: CommandReceiver, 38 | logger: Logger, 39 | long_queue_policy: LongQueuePolicy, 40 | dropper: Box, 41 | } 42 | impl DeviceThread 43 | where 44 | N: NonVolatileMemory + Send + 'static, 45 | { 46 | /// デバイスの実行スレッドを起動する. 47 | pub fn spawn( 48 | builder: DeviceBuilder, 49 | init_storage: F, 50 | ) -> (DeviceThreadHandle, DeviceThreadMonitor) 51 | where 52 | F: FnOnce() -> Result> + Send + 'static, 53 | { 54 | let mut metrics = DeviceMetrics::new(&builder.metrics); 55 | metrics.status.set(f64::from(DeviceStatus::Starting as u8)); 56 | 57 | let (command_tx, command_rx) = std_mpsc::channel(); 58 | let (monitored, monitor) = oneshot::monitor(); 59 | let handle = DeviceThreadHandle { 60 | command_tx: command_tx.clone(), 61 | metrics: Arc::new(metrics.clone()), 62 | }; 63 | thread::spawn(move || { 64 | let result = track!(init_storage()).and_then(|storage| { 65 | metrics.storage = Some(storage.metrics().clone()); 66 | metrics.status.set(f64::from(DeviceStatus::Running as u8)); 67 | // LongQueuePolicy が RefuseNewRequests か Drop だったら、この後 run_once で使うため、dropper を作っておく。 68 | // Stop の場合も実装を簡単にするためにプレイスホルダーの dropper を作る。 69 | let ratio = builder.long_queue_policy.ratio(); 70 | let dropper = Box::new(ProbabilisticDropper::new(builder.logger.clone(), ratio)) 71 | as Box; 72 | let mut device = DeviceThread { 73 | metrics: metrics.clone(), 74 | queue: DeadlineQueue::new(), 75 | storage, 76 | idle_threshold: builder.idle_threshold, 77 | max_queue_len: builder.max_queue_len, 78 | max_keep_busy_duration: builder.max_keep_busy_duration, 79 | busy_threshold: builder.busy_threshold, 80 | start_busy_time: None, 81 | command_tx, 82 | command_rx, 83 | logger: builder.logger, 84 | long_queue_policy: builder.long_queue_policy, 85 | dropper, 86 | }; 87 | loop { 88 | match track!(device.run_once()) { 89 | Err(e) => break Err(e), 90 | Ok(false) => break Ok(()), 91 | Ok(true) => {} 92 | } 93 | } 94 | }); 95 | metrics.status.set(f64::from(DeviceStatus::Stopped as u8)); 96 | metrics.storage = None; 97 | monitored.exit(result); 98 | }); 99 | 100 | (handle, DeviceThreadMonitor(monitor)) 101 | } 102 | 103 | fn run_once(&mut self) -> Result { 104 | if let Ok(command) = self.command_rx.try_recv() { 105 | return self.push_to_queue(command); 106 | } 107 | if let Some(command) = self.queue.pop() { 108 | self.metrics.dequeued_commands.increment(&command); 109 | let result = track!(self.check_overload()); 110 | let prioritized = command.prioritized(); 111 | // 過負荷になっていたら、long_queue_policy に応じて挙動を変える 112 | // ただし、prioritized なリクエストの場合は過負荷でも drop せずに処理をする 113 | if let (Err(e), false) = (result, prioritized) { 114 | match &self.long_queue_policy { 115 | LongQueuePolicy::RefuseNewRequests { .. } => {} 116 | LongQueuePolicy::Stop => return track!(Err(e)), 117 | LongQueuePolicy::Drop { .. } => { 118 | // 確率 ratio で drop する 119 | if self.dropper.will_drop() { 120 | let elapsed = self.start_busy_time.map(|t| t.elapsed().as_secs()); 121 | warn!( 122 | self.logger, 123 | "Request dropped: {:?}", 124 | command; 125 | "queue_len" => self.queue.len(), 126 | "from_busy (sec)" => elapsed, 127 | ); 128 | let result = self.handle_command_with_error( 129 | command, 130 | ErrorKind::RequestDropped.cause(e).into(), 131 | ); 132 | return Ok(result); 133 | } 134 | } 135 | } 136 | } 137 | return track!(self.handle_command(command)); 138 | } 139 | 140 | match self.command_rx.recv_timeout(self.idle_threshold) { 141 | Err(RecvTimeoutError::Disconnected) => unreachable!(), 142 | Err(RecvTimeoutError::Timeout) => { 143 | self.metrics.side_jobs.increment(); 144 | track!(self.storage.run_side_job_once())?; 145 | Ok(true) 146 | } 147 | Ok(command) => self.push_to_queue(command), 148 | } 149 | } 150 | 151 | /// ここでも command の処理をせざるを得ない都合上、終了しないかどうかの bool 値を返す。 152 | fn push_to_queue(&mut self, command: Command) -> Result { 153 | let result = track!(self.check_overload()); 154 | let prioritized = command.prioritized(); 155 | if let Err(e) = track!(self.check_queue_limit()) { 156 | // queue length の hard limit を突破しているので、prioritized かどうかに関係なくエラーを返す。 157 | // 常にリクエストを拒否すれば問題ない。 158 | let elapsed = self.start_busy_time.map(|t| t.elapsed().as_secs()); 159 | error!( 160 | self.logger, "Request refused (hard limit): {:?}", 161 | command; 162 | "queue_len" => self.queue.len(), 163 | "queue_len_hard_limit" => self.max_queue_len, 164 | "from_busy (sec)" => elapsed, 165 | ); 166 | self.metrics.dequeued_commands.increment(&command); 167 | let result = 168 | self.handle_command_with_error(command, ErrorKind::RequestRefused.cause(e).into()); 169 | return Ok(result); 170 | } 171 | if let (Err(e), false) = (result, prioritized) { 172 | match &self.long_queue_policy { 173 | LongQueuePolicy::RefuseNewRequests { .. } => { 174 | // 確率 ratio で refuse する 175 | if self.dropper.will_drop() { 176 | let elapsed = self.start_busy_time.map(|t| t.elapsed().as_secs()); 177 | warn!( 178 | self.logger, "Request refused: {:?}", 179 | command; 180 | "queue_len" => self.queue.len(), 181 | "from_busy (sec)" => elapsed, 182 | ); 183 | self.metrics.dequeued_commands.increment(&command); 184 | let result = self.handle_command_with_error( 185 | command, 186 | ErrorKind::RequestRefused.cause(e).into(), 187 | ); 188 | return Ok(result); 189 | } 190 | } 191 | LongQueuePolicy::Stop => { 192 | self.metrics.dequeued_commands.increment(&command); 193 | return track!(Err(e)); 194 | } 195 | LongQueuePolicy::Drop { .. } => {} 196 | } 197 | } 198 | self.queue.push(command); 199 | Ok(true) 200 | } 201 | 202 | fn handle_command(&mut self, command: Command) -> Result { 203 | match command { 204 | Command::Get(c) => { 205 | let result = track!(self.storage.get(c.lump_id())); 206 | if result.is_err() { 207 | self.metrics.failed_commands.get.increment(); 208 | } 209 | c.reply(result); 210 | Ok(true) 211 | } 212 | Command::Head(c) => { 213 | let value = self.storage.head(c.lump_id()); 214 | c.reply(Ok(value)); 215 | Ok(true) 216 | } 217 | Command::List(c) => { 218 | let value = self.storage.list(); 219 | c.reply(Ok(value)); 220 | Ok(true) 221 | } 222 | Command::ListRange(c) => { 223 | let value = self.storage.list_range(c.lump_range()); 224 | c.reply(Ok(value)); 225 | Ok(true) 226 | } 227 | Command::Put(c) => { 228 | debug!(self.logger, "Put LumpId=(\"{}\")", c.lump_id()); 229 | let result = track!(self.storage.put(c.lump_id(), c.lump_data())); 230 | if result.is_err() { 231 | self.metrics.failed_commands.put.increment(); 232 | } 233 | if let Some(e) = maybe_critical_error(&result) { 234 | c.reply(result); 235 | Err(e) 236 | } else { 237 | let do_sync = c.do_sync_journal(); 238 | c.reply(result); 239 | if do_sync { 240 | let sync_result = track!(self.storage.journal_sync()); 241 | sync_result.map(|_| true) 242 | } else { 243 | Ok(true) 244 | } 245 | } 246 | } 247 | Command::Delete(c) => { 248 | let result = track!(self.storage.delete(c.lump_id())); 249 | if result.is_err() { 250 | self.metrics.failed_commands.delete.increment(); 251 | } 252 | if let Some(e) = maybe_critical_error(&result) { 253 | c.reply(result); 254 | Err(e) 255 | } else { 256 | let do_sync = c.do_sync_journal(); 257 | c.reply(result); 258 | if do_sync { 259 | let sync_result = track!(self.storage.journal_sync()); 260 | sync_result.map(|_| true) 261 | } else { 262 | Ok(true) 263 | } 264 | } 265 | } 266 | Command::DeleteRange(c) => { 267 | let result = track!(self.storage.delete_range(c.lump_range())); 268 | if result.is_err() { 269 | self.metrics.failed_commands.delete_range.increment(); 270 | } 271 | if let Some(e) = maybe_critical_error(&result) { 272 | c.reply(result); 273 | Err(e) 274 | } else { 275 | let do_sync = c.do_sync_journal(); 276 | c.reply(result); 277 | if do_sync { 278 | let sync_result = track!(self.storage.journal_sync()); 279 | sync_result.map(|_| true) 280 | } else { 281 | Ok(true) 282 | } 283 | } 284 | } 285 | Command::UsageRange(c) => { 286 | let usage = self.storage.usage_range(c.lump_range()); 287 | c.reply(Ok(usage)); 288 | Ok(true) 289 | } 290 | Command::Stop(_) => Ok(false), 291 | } 292 | } 293 | 294 | // command に対し、常に指定されたエラーを返答する。 295 | // この関数自身は常に成功するため、handle_command と違い bool を返す。 296 | fn handle_command_with_error(&mut self, command: Command, error: Error) -> bool { 297 | self.metrics.failed_commands.increment(&command); 298 | match command { 299 | Command::Get(c) => c.reply(track!(Err(error))), 300 | Command::Head(c) => c.reply(track!(Err(error))), 301 | Command::List(c) => c.reply(track!(Err(error))), 302 | Command::ListRange(c) => c.reply(track!(Err(error))), 303 | Command::Put(c) => c.reply(track!(Err(error))), 304 | Command::Delete(c) => c.reply(track!(Err(error))), 305 | Command::DeleteRange(c) => c.reply(track!(Err(error))), 306 | Command::UsageRange(c) => c.reply(track!(Err(error))), 307 | Command::Stop(_) => { 308 | // ここに来た場合だけ false を返し、残りのパスは全て true を返す。 309 | return false; 310 | } 311 | } 312 | true 313 | } 314 | 315 | fn check_overload(&mut self) -> Result<()> { 316 | if self.queue.len() < self.busy_threshold { 317 | if self.start_busy_time.is_some() { 318 | self.start_busy_time = None; 319 | } 320 | } else if let Some(elapsed) = self.start_busy_time.map(|t| t.elapsed()) { 321 | track_assert!(elapsed <= self.max_keep_busy_duration, ErrorKind::DeviceBusy; 322 | elapsed, self.max_keep_busy_duration, self.busy_threshold); 323 | } else { 324 | self.start_busy_time = Some(Instant::now()); 325 | } 326 | Ok(()) 327 | } 328 | 329 | fn check_queue_limit(&mut self) -> Result<()> { 330 | track_assert!(self.queue.len() <= self.max_queue_len, ErrorKind::DeviceBusy; 331 | self.queue.len(), self.max_queue_len); 332 | Ok(()) 333 | } 334 | } 335 | 336 | /// ストレージのデータが壊れている可能性があるエラーかどうかを判定. 337 | fn maybe_critical_error(result: &Result) -> Option { 338 | result.as_ref().err().and_then(|e| match *e.kind() { 339 | ErrorKind::InconsistentState | ErrorKind::StorageCorrupted | ErrorKind::Other => { 340 | Some(e.clone()) 341 | } 342 | _ => None, 343 | }) 344 | } 345 | 346 | /// デバイスの実行スレッドの死活監視用オブジェクト. 347 | #[derive(Debug)] 348 | pub struct DeviceThreadMonitor(oneshot::Monitor<(), Error>); 349 | impl Future for DeviceThreadMonitor { 350 | type Item = (); 351 | type Error = Error; 352 | fn poll(&mut self) -> Poll { 353 | track!(self 354 | .0 355 | .poll() 356 | .map_err(|e| e.unwrap_or_else(|| ErrorKind::DeviceTerminated 357 | .cause("`DeviceThread` terminated unintentionally") 358 | .into()))) 359 | } 360 | } 361 | 362 | /// デバイススレッドを操作するためのハンドル. 363 | #[derive(Debug, Clone)] 364 | pub struct DeviceThreadHandle { 365 | command_tx: CommandSender, 366 | metrics: Arc, // 必須では無いが`Clone`時の効率を上げるために`Arc`で囲む. 367 | } 368 | impl DeviceThreadHandle { 369 | pub fn send_command(&self, command: Command) { 370 | self.metrics.enqueued_commands.increment(&command); 371 | if let Err(SendError(command)) = self.command_tx.send(command) { 372 | self.metrics.dequeued_commands.increment(&command); 373 | self.metrics.failed_commands.increment(&command); 374 | } 375 | } 376 | pub fn metrics(&self) -> &Arc { 377 | &self.metrics 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{self, str::FromStr}; 2 | use trackable::error::ErrorKindExt; 3 | 4 | /// crate固有のエラー型. 5 | #[derive(Debug, Clone, TrackableError)] 6 | pub struct Error(trackable::error::TrackableError); 7 | impl From for Error { 8 | fn from(e: std::io::Error) -> Self { 9 | if let Some(e) = e.get_ref().and_then(|e| e.downcast_ref::()).cloned() { 10 | e 11 | } else if e.kind() == std::io::ErrorKind::InvalidInput { 12 | ErrorKind::InvalidInput.cause(e).into() 13 | } else { 14 | ErrorKind::Other.cause(e).into() 15 | } 16 | } 17 | } 18 | impl From for std::io::Error { 19 | fn from(e: Error) -> Self { 20 | if *e.kind() == ErrorKind::InvalidInput { 21 | std::io::Error::new(std::io::ErrorKind::InvalidInput, e) 22 | } else { 23 | std::io::Error::new(std::io::ErrorKind::Other, e) 24 | } 25 | } 26 | } 27 | impl From> for Error { 28 | fn from(e: std::sync::PoisonError) -> Self { 29 | ErrorKind::Other.cause(e.to_string()).into() 30 | } 31 | } 32 | 33 | /// 発生し得るエラーの種別. 34 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 35 | #[non_exhaustive] 36 | pub enum ErrorKind { 37 | /// リクエストキューが詰まっている、等の過負荷状態. 38 | /// 39 | /// また、初期化処理中の場合にも、このエラーが返される. 40 | /// 41 | /// # 典型的な対応策 42 | /// 43 | /// - 利用者が時間をおいてリトライする 44 | /// - 優先度が低いリクエストの新規発行をしばらく控える 45 | DeviceBusy, 46 | 47 | /// デバイス(の管理スレッド)が停止しており、利用不可能. 48 | /// 49 | /// 正常・異常に関わらず、停止後のデバイスにリクエストが 50 | /// 発行された場合には、このエラーが返される. 51 | /// 52 | /// # 典型的な対応策 53 | /// 54 | /// - デバイスを再起動する 55 | DeviceTerminated, 56 | 57 | /// ストレージに空き容量がない. 58 | /// 59 | /// # 典型的な対応策 60 | /// 61 | /// - 利用者が不要なlumpを削除する 62 | /// - ストレージの容量を増やした上で、初期化・再構築を行う 63 | StorageFull, 64 | 65 | /// ストレージが破損している. 66 | /// 67 | /// ジャーナル領域のチェックサム検証が失敗した場合等にこのエラーが返される. 68 | /// 69 | /// # 典型的な対応策 70 | /// 71 | /// - もし人手で復旧可能な場合には復旧する 72 | /// - それが無理であれば、諦めて初期化(全削除)を行う 73 | StorageCorrupted, 74 | 75 | /// 入力が不正. 76 | /// 77 | /// # 典型的な対応策 78 | /// 79 | /// - 利用者側のプログラムを修正して入力を正しくする 80 | InvalidInput, 81 | 82 | /// 内部状態が不整合に陥っている. 83 | /// 84 | /// プログラムにバグがあることを示している. 85 | /// 86 | /// # 典型的な対応策 87 | /// 88 | /// - バグ修正を行ってプログラムを更新する 89 | InconsistentState, 90 | 91 | /// 過負荷のため、リクエストはドロップされた. 92 | /// 93 | /// # 典型的な対応策 94 | /// 95 | /// - 負荷の高い時間を避けてもう一度試す 96 | RequestDropped, 97 | 98 | /// 過負荷のため、リクエストは拒否された. 99 | /// 100 | /// # 典型的な対応策 101 | /// 102 | /// - 負荷の高い時間を避けてもう一度試す 103 | RequestRefused, 104 | 105 | /// その他エラー. 106 | /// 107 | /// E.g., I/Oエラー 108 | /// 109 | /// # 典型的な対応策 110 | /// 111 | /// - 利用者側で(指数バックオフ等を挟みつつ)何度かリトライ 112 | /// - それでもダメなら、致命的な異常が発生していると判断 113 | Other, 114 | } 115 | impl trackable::error::ErrorKind for ErrorKind {} 116 | 117 | impl std::fmt::Display for ErrorKind { 118 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 119 | match self { 120 | ErrorKind::StorageFull => write!(f, "StorageFull"), 121 | ErrorKind::StorageCorrupted => write!(f, "StorageCorrupted"), 122 | ErrorKind::DeviceBusy => write!(f, "DeviceBusy"), 123 | ErrorKind::DeviceTerminated => write!(f, "DeviceTerminated"), 124 | ErrorKind::InvalidInput => write!(f, "InvalidInput"), 125 | ErrorKind::InconsistentState => write!(f, "InconsistentState"), 126 | ErrorKind::RequestDropped => write!(f, "RequestDropped"), 127 | ErrorKind::RequestRefused => write!(f, "RequestRefused"), 128 | ErrorKind::Other => write!(f, "Other"), 129 | } 130 | } 131 | } 132 | 133 | impl FromStr for ErrorKind { 134 | type Err = (); 135 | fn from_str(s: &str) -> Result { 136 | let kind = match s { 137 | "StorageFull" => ErrorKind::StorageFull, 138 | "StorageCorrupted" => ErrorKind::StorageCorrupted, 139 | "DeviceBusy" => ErrorKind::DeviceBusy, 140 | "DeviceTerminated" => ErrorKind::DeviceTerminated, 141 | "InvalidInput" => ErrorKind::InvalidInput, 142 | "RequestDropped" => ErrorKind::RequestDropped, 143 | "RequestRefused" => ErrorKind::RequestRefused, 144 | "InconsistentState" => ErrorKind::InconsistentState, 145 | "Other" => ErrorKind::Other, 146 | _ => return Err(()), 147 | }; 148 | Ok(kind) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Canny Lump Storage. 2 | //! 3 | //! `cannyls`は、予測可能なレイテンシの提供を目的として設計された、ローカル用のkey-valueストレージ. 4 | //! 5 | //! # 特徴 6 | //! 7 | //! - 128bitのIDを有する[lump]群を保持するためのストレージ(ローカルKVS) 8 | //! - メモリ使用量を抑えるために固定長のIDを採用 9 | //! - (主に)HTTPのGET/PUT/DELETE相当の操作を[lump]に対して実行可能 10 | //! - 各操作には[deadline]という時間軸ベースの優先順位を指定可能 11 | //! - 一つの物理デバイス(e.g., HDD)に対して、一つの[device]管理スレッドが割り当てられて、リクエストがスケジューリングされる 12 | //! - 一つの物理デバイスに対するI/O命令は、全てこの管理スレッド上で直列化されて処理される 13 | //! - **直列化** という特性上、HDDと相性が良い (逆にSSDの場合には性能が活用しきれない可能性がある) 14 | //! - キャッシュ層を備えず、各操作で発行されるディスクI/O回数が(ほぼ)正確に予測可能: 15 | //! - GET/DELETE: 一回 16 | //! - PUT: 最大二回 17 | //! - ※ 実際にはバックグランド処理(e.g., GC)用のI/Oが発行されることがあるので、上記の値は償却された回数となる 18 | //! - "lusf"という[ストレージフォーマット(v1.0)][format]を定義および使用している 19 | //! - 最大で512TBの容量の物理デバイス(e.g., HDD)をサポート 20 | //! - 冗長化やデータの整合性保証等は行わない 21 | //! 22 | //! # モジュールの依存関係 23 | //! 24 | //! ```text 25 | //! device => storage => nvm 26 | //! ``` 27 | //! 28 | //! - [device]モジュール: 29 | //! - 主に[Device]構造体を提供 30 | //! - `cannyls`の利用者が直接触るのはこの構造体 31 | //! - [Storage]を制御するための管理スレッドを起動し、それに対するリクエスト群のスケジューリング等を担当する 32 | //! - [storage]モジュール: 33 | //! - 主に[Storage]構造体を提供 34 | //! - [nvm]を永続化層として利用し、その上に[ストレージフォーマット(v1.0)][format]を実装している 35 | //! - [nvm]モジュール: 36 | //! - 主に[NonVolatileMemory]トレイトとその実装である[FileNvm]を提供 37 | //! - [storage]に対して永続化層を提供するのが目的 38 | //! - 現時点では未実装だが、ブロックデバイスを直接操作する[NonVolatileMemory]実装を用意することで、 39 | //! OS層を完全にバイパスすることも可能 40 | //! 41 | //! # アーキテクチャの詳細 42 | //! 43 | //! [Wiki]を参照のこと。 44 | //! 45 | //! [lump]: ./lump/index.html 46 | //! [deadline]: ./deadline/index.html 47 | //! [device]: ./device/index.html 48 | //! [Device]: ./device/struct.Device.html 49 | //! [storage]: ./storage/index.html 50 | //! [Storage]: ./storage/struct.Storage.html 51 | //! [nvm]: ./nvm/index.html 52 | //! [NonVolatileMemory]: ./nvm/trait.NonVolatileMemory.html 53 | //! [FileNvm]: ./nvm/struct.FileNvm.html 54 | //! [format]: https://github.com/frugalos/cannyls/wiki/Storage-Format 55 | //! [Wiki]: https://github.com/frugalos/cannyls/wiki/ 56 | #![warn(missing_docs)] 57 | extern crate adler32; 58 | extern crate byteorder; 59 | #[cfg(test)] 60 | extern crate fibers_global; 61 | #[cfg(feature = "device")] 62 | extern crate futures; 63 | extern crate libc; 64 | extern crate prometrics; 65 | #[cfg(test)] 66 | extern crate tempdir; 67 | #[macro_use] 68 | extern crate trackable; 69 | extern crate uuid; 70 | #[macro_use] 71 | #[cfg(feature = "device")] 72 | extern crate slog; 73 | 74 | pub use crate::error::{Error, ErrorKind}; 75 | 76 | macro_rules! track_io { 77 | ($expr:expr) => { 78 | $expr.map_err(|e: ::std::io::Error| track!(crate::Error::from(e))) 79 | }; 80 | } 81 | 82 | pub mod block; 83 | pub mod deadline; 84 | #[cfg(feature = "device")] 85 | pub mod device; 86 | pub mod lump; 87 | pub mod metrics; 88 | pub mod nvm; 89 | pub mod storage; 90 | 91 | mod error; 92 | 93 | /// crate固有の`Result`型. 94 | pub type Result = std::result::Result; 95 | -------------------------------------------------------------------------------- /src/lump.rs: -------------------------------------------------------------------------------- 1 | //! Lump関連のデータ構造群. 2 | //! 3 | //! "lump"とは、`cannyls`におけるデータの格納単位. 4 | //! 各lumpは「128bit幅のID」と「最大30MB程度のデータ(任意のバイト列)」から構成される. 5 | //! 6 | //! `cannyls`のレイヤでは、保存されているlumpの整合性の保証や検証は行わないため、 7 | //! 必要であれば、利用側で冗長化やチェックサム検証等を施す必要がある. 8 | use std::cmp; 9 | use std::fmt; 10 | use std::str::FromStr; 11 | use std::u128; 12 | use trackable::error::ErrorKindExt; 13 | 14 | use crate::block::BlockSize; 15 | use crate::storage::DataRegionLumpData; 16 | use crate::{Error, ErrorKind, Result}; 17 | 18 | /// Lumpの識別子(128bit幅). 19 | #[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] 20 | pub struct LumpId(u128); 21 | impl LumpId { 22 | /// 識別子のバイト幅. 23 | pub const SIZE: usize = 16; 24 | 25 | /// 新しい`LumpId`インスタンスを生成する. 26 | /// 27 | /// # Examples 28 | /// 29 | /// ``` 30 | /// use cannyls::lump::LumpId; 31 | /// 32 | /// assert_eq!(LumpId::new(0x12_3456).to_string(), "00000000000000000000000000123456"); 33 | /// 34 | /// // 16進数文字列からも生成可能 35 | /// assert_eq!("123456".parse::().unwrap(), LumpId::new(0x12_3456)); 36 | /// ``` 37 | pub fn new(id: u128) -> Self { 38 | LumpId(id) 39 | } 40 | 41 | /// 識別子の値(128bit整数)を返す. 42 | pub fn as_u128(&self) -> u128 { 43 | self.0 44 | } 45 | } 46 | impl FromStr for LumpId { 47 | type Err = Error; 48 | 49 | /// 16進数表記の数値から`LumpId`を生成する. 50 | /// 51 | /// 数値の"128bit整数"として扱われ、先頭のゼロは省略可能(`"ab12"`と`"00ab12"`は等価). 52 | /// 53 | /// # Errors 54 | /// 55 | /// 以下のいずれかの場合には、種類が`ErrorKind::InvalidInput`のエラーが返される: 56 | /// 57 | /// - 文字列が16進数表記の整数値を表していない 58 | /// - 文字列の長さが32文字を超えている 59 | /// 60 | /// # Examples 61 | /// 62 | /// ``` 63 | /// use std::str::{self, FromStr}; 64 | /// use cannyls::ErrorKind; 65 | /// use cannyls::lump::LumpId; 66 | /// 67 | /// assert_eq!(LumpId::from_str("00ab12").ok(), 68 | /// Some(LumpId::new(0xab12))); 69 | /// 70 | /// assert_eq!(LumpId::from_str("foo_bar").err().map(|e| *e.kind()), 71 | /// Some(ErrorKind::InvalidInput)); 72 | /// 73 | /// let large_input = str::from_utf8(&[b'a', 33][..]).unwrap(); 74 | /// assert_eq!(LumpId::from_str(large_input).err().map(|e| *e.kind()), 75 | /// Some(ErrorKind::InvalidInput)); 76 | /// ``` 77 | fn from_str(s: &str) -> Result { 78 | let id = track!(u128::from_str_radix(s, 16).map_err(|e| ErrorKind::InvalidInput.cause(e)))?; 79 | Ok(LumpId::new(id)) 80 | } 81 | } 82 | impl fmt::Debug for LumpId { 83 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 84 | write!(f, r#"LumpId("{}")"#, self) 85 | } 86 | } 87 | impl fmt::Display for LumpId { 88 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 89 | for i in (0..Self::SIZE).rev() { 90 | let b = (self.0 >> (8 * i)) as u8; 91 | write!(f, "{:02x}", b)?; 92 | } 93 | Ok(()) 94 | } 95 | } 96 | 97 | /// Lumpのデータ. 98 | /// 99 | /// 最大で`MAX_SIZE`までのバイト列を保持可能. 100 | #[derive(Clone)] 101 | pub struct LumpData(LumpDataInner); 102 | impl LumpData { 103 | /// データの最大長(バイト単位). 104 | /// 105 | /// 最小ブロックサイズを用いた場合に表現可能な最大サイズまでのデータが保持可能. 106 | /// 最後の`-2`は、内部的に付与されるメタ情報のサイズ分. 107 | /// 108 | /// # 蛇足 109 | /// 110 | /// 現状は簡単のために、最小のブロックサイズに合わせた最大サイズ、となっている。 111 | /// 112 | /// ただし、仕組み上は、ストレージが採用したブロックサイズがそれよりも大きい場合には、 113 | /// 保存可能なデータサイズを比例して大きくすることは可能. 114 | /// 115 | /// 一つ一つのlumpのサイズをあまり巨大にしてしまうと、 116 | /// 一つのlumpの読み書き処理が全体のレイテンシを阻害してしまう可能性もあるので、 117 | /// 現状くらいの制限でちょうど良いのではないかとも思うが、 118 | /// もし最大サイズをどうしても上げたい場合には、それも不可能ではない、 119 | /// ということは記しておく. 120 | pub const MAX_SIZE: usize = 0xFFFF * (BlockSize::MIN as usize) - 2; 121 | 122 | /// ジャーナル領域に埋め込み可能なデータの最大長(バイト単位). 123 | pub const MAX_EMBEDDED_SIZE: usize = 0xFFFF; 124 | 125 | /// 引数で指定されたデータを保持する`LumpData`インスタンスを生成する. 126 | /// 127 | /// # 性能上の注意 128 | /// 129 | /// この関数で生成された`LumpData`インスタンスは、保存先ストレージのブロック境界に合わせた 130 | /// アライメントが行われていないため、PUTによる保存時に必ず一度、 131 | /// アライメント用にデータサイズ分のメモリコピーが発生してしまう. 132 | /// 133 | /// 保存時のコピーを避けたい場合には[`Storage`]ないし[`DeviceHandle`]が提供している 134 | /// `allocate_lump_data_with_bytes`メソッドを使用して、 135 | /// アライメント済みの`LumpData`インスタンスを生成すると良い. 136 | /// 137 | /// [`Storage`]: ../storage/struct.Storage.html 138 | /// [`DeviceHandle`]: ../device/struct.DeviceHandle.html 139 | /// 140 | /// # Errors 141 | /// 142 | /// データのサイズが`MAX_SIZE`を超えている場合は、`ErrorKind::InvalidInput`エラーが返される. 143 | #[allow(clippy::new_ret_no_self)] 144 | pub fn new(data: Vec) -> Result { 145 | track_assert!( 146 | data.len() <= LumpData::MAX_SIZE, 147 | ErrorKind::InvalidInput, 148 | "Too large lump data: {} bytes", 149 | data.len() 150 | ); 151 | Ok(LumpData(LumpDataInner::DataRegionUnaligned(data))) 152 | } 153 | 154 | /// ジャーナル領域埋め込み用の`LumpData`インスタンスを生成する. 155 | /// 156 | /// # Errors 157 | /// 158 | /// `data`の長さが`MAX_EMBEDDED_SIZE`を超えている場合は、`ErrorKind::InvalidInput`エラーが返される. 159 | /// 160 | pub fn new_embedded(data: Vec) -> Result { 161 | track_assert!( 162 | data.len() <= LumpData::MAX_EMBEDDED_SIZE, 163 | ErrorKind::InvalidInput, 164 | "Too large embedded lump data: {} bytes", 165 | data.len() 166 | ); 167 | Ok(LumpData(LumpDataInner::JournalRegion(data))) 168 | } 169 | 170 | /// データを表すバイト列への参照を返す. 171 | pub fn as_bytes(&self) -> &[u8] { 172 | self.as_ref() 173 | } 174 | 175 | /// データを表すバイト列への破壊的な参照を返す. 176 | pub fn as_bytes_mut(&mut self) -> &mut [u8] { 177 | self.as_mut() 178 | } 179 | 180 | /// 所有権を放棄して、内部のバイト列を返す. 181 | /// 182 | /// なお、このインスタンスが確保しているのがアライメントされたメモリ領域の場合には、 183 | /// それを通常の`Vec`に変換するためのメモリコピーが、本メソッド呼び出し時に発生することになる. 184 | pub fn into_bytes(self) -> Vec { 185 | match self.0 { 186 | LumpDataInner::JournalRegion(d) => d, 187 | LumpDataInner::DataRegion(d) => Vec::from(d.as_bytes()), 188 | LumpDataInner::DataRegionUnaligned(d) => d, 189 | } 190 | } 191 | 192 | /// アライメント済みの`LumpData`インスタンス用のメモリ領域を割り当てる. 193 | /// 194 | /// この関数によって割当てられたメモリ領域の初期値は未定義であり、 195 | /// 利用者は事前にゼロ埋めされていることを仮定してはいけない. 196 | /// 197 | /// # Errors 198 | /// 199 | /// 指定されたサイズが`MAX_SIZE`を超えている場合は、`ErrorKind::InvalidInput`エラーが返される. 200 | pub(crate) fn aligned_allocate(data_len: usize, block_size: BlockSize) -> Result { 201 | track_assert!( 202 | data_len <= LumpData::MAX_SIZE, 203 | ErrorKind::InvalidInput, 204 | "Too large lump data: {} bytes", 205 | data_len 206 | ); 207 | Ok(LumpData::from(DataRegionLumpData::new( 208 | data_len, block_size, 209 | ))) 210 | } 211 | 212 | pub(crate) fn as_inner(&self) -> &LumpDataInner { 213 | &self.0 214 | } 215 | } 216 | impl AsRef<[u8]> for LumpData { 217 | fn as_ref(&self) -> &[u8] { 218 | match self.0 { 219 | LumpDataInner::JournalRegion(ref d) => d, 220 | LumpDataInner::DataRegion(ref d) => d.as_bytes(), 221 | LumpDataInner::DataRegionUnaligned(ref d) => d, 222 | } 223 | } 224 | } 225 | impl AsMut<[u8]> for LumpData { 226 | fn as_mut(&mut self) -> &mut [u8] { 227 | match self.0 { 228 | LumpDataInner::JournalRegion(ref mut d) => d, 229 | LumpDataInner::DataRegion(ref mut d) => d.as_bytes_mut(), 230 | LumpDataInner::DataRegionUnaligned(ref mut d) => d, 231 | } 232 | } 233 | } 234 | impl fmt::Debug for LumpData { 235 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 236 | let block_size = if let LumpDataInner::DataRegion(ref x) = self.0 { 237 | Some(x.block_size()) 238 | } else { 239 | None 240 | }; 241 | 242 | let len = cmp::min(128, self.as_bytes().len()); 243 | let bytes = &self.as_bytes()[0..len]; 244 | let omitted = if len < self.as_ref().len() { 245 | format!("({} bytes omitted)", self.as_ref().len() - len) 246 | } else { 247 | "".to_owned() 248 | }; 249 | write!( 250 | f, 251 | "LumpData {{ block_size: {:?}, bytes: {:?}{} }}", 252 | block_size, bytes, omitted 253 | ) 254 | } 255 | } 256 | impl PartialEq for LumpData { 257 | fn eq(&self, other: &Self) -> bool { 258 | self.as_ref() == other.as_ref() 259 | } 260 | } 261 | impl Eq for LumpData {} 262 | impl From for LumpData { 263 | fn from(f: DataRegionLumpData) -> Self { 264 | LumpData(LumpDataInner::DataRegion(f)) 265 | } 266 | } 267 | 268 | #[derive(Clone)] 269 | pub(crate) enum LumpDataInner { 270 | JournalRegion(Vec), 271 | DataRegion(DataRegionLumpData), 272 | DataRegionUnaligned(Vec), 273 | } 274 | 275 | /// Lumpの概要情報. 276 | /// 277 | /// "ヘッダ"という用語は若干不正確だが、 278 | /// `HEAD`リクエストの結果として取得可能なものなので、 279 | /// このような名前にしている. 280 | #[derive(Debug, Clone)] 281 | pub struct LumpHeader { 282 | /// データサイズの近似値(バイト単位). 283 | /// 284 | /// 実際のデータサイズに管理用のメタ情報を追加した上で、 285 | /// 次のブロックサイズ境界への切り上げたサイズ、となるため、 286 | /// 最大でストレージのブロックサイズ二つ分だけ、 287 | /// 実際よりも大きな値となることがある. 288 | /// 289 | /// なお、対象lumpのデータがジャーナル領域に埋め込まれている場合には、 290 | /// 常に正確なサイズが返される. 291 | pub approximate_data_size: u32, 292 | } 293 | -------------------------------------------------------------------------------- /src/nvm/memory.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Cursor, Read, Seek, SeekFrom, Write}; 2 | 3 | use crate::block::BlockSize; 4 | use crate::nvm::NonVolatileMemory; 5 | use crate::{ErrorKind, Result}; 6 | 7 | type Memory = Cursor>; 8 | 9 | /// メモリベースの`NonVolatileMemory`の実装. 10 | /// 11 | /// # 注意 12 | /// 13 | /// これは主にテストや性能計測用途を意図した実装であり、 14 | /// `NonVolatileMemory`が本来要求する"不揮発性"は満たしていない. 15 | #[derive(Debug)] 16 | pub struct MemoryNvm { 17 | memory: Memory, 18 | } 19 | impl MemoryNvm { 20 | /// 新しい`MemoryNvm`インスタンスを生成する. 21 | pub fn new(memory: Vec) -> Self { 22 | MemoryNvm { 23 | memory: Cursor::new(memory), 24 | } 25 | } 26 | 27 | #[cfg(test)] 28 | pub fn as_bytes(&self) -> &[u8] { 29 | self.memory.get_ref() 30 | } 31 | 32 | fn seek_impl(&mut self, position: u64) -> Result<()> { 33 | track_assert!( 34 | self.block_size().is_aligned(position), 35 | ErrorKind::InvalidInput 36 | ); 37 | self.memory.set_position(position); 38 | Ok(()) 39 | } 40 | fn read_impl(&mut self, buf: &mut [u8]) -> Result { 41 | track_assert!( 42 | self.block_size().is_aligned(buf.len() as u64), 43 | ErrorKind::InvalidInput 44 | ); 45 | track_io!(self.memory.read(buf)) 46 | } 47 | fn write_impl(&mut self, buf: &[u8]) -> Result<()> { 48 | track_assert!( 49 | self.block_size().is_aligned(buf.len() as u64), 50 | ErrorKind::InvalidInput 51 | ); 52 | 53 | // アライメントを維持するためには`write_all`を使う必要がある 54 | track_io!(self.memory.write_all(buf)) 55 | } 56 | } 57 | impl NonVolatileMemory for MemoryNvm { 58 | fn sync(&mut self) -> Result<()> { 59 | Ok(()) 60 | } 61 | fn position(&self) -> u64 { 62 | self.memory.position() 63 | } 64 | fn capacity(&self) -> u64 { 65 | self.memory.get_ref().len() as u64 66 | } 67 | fn block_size(&self) -> BlockSize { 68 | BlockSize::min() 69 | } 70 | fn split(mut self, position: u64) -> Result<(Self, Self)> { 71 | track_assert_eq!( 72 | position, 73 | self.block_size().ceil_align(position), 74 | ErrorKind::InvalidInput 75 | ); 76 | track_assert!(position <= self.capacity(), ErrorKind::InvalidInput); 77 | let right = self.memory.get_mut().split_off(position as usize); 78 | let right = MemoryNvm::new(right); 79 | 80 | self.memory.set_position(0); 81 | Ok((self, right)) 82 | } 83 | } 84 | impl Seek for MemoryNvm { 85 | fn seek(&mut self, pos: SeekFrom) -> io::Result { 86 | let position = self.convert_to_offset(pos)?; 87 | track!(self.seek_impl(position))?; 88 | Ok(position) 89 | } 90 | } 91 | impl Read for MemoryNvm { 92 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 93 | let read_size = track!(self.read_impl(buf))?; 94 | Ok(read_size) 95 | } 96 | } 97 | impl Write for MemoryNvm { 98 | fn write(&mut self, buf: &[u8]) -> io::Result { 99 | track!(self.write_impl(buf))?; 100 | Ok(buf.len()) 101 | } 102 | fn flush(&mut self) -> io::Result<()> { 103 | Ok(()) 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod tests { 109 | use std::io::{Read, Seek, SeekFrom, Write}; 110 | use trackable::result::TestResult; 111 | 112 | use super::*; 113 | use crate::nvm::NonVolatileMemory; 114 | 115 | #[test] 116 | fn it_works() -> TestResult { 117 | let mut nvm = MemoryNvm::new(vec![0; 1024]); 118 | assert_eq!(nvm.capacity(), 1024); 119 | assert_eq!(nvm.position(), 0); 120 | 121 | // read, write, seek 122 | let mut buf = vec![0; 512]; 123 | track_io!(nvm.read_exact(&mut buf))?; 124 | assert_eq!(buf, vec![0; 512]); 125 | assert_eq!(nvm.position(), 512); 126 | 127 | track_io!(nvm.write(&[1; 512][..]))?; 128 | assert_eq!(nvm.position(), 1024); 129 | 130 | track_io!(nvm.seek(SeekFrom::Start(512)))?; 131 | assert_eq!(nvm.position(), 512); 132 | 133 | track_io!(nvm.read_exact(&mut buf))?; 134 | assert_eq!(buf, vec![1; 512]); 135 | assert_eq!(nvm.position(), 1024); 136 | 137 | // split 138 | let (mut left, mut right) = track!(nvm.split(512))?; 139 | 140 | assert_eq!(left.capacity(), 512); 141 | track_io!(left.seek(SeekFrom::Start(0)))?; 142 | track_io!(left.read_exact(&mut buf))?; 143 | assert_eq!(buf, vec![0; 512]); 144 | assert_eq!(left.position(), 512); 145 | assert!(left.read_exact(&mut buf).is_err()); 146 | 147 | assert_eq!(right.capacity(), 512); 148 | track_io!(right.seek(SeekFrom::Start(0)))?; 149 | track_io!(right.read_exact(&mut buf))?; 150 | assert_eq!(buf, vec![1; 512]); 151 | assert_eq!(right.position(), 512); 152 | assert!(right.read_exact(&mut buf).is_err()); 153 | Ok(()) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/nvm/mod.rs: -------------------------------------------------------------------------------- 1 | //! 不揮発性メモリのインターフェース定義と実装群. 2 | //! 3 | //! このモジュールは[Storage](../storage/struct.Storage.html)がデータの読み書きに使用する 4 | //! 永続化領域を提供する. 5 | use std::io::{Read, Seek, SeekFrom, Write}; 6 | 7 | pub use self::file::{FileNvm, FileNvmBuilder}; 8 | pub use self::memory::MemoryNvm; 9 | pub use self::shared_memory::SharedMemoryNvm; 10 | 11 | use crate::block::{AlignedBytes, BlockSize}; 12 | use crate::{ErrorKind, Result}; 13 | 14 | mod file; 15 | mod memory; 16 | mod shared_memory; 17 | 18 | /// 不揮発性メモリを表すトレイト. 19 | /// 20 | /// "不揮発性メモリ"は「永続化可能なバイト列(領域)」を意味し、lump群を保存するために使用される. 21 | /// 22 | /// 読み書きの際には、位置およびサイズ、がブロック境界にアライメントされている必要がある. 23 | /// 24 | /// このトレイト自体は汎用的なインタフェースを提供しているが、 25 | /// 実装は、読み書きされるデータは[`lusf`]形式に即したものである、ということを想定しても構わない. 26 | /// (e.g., `lusf`のヘッダからキャパシティ情報を取得する) 27 | /// 28 | /// [`lusf`]: https://github.com/frugalos/cannyls/wiki/Storage-Format 29 | pub trait NonVolatileMemory: Sized + Read + Write + Seek { 30 | /// メモリの内容を、物理デバイスに同期する. 31 | /// 32 | /// 内部的にバッファ管理等を行っておらず、常に内容が同期されている場合には、 33 | /// このメソッド内で特に何かを行う必要はない。 34 | fn sync(&mut self) -> Result<()>; 35 | 36 | /// 読み書き用カーソルの現在位置を返す. 37 | fn position(&self) -> u64; 38 | 39 | /// メモリの容量(バイト単位)を返す. 40 | fn capacity(&self) -> u64; 41 | 42 | /// このインスタンスのブロックサイズを返す. 43 | /// 44 | /// 利用者は、ブロックサイズに揃うように、読み書き時のアライメントを行う必要がある. 45 | fn block_size(&self) -> BlockSize; 46 | 47 | /// メモリを指定位置で分割する. 48 | /// 49 | /// # Errors 50 | /// 51 | /// 以下の場合には、種類が`ErrorKind::InvalidInput`のエラーが返される: 52 | /// 53 | /// - 指定位置が容量を超えている 54 | /// - `position`がブロック境界ではない 55 | fn split(self, position: u64) -> Result<(Self, Self)>; 56 | 57 | /// `SeekFrom`形式で指定された位置を、開始地点からのオフセットに変換する. 58 | /// 59 | /// # Errors 60 | /// 61 | /// 「指定位置が容量を超えている」ないし「`0`未満」の場合には、 62 | /// 種類が`ErrorKind::InvalidInput`のエラーが返される. 63 | fn convert_to_offset(&self, pos: SeekFrom) -> Result { 64 | match pos { 65 | SeekFrom::Start(offset) => { 66 | track_assert!(offset <= self.capacity(), ErrorKind::InvalidInput); 67 | Ok(offset) 68 | } 69 | SeekFrom::End(delta) => { 70 | let offset = self.capacity() as i64 + delta; 71 | track_assert!(0 <= offset, ErrorKind::InvalidInput); 72 | Ok(offset as u64) 73 | } 74 | SeekFrom::Current(delta) => { 75 | let offset = self.position() as i64 + delta; 76 | track_assert!(0 <= offset, ErrorKind::InvalidInput); 77 | Ok(offset as u64) 78 | } 79 | } 80 | } 81 | 82 | /// このインスタンスが指定するブロック境界へのアライメントを保証した書き込みを行う. 83 | /// 84 | /// `f`の引数(一時バッファ)に対して書き込まれたデータは、その後`AlignedBytes`にコピーされた上で、 85 | /// 実際のNVMに書き出される. 86 | /// 87 | /// なお一時バッファの末尾から、次のブロック境界までは、 88 | /// 任意のバイト列で埋められるので、注意が必要(i.e., 書き込み先の既存データは上書きされる). 89 | fn aligned_write_all(&mut self, f: F) -> Result<()> 90 | where 91 | F: FnOnce(&mut Vec) -> Result<()>, 92 | { 93 | let mut buf = Vec::new(); 94 | track!(f(&mut buf))?; 95 | 96 | let mut aligned_bytes = AlignedBytes::from_bytes(&buf, self.block_size()); 97 | aligned_bytes.align(); 98 | track_io!(self.write_all(&aligned_bytes))?; 99 | Ok(()) 100 | } 101 | 102 | /// このインスタンスが指定するブロック境界へのアライメントを保証した上で、指定サイズ分のバイト列を読み込む. 103 | fn aligned_read_bytes(&mut self, size: usize) -> Result { 104 | let mut buf = AlignedBytes::new(size, self.block_size()); 105 | buf.align(); 106 | track_io!(self.read_exact(&mut buf))?; 107 | buf.truncate(size); 108 | Ok(buf) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/nvm/shared_memory.rs: -------------------------------------------------------------------------------- 1 | use std::cmp; 2 | use std::io::{self, Read, Seek, SeekFrom, Write}; 3 | use std::sync::{Arc, Mutex}; 4 | 5 | use crate::block::BlockSize; 6 | use crate::nvm::NonVolatileMemory; 7 | use crate::{Error, ErrorKind, Result}; 8 | 9 | /// インスタンスを共有可能な、メモリベースの`NonVolatileMemory`の実装. 10 | /// 11 | /// # 注意 12 | /// 13 | /// これはテスト用途のみを意図した実装であり、 14 | /// `NonVolatileMemory`が本来要求する"不揮発性"は満たしていない. 15 | #[derive(Debug, Clone)] 16 | pub struct SharedMemoryNvm { 17 | memory: Arc>>, 18 | memory_start: usize, 19 | memory_end: usize, 20 | block_size: BlockSize, 21 | position: usize, 22 | } 23 | impl SharedMemoryNvm { 24 | /// 新しい`SharedMemoryNvm`インスタンスを生成する. 25 | /// 26 | /// `SharedMemoryNvm::with_block_size(memory, BlockSize::min())`と等しい。 27 | pub fn new(memory: Vec) -> Self { 28 | Self::with_block_size(memory, BlockSize::min()) 29 | } 30 | 31 | #[cfg(test)] 32 | pub fn to_bytes(&self) -> Vec { 33 | let lock = self.memory.lock().unwrap(); 34 | lock.clone() 35 | } 36 | 37 | /// ブロックサイズを指定して`SharedMemoryNvm`インスタンスを生成する. 38 | pub fn with_block_size(memory: Vec, block_size: BlockSize) -> Self { 39 | let memory_end = memory.len(); 40 | SharedMemoryNvm { 41 | memory: Arc::new(Mutex::new(memory)), 42 | block_size, 43 | memory_start: 0, 44 | memory_end, 45 | position: 0, 46 | } 47 | } 48 | 49 | /// ブロックサイズを変更する. 50 | pub fn set_block_size(&mut self, block_size: BlockSize) { 51 | self.block_size = block_size; 52 | } 53 | 54 | fn with_bytes_mut(&mut self, f: F) -> Result 55 | where 56 | F: FnOnce(&mut [u8]) -> T, 57 | { 58 | match self.memory.lock() { 59 | Ok(mut lock) => Ok(f(&mut lock[self.position..self.memory_end])), 60 | Err(error) => Err(track!(Error::from(error))), 61 | } 62 | } 63 | 64 | fn seek_impl(&mut self, position: u64) -> Result<()> { 65 | track_assert!( 66 | self.block_size().is_aligned(position), 67 | ErrorKind::InvalidInput 68 | ); 69 | self.position = self.memory_start + position as usize; 70 | track_assert!(self.position <= self.memory_end, ErrorKind::InvalidInput); 71 | Ok(()) 72 | } 73 | 74 | fn read_impl(&mut self, buf: &mut [u8]) -> Result { 75 | track_assert!( 76 | self.block_size().is_aligned(buf.len() as u64), 77 | ErrorKind::InvalidInput 78 | ); 79 | 80 | let size = track!(self.with_bytes_mut(|memory| { 81 | let len = cmp::min(memory.len(), buf.len()); 82 | (&mut buf[..len]).copy_from_slice(&memory[..len]); 83 | len 84 | }))?; 85 | self.position += size; 86 | Ok(size) 87 | } 88 | 89 | fn write_impl(&mut self, buf: &[u8]) -> Result<()> { 90 | track_assert!( 91 | self.block_size().is_aligned(buf.len() as u64), 92 | ErrorKind::InvalidInput 93 | ); 94 | 95 | let size = track!(self.with_bytes_mut(|memory| { 96 | let len = cmp::min(memory.len(), buf.len()); 97 | (&mut memory[..len]).copy_from_slice(&buf[..len]); 98 | len 99 | }))?; 100 | self.position += size; 101 | Ok(()) 102 | } 103 | } 104 | impl NonVolatileMemory for SharedMemoryNvm { 105 | fn sync(&mut self) -> Result<()> { 106 | Ok(()) 107 | } 108 | fn position(&self) -> u64 { 109 | (self.position - self.memory_start) as u64 110 | } 111 | fn capacity(&self) -> u64 { 112 | (self.memory_end - self.memory_start) as u64 113 | } 114 | fn block_size(&self) -> BlockSize { 115 | self.block_size 116 | } 117 | fn split(self, position: u64) -> Result<(Self, Self)> { 118 | track_assert_eq!( 119 | position, 120 | self.block_size().ceil_align(position), 121 | ErrorKind::InvalidInput 122 | ); 123 | track_assert!(position <= self.capacity(), ErrorKind::InvalidInput); 124 | let mut left = self.clone(); 125 | let mut right = self; 126 | 127 | left.memory_end = left.memory_start + position as usize; 128 | right.memory_start = left.memory_end; 129 | 130 | left.position = left.memory_start; 131 | right.position = right.memory_start; 132 | 133 | Ok((left, right)) 134 | } 135 | } 136 | impl Seek for SharedMemoryNvm { 137 | fn seek(&mut self, pos: SeekFrom) -> io::Result { 138 | let position = self.convert_to_offset(pos)?; 139 | track!(self.seek_impl(position))?; 140 | Ok(position) 141 | } 142 | } 143 | impl Read for SharedMemoryNvm { 144 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 145 | let read_size = track!(self.read_impl(buf))?; 146 | Ok(read_size) 147 | } 148 | } 149 | impl Write for SharedMemoryNvm { 150 | fn write(&mut self, buf: &[u8]) -> io::Result { 151 | track!(self.write_impl(buf))?; 152 | Ok(buf.len()) 153 | } 154 | fn flush(&mut self) -> io::Result<()> { 155 | Ok(()) 156 | } 157 | } 158 | 159 | #[cfg(test)] 160 | mod tests { 161 | use std::io::{Read, Seek, SeekFrom, Write}; 162 | use trackable::result::TestResult; 163 | 164 | use super::*; 165 | use crate::nvm::NonVolatileMemory; 166 | 167 | #[test] 168 | fn it_works() -> TestResult { 169 | let mut nvm = SharedMemoryNvm::new(vec![0; 1024]); 170 | assert_eq!(nvm.capacity(), 1024); 171 | assert_eq!(nvm.position(), 0); 172 | 173 | // read, write, seek 174 | let mut buf = vec![0; 512]; 175 | track_io!(nvm.read_exact(&mut buf))?; 176 | assert_eq!(buf, vec![0; 512]); 177 | assert_eq!(nvm.position(), 512); 178 | 179 | track_io!(nvm.write(&[1; 512][..]))?; 180 | assert_eq!(nvm.position(), 1024); 181 | 182 | track_io!(nvm.seek(SeekFrom::Start(512)))?; 183 | assert_eq!(nvm.position(), 512); 184 | 185 | track_io!(nvm.read_exact(&mut buf))?; 186 | assert_eq!(buf, vec![1; 512]); 187 | assert_eq!(nvm.position(), 1024); 188 | 189 | // split 190 | let (mut left, mut right) = track!(nvm.split(512))?; 191 | 192 | assert_eq!(left.capacity(), 512); 193 | track_io!(left.seek(SeekFrom::Start(0)))?; 194 | track_io!(left.read_exact(&mut buf))?; 195 | assert_eq!(buf, vec![0; 512]); 196 | assert_eq!(left.position(), 512); 197 | assert!(left.read_exact(&mut buf).is_err()); 198 | 199 | assert_eq!(right.capacity(), 512); 200 | track_io!(right.seek(SeekFrom::Start(0)))?; 201 | track_io!(right.read_exact(&mut buf))?; 202 | assert_eq!(buf, vec![1; 512]); 203 | assert_eq!(right.position(), 512); 204 | assert!(right.read_exact(&mut buf).is_err()); 205 | Ok(()) 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/storage/address.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Sub}; 2 | 3 | /// ストレージ内のアドレス表現に使われている40bit幅の整数値. 4 | /// 5 | /// アドレスの単位は、以下のように使用箇所によって異なっている: 6 | /// 7 | /// - ジャーナル領域: **バイト単位** 8 | ///- データ領域: **ブロック単位** 9 | #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] 10 | pub struct Address(u64); 11 | impl Address { 12 | /// 取り得るアドレスの最大値. 13 | pub const MAX: u64 = (1 << 40) - 1; 14 | 15 | /// アドレスの値を返す. 16 | pub fn as_u64(self) -> u64 { 17 | self.0 18 | } 19 | 20 | /// `value`を対応する位置のアドレスに変換する. 21 | /// 22 | /// `value`の値が40bit以内に収まらない場合には`None`が返される. 23 | pub fn from_u64(value: u64) -> Option { 24 | if value <= Self::MAX { 25 | Some(Address(value)) 26 | } else { 27 | None 28 | } 29 | } 30 | } 31 | impl From for Address { 32 | fn from(from: u32) -> Self { 33 | Address(u64::from(from)) 34 | } 35 | } 36 | impl Add for Address { 37 | type Output = Self; 38 | fn add(self, rhs: Self) -> Self { 39 | let value = self.0 + rhs.0; 40 | Address::from_u64(value).expect("address overflow") 41 | } 42 | } 43 | impl Sub for Address { 44 | type Output = Self; 45 | fn sub(self, rhs: Self) -> Self { 46 | let value = self.0.checked_sub(rhs.0).expect("address underflow"); 47 | Address(value) 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | 55 | #[test] 56 | fn it_works() { 57 | assert_eq!(Address::from_u64(0).map(|a| a.as_u64()), Some(0)); 58 | assert_eq!( 59 | Address::from_u64(Address::MAX).map(|a| a.as_u64()), 60 | Some(Address::MAX) 61 | ); 62 | assert_eq!(Address::from_u64(Address::MAX + 1), None); 63 | 64 | assert_eq!(Address::from(10) + Address::from(2), Address::from(12)); 65 | assert_eq!(Address::from(10) - Address::from(2), Address::from(8)); 66 | } 67 | 68 | #[test] 69 | #[should_panic] 70 | fn overflow() { 71 | let _ = Address::from_u64(Address::MAX).map(|a| a + Address::from(1)); 72 | } 73 | 74 | #[test] 75 | #[should_panic] 76 | fn underflow() { 77 | let _ = Address::from(0) - Address::from(1); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/storage/allocator/data_portion_allocator.rs: -------------------------------------------------------------------------------- 1 | //! Data Portion Allocator. 2 | 3 | use std::cmp; 4 | use std::collections::BTreeSet; 5 | use std::ops::Bound::{Excluded, Included, Unbounded}; 6 | 7 | use super::free_portion::{EndBasedFreePortion, FreePortion, SizeBasedFreePortion}; 8 | use super::U24; 9 | use crate::metrics::DataAllocatorMetrics; 10 | use crate::storage::portion::DataPortion; 11 | use crate::storage::Address; 12 | use crate::{ErrorKind, Result}; 13 | 14 | /// データ領域用のアロケータ. 15 | /// 16 | /// 指定された容量を有するデータ領域から、個々のlumpに必要な部分領域の割当を担当する. 17 | /// 18 | /// 割当の単位は"バイト"ではなく、"ブロック"となる. 19 | /// (ただし、これをアロケータのレイヤーで意識する必要はない) 20 | /// 21 | /// この実装自体は、完全にメモリ上のデータ構造であり、状態は永続化されない. 22 | /// 23 | /// ただし、部分領域群の割当状況自体はジャーナル領域に 24 | /// 情報が残っているので、アロケータインスタンスの構築時には、そこから前回の状態を復元することになる. 25 | /// 26 | /// # 割当戦略 27 | /// 28 | /// このアロケータは"BestFit"戦略を採用している. 29 | /// 30 | /// "BestFit"戦略では、空き領域のリストを管理している. 31 | /// 32 | /// 新規割当要求が発行された際には、空き領域のリストを探索し、 33 | /// 要求サイズを満たす空き領域の中で、一番サイズが小さいものが選択される. 34 | /// 35 | /// 選択された空き領域は、その中から要求サイズ分だけの割当を行い、 36 | /// もしまだ余剰分がある場合には、再び空き領域リストに戻される. 37 | #[derive(Debug)] 38 | pub struct DataPortionAllocator { 39 | size_to_free: BTreeSet, 40 | end_to_free: BTreeSet, 41 | metrics: DataAllocatorMetrics, 42 | } 43 | impl DataPortionAllocator { 44 | /// アロケータを構築する. 45 | /// 46 | /// `portions`には、既に割当済みの部分領域群が列挙されている. 47 | /// 48 | /// アロケータが利用可能な領域のサイズ(キャパシティ)の情報は、`metrics`から取得される. 49 | pub fn build(metrics: DataAllocatorMetrics, portions: I) -> Result 50 | where 51 | I: Iterator, 52 | { 53 | let block_size = u64::from(metrics.block_size.as_u16()); 54 | let mut portions = portions.collect::>(); 55 | metrics 56 | .allocated_portions_at_starting 57 | .add_u64(portions.len() as u64); 58 | metrics 59 | .allocated_bytes_at_starting 60 | .add_u64(portions.iter().map(|p| u64::from(p.len) * block_size).sum()); 61 | 62 | let sentinel = DataPortion { 63 | start: Address::from(0), 64 | len: 0, 65 | }; 66 | portions.push(sentinel); 67 | // DataPortionの終端を用いて降順ソートを行う。 68 | // すなわち、ソート後は、先頭であればあるほどend()の値は大きい。 69 | portions.sort_by_key(|&b| std::cmp::Reverse(b.end())); 70 | 71 | // 変数tailの意味は次の通り: 72 | // tail位置には値が書き込めない・書き込まれている、すなわち空いてはいない。 73 | let mut tail = metrics.capacity_bytes / block_size; 74 | let mut allocator = DataPortionAllocator { 75 | size_to_free: BTreeSet::new(), 76 | end_to_free: BTreeSet::new(), 77 | metrics, 78 | }; 79 | for portion in portions { 80 | track_assert!(portion.end().as_u64() <= tail, ErrorKind::InvalidInput); 81 | 82 | // endはexclusiveであることに注意する。 83 | // すなわち、endの手前まではデータが詰まっているが、endにはデータがない 84 | while portion.end().as_u64() < tail { 85 | let delta = tail - portion.end().as_u64(); // いま着目しているportionの後ろ側にある空きブロック数 86 | let size = cmp::min(0xFF_FFFF, delta) as U24; // 最大でも24バイト表現なので切り詰めを行う 87 | 88 | // tail-size位置 から size分 の空き容量があることが分かっているので 89 | // これを追加する 90 | tail -= u64::from(size); 91 | let free = FreePortion::new(Address::from_u64(tail).unwrap(), size); 92 | allocator.add_free_portion(free); 93 | } 94 | tail = portion.start.as_u64(); 95 | } 96 | Ok(allocator) 97 | } 98 | 99 | /// `size`分の部分領域の割当を行う. 100 | /// 101 | /// 十分な領域が存在しない場合には`None`が返される. 102 | pub fn allocate(&mut self, size: u16) -> Option { 103 | let portion = SizeBasedFreePortion(FreePortion::new(Address::from(0), U24::from(size))); 104 | if let Some(mut free) = self 105 | .size_to_free 106 | // `SizedBasedFreePortion`の全順序を用いて `size` を含むFreePortionを探す 107 | .range((Included(&portion), Unbounded)) 108 | // 従って、next()では(存在すれば)size以上かつ最小のFreePortionを取得することになる 109 | .next() 110 | .map(|p| p.0) 111 | { 112 | debug_assert!(U24::from(size) <= free.len()); 113 | self.delete_free_portion(free); 114 | let allocated = free.allocate(size); 115 | if free.len() > 0 { 116 | // まだfree portionに空きがある場合は再利用する 117 | self.add_free_portion(free); 118 | } 119 | self.metrics.count_allocation(allocated.len); 120 | Some(allocated) 121 | } else { 122 | self.metrics.nospace_failures.increment(); 123 | None 124 | } 125 | } 126 | 127 | /// 割当済みの部分領域の解放を行う. 128 | /// 129 | /// # 事前条件 130 | /// 131 | /// - `portion`は「以前に割当済み」かつ「未解放」の部分領域である 132 | pub fn release(&mut self, portion: DataPortion) { 133 | assert!(self.is_allocated_portion(&portion), "{:?}", portion); 134 | self.metrics.count_releasion(portion.len); 135 | let portion = self.merge_free_portions_if_possible(FreePortion::from(portion)); 136 | self.add_free_portion(portion); 137 | } 138 | 139 | /// アロケータ用のメトリクスを返す. 140 | pub fn metrics(&self) -> &DataAllocatorMetrics { 141 | &self.metrics 142 | } 143 | 144 | fn add_free_portion(&mut self, portion: FreePortion) { 145 | assert!(self.size_to_free.insert(SizeBasedFreePortion(portion))); 146 | assert!(self.end_to_free.insert(EndBasedFreePortion(portion))); 147 | self.metrics.inserted_free_portions.increment(); 148 | } 149 | 150 | fn delete_free_portion(&mut self, portion: FreePortion) { 151 | assert!(self.size_to_free.remove(&SizeBasedFreePortion(portion))); 152 | assert!(self.end_to_free.remove(&EndBasedFreePortion(portion))); 153 | self.metrics.removed_free_portions.increment(); 154 | } 155 | 156 | // `portion`と隣接する領域がフリーリスト内に存在する場合には、それらをまとめてしまう. 157 | fn merge_free_portions_if_possible(&mut self, mut portion: FreePortion) -> FreePortion { 158 | // 「`portion`の始端」に一致する終端を持つportion `prev`を探す。 159 | // もし存在するなら、 prev portion の並びでmerge可能である。 160 | // 注意: BTreeSetのgetでは、EqではなくOrd traitが用いられる。 161 | // 従ってendが一致する場合に限りOrdering::Equalとなる。 162 | let key = FreePortion::new(portion.start(), 0); 163 | if let Some(prev) = self.end_to_free.get(&EndBasedFreePortion(key)).map(|p| p.0) { 164 | if portion.checked_extend(prev.len()) { 165 | // trueの場合は副作用が発生するが、次で捨てる 166 | portion = FreePortion::new(prev.start(), portion.len()); 167 | self.delete_free_portion(prev); // prevの情報は不要なので削除 168 | } 169 | } 170 | 171 | // 「`portion`の終端」に一致する始端を持つportion `next` を探す。 172 | // もし存在するなら、 portion next の並びでmerge可能である。 173 | let key = FreePortion::new(portion.end(), 0); 174 | if let Some(next) = self 175 | .end_to_free 176 | .range((Excluded(&EndBasedFreePortion(key)), Unbounded)) 177 | .next() 178 | .map(|p| p.0) 179 | { 180 | // `next`については`portion.end < next.end`を満たす最小のポーションということしか分かっていない。 181 | // portion.end == next.start かどうかを確認する必要がある。 182 | if next.start() == portion.end() && portion.checked_extend(next.len()) { 183 | self.delete_free_portion(next); // nextの情報は不要なので削除 184 | } 185 | } 186 | 187 | portion 188 | } 189 | 190 | // EndBasedFreePortionを用いて、 191 | // フリーリスト内のいずれとも領域が重なっていないかどうかを検査する。 192 | // 領域が重なっていない場合 <=> 返り値がtrue に限り、割当済みの領域であると判断する。 193 | // 194 | // メモ: 195 | // 現在の実装では `next()` を用いているため、 196 | // フリーリスト内の相異なる部分領域が互いに素であるという前提が必要である。 197 | // ただしこの前提は通常のCannyLSの使用であれば成立する。 198 | fn is_allocated_portion(&self, portion: &DataPortion) -> bool { 199 | let key = EndBasedFreePortion(FreePortion::new(portion.start, 0)); 200 | if let Some(next) = self.end_to_free.range((Excluded(&key), Unbounded)).next() { 201 | // 終端位置が `portion.start` を超えるfree portionのうち最小のもの `next` については 202 | // - portion.end() <= next.0.start() すなわち overlapしていないか 203 | // - portion.end() > next.0.start() すなわち overlapしているか 204 | // を検査する 205 | portion.end() <= next.0.start() 206 | } else { 207 | true 208 | } 209 | } 210 | } 211 | 212 | #[cfg(test)] 213 | mod tests { 214 | use prometrics::metrics::MetricBuilder; 215 | use std::iter; 216 | use trackable::result::TestResult; 217 | 218 | use crate::block::BlockSize; 219 | use crate::lump::LumpId; 220 | use crate::metrics::DataAllocatorMetrics; 221 | use crate::storage::allocator::DataPortionAllocator; 222 | use crate::storage::index::LumpIndex; 223 | use crate::storage::portion::{DataPortion, Portion}; 224 | use crate::storage::Address; 225 | 226 | #[test] 227 | fn it_works() -> TestResult { 228 | let capacity = Address::from(24); 229 | let mut allocator = track!(DataPortionAllocator::build( 230 | metrics(capacity), 231 | iter::empty() 232 | ))?; 233 | assert_eq!(allocator.allocate(10), Some(portion(0, 10))); 234 | assert_eq!(allocator.allocate(10), Some(portion(10, 10))); 235 | assert_eq!(allocator.allocate(10), None); 236 | assert_eq!(allocator.allocate(4), Some(portion(20, 4))); 237 | 238 | allocator.release(portion(10, 10)); 239 | assert_eq!(allocator.allocate(5), Some(portion(10, 5))); 240 | assert_eq!(allocator.allocate(2), Some(portion(15, 2))); 241 | assert_eq!(allocator.allocate(4), None); 242 | 243 | let m = allocator.metrics(); 244 | assert_eq!(m.free_list_len(), 1); 245 | assert_eq!(m.allocated_portions(), 5); 246 | assert_eq!(m.released_portions(), 1); 247 | assert_eq!(m.nospace_failures(), 2); 248 | assert_eq!(m.usage_bytes(), 21 * u64::from(BlockSize::MIN)); 249 | assert_eq!(m.capacity_bytes, 24 * u64::from(BlockSize::MIN)); 250 | Ok(()) 251 | } 252 | 253 | #[test] 254 | #[should_panic] 255 | fn it_panics() { 256 | let capacity = Address::from(24); 257 | let mut allocator = DataPortionAllocator::build(metrics(capacity), iter::empty()) 258 | .expect("Unexpected panic"); 259 | 260 | // Try releasing an unallocated portion 261 | allocator.release(portion(10, 10)); 262 | } 263 | 264 | #[test] 265 | fn rebuild() -> TestResult { 266 | let mut index = LumpIndex::new(); 267 | index.insert(lump_id("000"), Portion::Data(portion(5, 10))); 268 | index.insert(lump_id("111"), Portion::Data(portion(2, 3))); 269 | index.insert(lump_id("222"), Portion::Data(portion(15, 5))); 270 | index.remove(&lump_id("000")); 271 | 272 | let capacity = Address::from(20); 273 | let mut allocator = track!(DataPortionAllocator::build( 274 | metrics(capacity), 275 | index.data_portions() 276 | ))?; 277 | assert_eq!(allocator.metrics().free_list_len(), 2); 278 | assert_eq!(allocator.metrics().allocated_portions(), 2); 279 | 280 | assert_eq!(allocator.allocate(11), None); 281 | assert_eq!(allocator.allocate(10), Some(portion(5, 10))); 282 | assert_eq!(allocator.allocate(3), None); 283 | assert_eq!(allocator.allocate(1), Some(portion(0, 1))); 284 | assert_eq!(allocator.allocate(1), Some(portion(1, 1))); 285 | assert_eq!(allocator.allocate(1), None); 286 | assert_eq!(allocator.metrics().free_list_len(), 0); 287 | assert_eq!( 288 | allocator.metrics().usage_bytes(), 289 | allocator.metrics().capacity_bytes 290 | ); 291 | Ok(()) 292 | } 293 | 294 | #[test] 295 | fn rebuild2() -> TestResult { 296 | let mut index = LumpIndex::new(); 297 | index.insert(lump_id("000"), Portion::Data(portion(1, 10))); 298 | index.insert(lump_id("222"), Portion::Data(portion(15, 5))); 299 | 300 | let capacity = Address::from(20); 301 | let mut allocator = track!(DataPortionAllocator::build( 302 | metrics(capacity), 303 | index.data_portions() 304 | ))?; 305 | 306 | assert_eq!(allocator.allocate(2), Some(portion(11, 2))); 307 | assert_eq!(allocator.allocate(1), Some(portion(0, 1))); 308 | Ok(()) 309 | } 310 | 311 | #[test] 312 | fn allocate_and_release() -> TestResult { 313 | let capacity = Address::from(419431); 314 | let mut allocator = track!(DataPortionAllocator::build( 315 | metrics(capacity), 316 | iter::empty() 317 | ))?; 318 | 319 | let p0 = allocator.allocate(65).unwrap(); 320 | let p1 = allocator.allocate(65).unwrap(); 321 | let p2 = allocator.allocate(65).unwrap(); 322 | allocator.release(p0); 323 | allocator.release(p1); 324 | 325 | let p3 = allocator.allocate(65).unwrap(); 326 | let p4 = allocator.allocate(65).unwrap(); 327 | allocator.release(p2); 328 | allocator.release(p3); 329 | 330 | let p5 = allocator.allocate(65).unwrap(); 331 | let p6 = allocator.allocate(65).unwrap(); 332 | allocator.release(p4); 333 | allocator.release(p5); 334 | allocator.release(p6); 335 | Ok(()) 336 | } 337 | 338 | fn lump_id(id: &str) -> LumpId { 339 | id.parse().unwrap() 340 | } 341 | 342 | fn portion(offset: u32, length: u16) -> DataPortion { 343 | DataPortion { 344 | start: Address::from(offset), 345 | len: length, 346 | } 347 | } 348 | 349 | fn metrics(capacity: Address) -> DataAllocatorMetrics { 350 | let capacity_bytes = capacity.as_u64() * u64::from(BlockSize::MIN); 351 | DataAllocatorMetrics::new(&MetricBuilder::new(), capacity_bytes, BlockSize::min()) 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /src/storage/allocator/free_portion.rs: -------------------------------------------------------------------------------- 1 | //! Free Portion. 2 | 3 | use std::cmp; 4 | 5 | use super::U24; 6 | use crate::storage::portion::DataPortion; 7 | use crate::storage::Address; 8 | 9 | /// 空き(割当可能)領域を表現するための構造体. 10 | /// 空き領域の開始位置と長さを保持しており、 11 | /// それぞれは `start` メソッドと `len` メソッドで取得できる。 12 | /// 13 | /// メモリを節約するために、内部的には64bit整数にエンコードして情報を保持している. 14 | /// その制約上、一つのインスタンスで表現可能な長さは24bit幅の範囲の制限されている. 15 | #[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)] 16 | pub struct FreePortion(u64); 17 | 18 | #[allow(clippy::len_without_is_empty)] 19 | impl FreePortion { 20 | /// 空き領域の開始位置 `offset` と 長さ `len` からインスタンスを作る。 21 | pub fn new(offset: Address, len: U24) -> Self { 22 | // Address構造体は事実上40bitであることに注意する。 23 | // lenを40bit左shiftしておくことで、`offset`と`len`の両方を 24 | // 64bitに詰め込む 25 | FreePortion((u64::from(len) << 40) | offset.as_u64()) 26 | } 27 | 28 | /// このPortionの開始位置を表す。 29 | pub fn start(self) -> Address { 30 | Address::from_u64(self.0 & Address::MAX).unwrap() 31 | } 32 | 33 | /// このPortionの終了位置を表す。 34 | pub fn end(self) -> Address { 35 | self.start() + Address::from(self.len()) 36 | } 37 | 38 | /// このPortionの長さを表す。 39 | pub fn len(self) -> U24 { 40 | (self.0 >> 40) as U24 41 | } 42 | 43 | /// `size`分だけ長さを増やす. 44 | /// 45 | /// ただし、それによって`U24`の範囲を超過してしまう場合には、更新は行わず、関数の結果として`false`を返す. 46 | pub fn checked_extend(&mut self, size: U24) -> bool { 47 | let new_len = u64::from(self.len()) + u64::from(size); 48 | if new_len <= 0xFF_FFFF { 49 | *self = FreePortion::new(self.start(), new_len as U24); 50 | true 51 | } else { 52 | false 53 | } 54 | } 55 | 56 | /// 先頭から`size`分だけ割り当てを行う. 57 | /// 58 | /// # Panics 59 | /// 60 | /// `size`が`self.len()`を超えている場合には、現在のスレッドがパニックする. 61 | pub fn allocate(&mut self, size: u16) -> DataPortion { 62 | assert!(U24::from(size) <= self.len()); 63 | // 自分自身を先頭からsizeでsplitする。 64 | // 前半をallocatedとし、後者により自分自身を更新する。 65 | let allocated = DataPortion { 66 | start: self.start(), 67 | len: size, 68 | }; 69 | *self = Self::new( 70 | self.start() + Address::from(u32::from(size)), 71 | self.len() - U24::from(size), 72 | ); 73 | allocated 74 | } 75 | } 76 | 77 | /// DataPortionの情報を保ったFreePortionを作る。 78 | /// すなわち、DataPortion `d` について次が成立する 79 | /// * `d.start == from(d).start()` 80 | /// * `d.len == from(d).len()` 81 | impl From for FreePortion { 82 | fn from(f: DataPortion) -> Self { 83 | FreePortion::new(f.start, U24::from(f.len)) 84 | } 85 | } 86 | 87 | /// 比較が"空き領域のサイズ順"で行われる`FreePortion`. 88 | /// 89 | /// 導入される全順序は、次の二つの全順序をこの順番で入れた辞書式順序 90 | /// - 1. 空き領域のサイズ 91 | /// - 2. 開始位置 92 | #[derive(Debug, Clone, PartialEq, Eq)] 93 | pub struct SizeBasedFreePortion(pub FreePortion); 94 | impl PartialOrd for SizeBasedFreePortion { 95 | fn partial_cmp(&self, other: &Self) -> Option { 96 | Some(self.cmp(other)) 97 | } 98 | } 99 | impl Ord for SizeBasedFreePortion { 100 | fn cmp(&self, other: &Self) -> cmp::Ordering { 101 | match self.0.len().cmp(&other.0.len()) { 102 | cmp::Ordering::Equal => self.0.start().cmp(&other.0.start()), 103 | not_equal => not_equal, 104 | } 105 | } 106 | } 107 | 108 | /// 比較が"終端位置が小さい順"で行われる`FreePortion`. 109 | #[derive(Debug, Clone, PartialEq, Eq)] 110 | pub struct EndBasedFreePortion(pub FreePortion); 111 | impl PartialOrd for EndBasedFreePortion { 112 | fn partial_cmp(&self, other: &Self) -> Option { 113 | Some(self.cmp(other)) 114 | } 115 | } 116 | impl Ord for EndBasedFreePortion { 117 | fn cmp(&self, other: &Self) -> cmp::Ordering { 118 | self.0.end().cmp(&other.0.end()) 119 | } 120 | } 121 | 122 | #[cfg(test)] 123 | mod tests { 124 | use super::*; 125 | use crate::storage::Address; 126 | 127 | #[test] 128 | fn it_works() { 129 | let mut p = FreePortion::new(Address::from(100), 50); 130 | assert_eq!(p.start(), Address::from(100)); 131 | assert_eq!(p.end(), Address::from(150)); 132 | assert_eq!(p.len(), 50); 133 | 134 | assert!(!p.checked_extend(0xFF_FFFF)); 135 | assert!(p.checked_extend(100)); 136 | assert_eq!(p.start(), Address::from(100)); 137 | assert_eq!(p.len(), 150); 138 | 139 | let allocated = p.allocate(30); 140 | assert_eq!(allocated.start, Address::from(100)); 141 | assert_eq!(allocated.len, 30); 142 | assert_eq!(p.start(), Address::from(130)); 143 | assert_eq!(p.len(), 120); 144 | 145 | let allocated = p.allocate(120); 146 | assert_eq!(allocated.start, Address::from(130)); 147 | assert_eq!(allocated.len, 120); 148 | assert_eq!(p.start(), Address::from(250)); 149 | assert_eq!(p.len(), 00); 150 | } 151 | 152 | #[test] 153 | #[should_panic] 154 | fn underflow() { 155 | let mut p = FreePortion::new(Address::from(100), 50); 156 | p.allocate(51); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/storage/allocator/mod.rs: -------------------------------------------------------------------------------- 1 | //! データ領域用のアロケータ. 2 | //! 3 | //! アロケータは、データ格納用に利用可能な連続した領域を(仮想的に)受け取り、 4 | //! 個々のlumpに対して、その中から必要なサイズの部分領域(Portion)を割り当てる責務を負っている。 5 | //! 6 | //! アロケータが担当するのは、領域の計算処理のみで、実際のデータの読み書き等を、この中で行うことは無い. 7 | pub use self::data_portion_allocator::DataPortionAllocator; 8 | 9 | mod data_portion_allocator; 10 | mod free_portion; 11 | 12 | /// 24bit幅の整数. 13 | type U24 = u32; 14 | -------------------------------------------------------------------------------- /src/storage/builder.rs: -------------------------------------------------------------------------------- 1 | use prometrics::metrics::MetricBuilder; 2 | use std::io::SeekFrom; 3 | use uuid::Uuid; 4 | 5 | use crate::block::BlockSize; 6 | use crate::metrics::{DataAllocatorMetrics, StorageMetrics}; 7 | use crate::nvm::NonVolatileMemory; 8 | use crate::storage::allocator::DataPortionAllocator; 9 | use crate::storage::data_region::DataRegion; 10 | use crate::storage::header::FULL_HEADER_SIZE; 11 | use crate::storage::index::LumpIndex; 12 | use crate::storage::journal::{JournalRegion, JournalRegionOptions}; 13 | use crate::storage::{ 14 | Storage, StorageHeader, MAJOR_VERSION, MAX_DATA_REGION_SIZE, MAX_JOURNAL_REGION_SIZE, 15 | MINOR_VERSION, 16 | }; 17 | use crate::{ErrorKind, Result}; 18 | 19 | /// `Storage`のビルダ. 20 | #[derive(Debug, Clone)] 21 | pub struct StorageBuilder { 22 | journal_region_ratio: f64, 23 | instance_uuid: Option, 24 | journal: JournalRegionOptions, 25 | metrics: MetricBuilder, 26 | } 27 | impl StorageBuilder { 28 | /// 新しい`StorageBuilder`インスタンスを生成する. 29 | pub fn new() -> Self { 30 | StorageBuilder { 31 | journal_region_ratio: 0.01, 32 | instance_uuid: None, 33 | journal: JournalRegionOptions::default(), 34 | metrics: MetricBuilder::new(), 35 | } 36 | } 37 | 38 | /// ストレージインスタンスを識別するためのUUIDを設定する. 39 | /// 40 | /// ストレージの作成時とオープン時で、指定した値の使われ方が異なる: 41 | /// 42 | /// - 作成時: 43 | /// - ここで指定した値が識別子として採用される 44 | /// - 本メソッドが呼ばれていない場合は、ランダムなUUIDが割り当てられる 45 | /// - オープン時: 46 | /// - ここで指定した値と、ストレージの識別子が比較され、もし異なっている場合にはオープンに失敗する 47 | /// - 本メソッドが呼ばれていない場合は、特にチェックは行われない 48 | pub fn instance_uuid(&mut self, uuid: Uuid) -> &mut Self { 49 | self.instance_uuid = Some(uuid); 50 | self 51 | } 52 | 53 | /// 利用可能な領域全体に占めるジャーナル領域の割合を設定する. 54 | /// 55 | /// 取り得る値は、0.0から1.0の間の小数である。 56 | /// この範囲外の値が指定された場合には、ストレージの構築時(e.g., `create()`呼び出し時)にエラーが返される. 57 | /// デフォルト値は、`0.01`. 58 | /// 59 | /// なお、これはストレージの新規作成時にのみ反映される値であり、 60 | /// 既存のストレージを開く場合には、作成時に指定された値が使用される. 61 | pub fn journal_region_ratio(&mut self, ratio: f64) -> &mut Self { 62 | self.journal_region_ratio = ratio; 63 | self 64 | } 65 | 66 | /// ジャーナル領域用のリングバッファのGCキューの長さ、を設定する. 67 | /// 68 | /// この値は、一回のGCで対象となるレコードの数、と等しい. 69 | /// 70 | /// デフォルト値は`4096`. 71 | /// 72 | /// # 参考 73 | /// 74 | /// - [ストレージのジャーナル領域のGC方法][gc] 75 | /// 76 | /// [gc]: https://github.com/frugalos/cannyls/wiki/Journal-Region-GC 77 | pub fn journal_gc_queue_size(&mut self, size: usize) -> &mut Self { 78 | self.journal.gc_queue_size = size; 79 | self 80 | } 81 | 82 | /// 物理デバイスへのジャーナルの同期間隔、を設定する. 83 | /// 84 | /// この値で指定された数のレコードがジャーナルに追加される度に、 85 | /// メモリ上のバッファが書き戻された上で、同期命令(e.g., `fdatasync`)が発行される. 86 | /// つまり、この間隔が長いほど書き込み時の性能は向上するが、信頼性は下がることになる. 87 | /// 88 | /// デフォルト値は`4096`. 89 | pub fn journal_sync_interval(&mut self, interval: usize) -> &mut Self { 90 | self.journal.sync_interval = interval; 91 | self 92 | } 93 | 94 | /// ストレージのブロックサイズを指定する. 95 | /// 96 | /// ここで指定した値は、ストレージの生成時にのみ使われる. 97 | /// (オープン時には、ヘッダに格納されている既存の値が使用される) 98 | /// 99 | /// デフォルト値は`BlockSize::min()`. 100 | /// 101 | /// # 注意 102 | /// 103 | /// ストレージのブロックサイズには、それが使用するNVMのブロック境界に揃った値を指定する必要がある. 104 | /// もしそうではない値が指定された場合には、ストレージの生成処理がエラーとなる. 105 | pub fn block_size(&mut self, block_size: BlockSize) -> &mut Self { 106 | self.journal.block_size = block_size; 107 | self 108 | } 109 | 110 | /// メトリクス用の共通設定を登録する. 111 | /// 112 | /// デフォルト値は`MetricBuilder::new()`. 113 | pub fn metrics(&mut self, metrics: MetricBuilder) -> &mut Self { 114 | self.metrics = metrics; 115 | self 116 | } 117 | 118 | /// 新規にストレージを生成する. 119 | pub fn create(&self, mut nvm: N) -> Result> 120 | where 121 | N: NonVolatileMemory, 122 | { 123 | let storage_block_size = self.journal.block_size; 124 | 125 | // NVMのブロック境界に揃っているかを確認 126 | track_assert!( 127 | storage_block_size.contains(nvm.block_size()), 128 | ErrorKind::InvalidInput; storage_block_size, nvm.block_size() 129 | ); 130 | 131 | let header = track!(self.make_header(nvm.capacity(), storage_block_size))?; 132 | 133 | track_io!(nvm.seek(SeekFrom::Start(0)))?; 134 | track!(nvm.aligned_write_all(|mut temp_buf| { 135 | // ストレージのヘッダを書き込む 136 | track!(header.write_header_region_to(&mut temp_buf))?; 137 | 138 | // ジャーナル領域を初期化する 139 | track!(JournalRegion::::initialize(temp_buf, storage_block_size))?; 140 | 141 | Ok(()) 142 | }))?; 143 | track!(nvm.sync())?; 144 | 145 | track!(self.open(nvm)) 146 | } 147 | 148 | /// 既に存在するストレージをオープンする. 149 | pub fn open(&self, mut nvm: N) -> Result> 150 | where 151 | N: NonVolatileMemory, 152 | { 153 | track_io!(nvm.seek(SeekFrom::Start(0)))?; 154 | 155 | // ヘッダを読み込む(アライメントを保証するためにバッファを経由) 156 | let buf = track!(nvm.aligned_read_bytes(FULL_HEADER_SIZE as usize))?; 157 | let mut header = track!(StorageHeader::read_from(&buf[..]))?; 158 | 159 | // ストレージのマイナーバージョンが古い場合には、最新に更新する 160 | if header.minor_version < MINOR_VERSION { 161 | header.minor_version = MINOR_VERSION; 162 | 163 | track_io!(nvm.seek(SeekFrom::Start(0)))?; 164 | track!(nvm.aligned_write_all(|temp_buf| { 165 | track!(header.write_header_region_to(temp_buf))?; 166 | Ok(()) 167 | }))?; 168 | } 169 | 170 | // `nvm`がストレージが採用しているブロックサイズに対応可能かを確認 171 | // 172 | // ヘッダに記載のストレージのブロックサイズが、NVMのブロック境界に揃っている場合には、 173 | // 完全一致ではなくても許容する 174 | track_assert!( 175 | header.block_size.contains(nvm.block_size()), 176 | ErrorKind::InvalidInput 177 | ); 178 | let mut journal_options = self.journal.clone(); 179 | journal_options.block_size = header.block_size; 180 | 181 | // UUIDをチェック 182 | if let Some(expected_uuid) = self.instance_uuid { 183 | track_assert_eq!(header.instance_uuid, expected_uuid, ErrorKind::InvalidInput); 184 | } 185 | 186 | // ジャーナルからインデックスとアロケータの状態を復元する 187 | let mut lump_index = LumpIndex::new(); 188 | let (journal_nvm, data_nvm) = track!(header.split_regions(nvm))?; 189 | let journal_region = track!(JournalRegion::open( 190 | journal_nvm, 191 | &mut lump_index, 192 | &self.metrics, 193 | journal_options 194 | ))?; 195 | 196 | let allocator = track!(DataPortionAllocator::build( 197 | DataAllocatorMetrics::new(&self.metrics, header.data_region_size, header.block_size), 198 | lump_index.data_portions(), 199 | ))?; 200 | 201 | // データ領域を準備 202 | let data_region = DataRegion::new(&self.metrics, allocator, data_nvm); 203 | 204 | let metrics = StorageMetrics::new( 205 | &self.metrics, 206 | &header, 207 | journal_region.metrics().clone(), 208 | data_region.metrics().clone(), 209 | ); 210 | metrics.put_lumps_at_starting.add_u64(lump_index.len()); 211 | Ok(Storage::new( 212 | header, 213 | journal_region, 214 | data_region, 215 | lump_index, 216 | metrics, 217 | )) 218 | } 219 | 220 | fn make_header(&self, capacity: u64, block_size: BlockSize) -> Result { 221 | let journal_and_data_region_size = track_assert_some!( 222 | capacity.checked_sub(StorageHeader::calc_region_size(block_size)), 223 | ErrorKind::InvalidInput, 224 | "Too small capacity: {}", 225 | capacity 226 | ); 227 | 228 | track_assert!( 229 | 0.0 <= self.journal_region_ratio && self.journal_region_ratio <= 1.0, 230 | ErrorKind::InvalidInput, 231 | "Invalid journal region ratio: {}", 232 | self.journal_region_ratio 233 | ); 234 | let journal_region_size = 235 | (journal_and_data_region_size as f64 * self.journal_region_ratio) as u64; 236 | let journal_region_size = block_size.ceil_align(journal_region_size); 237 | track_assert!( 238 | journal_region_size <= MAX_JOURNAL_REGION_SIZE, 239 | ErrorKind::InvalidInput, 240 | "Too large journal region: {} (capacity={}, ratio={})", 241 | journal_region_size, 242 | capacity, 243 | self.journal_region_ratio 244 | ); 245 | 246 | let data_region_size = 247 | block_size.floor_align(journal_and_data_region_size - journal_region_size); 248 | track_assert!( 249 | data_region_size <= MAX_DATA_REGION_SIZE, 250 | ErrorKind::InvalidInput, 251 | "Too large data region: {} (capacity={}, journal_region_ratio={})", 252 | data_region_size, 253 | capacity, 254 | self.journal_region_ratio 255 | ); 256 | 257 | Ok(StorageHeader { 258 | major_version: MAJOR_VERSION, 259 | minor_version: MINOR_VERSION, 260 | instance_uuid: self.instance_uuid.unwrap_or_else(Uuid::new_v4), 261 | block_size, 262 | journal_region_size, 263 | data_region_size, 264 | }) 265 | } 266 | } 267 | impl Default for StorageBuilder { 268 | fn default() -> Self { 269 | Self::new() 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/storage/data_region.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ByteOrder}; 2 | use prometrics::metrics::MetricBuilder; 3 | use std::io::{Read, SeekFrom, Write}; 4 | 5 | use crate::block::{AlignedBytes, BlockSize}; 6 | use crate::metrics::DataRegionMetrics; 7 | use crate::nvm::NonVolatileMemory; 8 | use crate::storage::allocator::DataPortionAllocator; 9 | use crate::storage::portion::DataPortion; 10 | use crate::{ErrorKind, Result}; 11 | 12 | /// 各データの末尾に埋め込まれる情報のサイズ. 13 | const LUMP_DATA_TRAILER_SIZE: usize = 2; 14 | 15 | /// ランプのデータを格納するための領域. 16 | #[derive(Debug)] 17 | pub struct DataRegion { 18 | allocator: DataPortionAllocator, 19 | nvm: N, 20 | block_size: BlockSize, 21 | metrics: DataRegionMetrics, 22 | } 23 | impl DataRegion 24 | where 25 | N: NonVolatileMemory, 26 | { 27 | /// 新しい`DataRegion`インスタンスを生成する. 28 | pub fn new(metric_builder: &MetricBuilder, allocator: DataPortionAllocator, nvm: N) -> Self { 29 | let capacity = allocator.metrics().capacity_bytes; 30 | let block_size = allocator.metrics().block_size; 31 | let allocator_metrics = allocator.metrics().clone(); 32 | DataRegion { 33 | allocator, 34 | nvm, 35 | block_size, 36 | metrics: DataRegionMetrics::new(metric_builder, capacity, allocator_metrics), 37 | } 38 | } 39 | 40 | /// データ領域のメトリクスを返す. 41 | pub fn metrics(&self) -> &DataRegionMetrics { 42 | &self.metrics 43 | } 44 | 45 | /// データを格納する. 46 | /// 47 | /// 格納場所は`DataRegion`が決定する. 48 | /// もし`data`を格納するだけの空きスペースがない場合には、`ErrorKind::StorageFull`エラーが返される. 49 | /// 50 | /// 成功した場合には、格納場所が返される. 51 | pub fn put(&mut self, data: &DataRegionLumpData) -> Result { 52 | track_assert!( 53 | data.block_size().contains(self.block_size), 54 | ErrorKind::InvalidInput 55 | ); 56 | let block_size = self.block_count(data.as_external_bytes().len() as u32) as u16; 57 | let portion = 58 | track_assert_some!(self.allocator.allocate(block_size), ErrorKind::StorageFull); 59 | 60 | let (offset, _size) = self.real_portion(&portion); 61 | track_io!(self.nvm.seek(SeekFrom::Start(offset)))?; 62 | track!(data.write_to(&mut self.nvm))?; 63 | 64 | // NOTE: 65 | // この後にジャーナルへの書き込みが行われ、 66 | // そこで(必要に応じて)`sync`メソッドが呼び出されるので、 67 | // この時点では`flush`のみに留める. 68 | track_io!(self.nvm.flush())?; 69 | 70 | Ok(portion) 71 | } 72 | 73 | /// 指定された領域に格納されているデータを取得する. 74 | /// 75 | /// `portion`で指定された領域が有効かどうかの判定は、このメソッド内では行われない. 76 | pub fn get(&mut self, portion: DataPortion) -> Result { 77 | let (offset, size) = self.real_portion(&portion); 78 | track_io!(self.nvm.seek(SeekFrom::Start(offset)))?; 79 | 80 | let buf = AlignedBytes::new(size, self.block_size); 81 | let data = track!(DataRegionLumpData::read_from(&mut self.nvm, buf))?; 82 | Ok(data) 83 | } 84 | 85 | /// 指定された領域に格納されているデータを削除する. 86 | /// 87 | /// # パニック 88 | /// 89 | /// `portion`で未割当の領域が指定された場合には、 90 | /// 現在の実行スレッドがパニックする. 91 | pub fn delete(&mut self, portion: DataPortion) { 92 | self.allocator.release(portion); 93 | } 94 | 95 | /// 部分領域の単位をブロックからバイトに変換する. 96 | fn real_portion(&self, portion: &DataPortion) -> (u64, usize) { 97 | let offset = portion.start.as_u64() * u64::from(self.block_size.as_u16()); 98 | let size = portion.len as usize * self.block_size.as_u16() as usize; 99 | (offset, size) 100 | } 101 | 102 | /// `size`分のデータをカバーするのに必要なブロック数. 103 | fn block_count(&self, size: u32) -> u32 { 104 | (size + u32::from(self.block_size.as_u16()) - 1) / u32::from(self.block_size.as_u16()) 105 | } 106 | } 107 | 108 | #[derive(Debug, Clone)] 109 | pub struct DataRegionLumpData { 110 | bytes: AlignedBytes, 111 | data_size: usize, 112 | } 113 | impl DataRegionLumpData { 114 | pub fn new(data_size: usize, block_size: BlockSize) -> Self { 115 | let size = data_size + LUMP_DATA_TRAILER_SIZE; 116 | let mut bytes = AlignedBytes::new(size, block_size); 117 | bytes.align(); 118 | 119 | let trailer_offset = bytes.len() - LUMP_DATA_TRAILER_SIZE; 120 | let padding_len = bytes.len() - size; 121 | debug_assert!(padding_len <= 0xFFFF); 122 | BigEndian::write_u16(&mut bytes[trailer_offset..], padding_len as u16); 123 | DataRegionLumpData { bytes, data_size } 124 | } 125 | 126 | pub fn block_size(&self) -> BlockSize { 127 | self.bytes.block_size() 128 | } 129 | 130 | pub fn as_bytes(&self) -> &[u8] { 131 | &self.bytes[..self.data_size] 132 | } 133 | 134 | pub fn as_bytes_mut(&mut self) -> &mut [u8] { 135 | &mut self.bytes[..self.data_size] 136 | } 137 | 138 | /// 永続化用のバイト列を返す. 139 | fn as_external_bytes(&self) -> &[u8] { 140 | self.bytes.as_ref() 141 | } 142 | 143 | fn write_to(&self, mut writer: W) -> Result<()> { 144 | track_io!(writer.write_all(self.as_external_bytes())) 145 | } 146 | 147 | fn read_from(mut reader: R, mut buf: AlignedBytes) -> Result { 148 | track_assert!(buf.len() >= LUMP_DATA_TRAILER_SIZE, ErrorKind::InvalidInput); 149 | track_io!(reader.read_exact(&mut buf))?; 150 | 151 | let padding_len = BigEndian::read_u16(&buf[buf.len() - LUMP_DATA_TRAILER_SIZE..]) as usize; 152 | let data_size = buf 153 | .len() 154 | .saturating_sub(LUMP_DATA_TRAILER_SIZE + padding_len); 155 | 156 | Ok(DataRegionLumpData { 157 | bytes: buf, 158 | data_size, 159 | }) 160 | } 161 | } 162 | 163 | #[cfg(test)] 164 | mod tests { 165 | use prometrics::metrics::MetricBuilder; 166 | use std::iter; 167 | use trackable::result::TestResult; 168 | 169 | use super::super::allocator::DataPortionAllocator; 170 | use super::*; 171 | use crate::block::BlockSize; 172 | use crate::metrics::DataAllocatorMetrics; 173 | use crate::nvm::MemoryNvm; 174 | 175 | #[test] 176 | fn data_region_works() -> TestResult { 177 | let capacity = 10 * 1024; 178 | let block_size = BlockSize::min(); 179 | let metrics = MetricBuilder::new(); 180 | let allocator = track!(DataPortionAllocator::build( 181 | DataAllocatorMetrics::new(&metrics, capacity, block_size), 182 | iter::empty(), 183 | ))?; 184 | let nvm = MemoryNvm::new(vec![0; capacity as usize]); 185 | let mut region = DataRegion::new(&metrics, allocator, nvm); 186 | 187 | // put 188 | let mut data = DataRegionLumpData::new(3, block_size); 189 | data.as_bytes_mut().copy_from_slice(b"foo"); 190 | let portion = track!(region.put(&data))?; 191 | 192 | // get 193 | assert_eq!( 194 | region.get(portion).ok().map(|d| d.as_bytes().to_owned()), 195 | Some(b"foo".to_vec()) 196 | ); 197 | Ok(()) 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/storage/header.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use std::fs::File; 3 | use std::io::{Read, Write}; 4 | use std::path::Path; 5 | use uuid::Uuid; 6 | 7 | use crate::block::BlockSize; 8 | use crate::nvm::NonVolatileMemory; 9 | use crate::storage::{ 10 | MAGIC_NUMBER, MAJOR_VERSION, MAX_DATA_REGION_SIZE, MAX_JOURNAL_REGION_SIZE, MINOR_VERSION, 11 | }; 12 | use crate::{ErrorKind, Result}; 13 | 14 | /// ヘッダを表現するのに必要なバイト数. 15 | const HEADER_SIZE: u16 = 16 | 2 /* major_version */ + 17 | 2 /* minor_version */ + 18 | 2 /* block_size */ + 19 | 16 /* UUID */ + 20 | 8 /* journal_region_size */ + 21 | 8 /* data_region_size */; 22 | 23 | /// **マジックナンバー** と **ヘッダサイズ** も含めたサイズ. 24 | pub(crate) const FULL_HEADER_SIZE: u16 = 4 + 2 + HEADER_SIZE; 25 | 26 | /// ストレージのヘッダ情報. 27 | /// 28 | /// # 参考 29 | /// 30 | /// - [ストレージフォーマット(v1.0)][format] 31 | /// 32 | /// [format]: https://github.com/frugalos/cannyls/wiki/Storage-Format 33 | #[derive(Debug, Clone)] 34 | pub struct StorageHeader { 35 | /// メジャーバージョン. 36 | /// 37 | /// メジャーバージョンが異なるストレージ同士のデータ形式には互換性が無い. 38 | /// 39 | /// 現在の最新バージョンは[`MAJOR_VERSION`](./constant.MAJOR_VERSION.html). 40 | pub major_version: u16, 41 | 42 | /// マイナーバージョン. 43 | /// 44 | /// マイナーバージョンには、後方互換性がある 45 | /// (i.e., 古い形式で作成されたストレージを、新しいプログラムで扱うことが可能). 46 | /// 47 | /// 現在の最新バージョンは[`MINOR_VERSION`](./constant.MINOR_VERSION.html). 48 | pub minor_version: u16, 49 | 50 | /// ストレージのブロックサイズ. 51 | pub block_size: BlockSize, 52 | 53 | /// ストレージの特定のインスタンスを識別するためのUUID. 54 | pub instance_uuid: Uuid, 55 | 56 | /// ジャーナル領域のサイズ(バイト単位). 57 | pub journal_region_size: u64, 58 | 59 | /// データ領域のサイズ(バイト単位). 60 | pub data_region_size: u64, 61 | } 62 | impl StorageHeader { 63 | /// ストレージが使用する領域全体のサイズを返す. 64 | /// 65 | /// 内訳としては **ヘッダ領域** と **ジャーナル領域** 、 **データ領域** のサイズの合計となる. 66 | pub fn storage_size(&self) -> u64 { 67 | self.region_size() + self.journal_region_size + self.data_region_size 68 | } 69 | 70 | /// ヘッダ領域のサイズを返す. 71 | /// 72 | /// **ヘッダ領域** には、以下が含まれる: 73 | /// 74 | /// - マジックナンバー 75 | /// - ヘッダ長 76 | /// - `StorageHeader` 77 | /// - 領域のサイズをブロック境界に揃えるためのパディング 78 | pub fn region_size(&self) -> u64 { 79 | Self::calc_region_size(self.block_size) 80 | } 81 | 82 | /// 存在するLump Storageから 83 | /// 保存済みのストレージヘッダを取り出す。 84 | pub fn read_from_file>(path: P) -> Result { 85 | let file = track_io!(File::open(path))?; 86 | track!(Self::read_from(file)) 87 | } 88 | 89 | /// ヘッダ情報を`reader`から読み込む. 90 | pub fn read_from(mut reader: R) -> Result { 91 | // magic number 92 | let mut magic_number = [0; 4]; 93 | track_io!(reader.read_exact(&mut magic_number))?; 94 | track_assert_eq!(magic_number, MAGIC_NUMBER, ErrorKind::InvalidInput); 95 | 96 | // header size 97 | let header_size = track_io!(reader.read_u16::())?; 98 | let mut reader = reader.take(u64::from(header_size)); 99 | 100 | // versions 101 | let major_version = track_io!(reader.read_u16::())?; 102 | let minor_version = track_io!(reader.read_u16::())?; 103 | track_assert_eq!( 104 | major_version, 105 | MAJOR_VERSION, 106 | ErrorKind::InvalidInput, 107 | "Unsupported major version", 108 | ); 109 | track_assert!( 110 | minor_version <= MINOR_VERSION, 111 | ErrorKind::InvalidInput, 112 | "Unsupported minor version: actual={}, supported={}", 113 | minor_version, 114 | MINOR_VERSION 115 | ); 116 | 117 | // block_size 118 | let block_size = track_io!(reader.read_u16::())?; 119 | let block_size = track!(BlockSize::new(block_size), "block_size:{}", block_size)?; 120 | 121 | // UUID 122 | let mut instance_uuid = [0; 16]; 123 | track_io!(reader.read_exact(&mut instance_uuid))?; 124 | let instance_uuid = Uuid::from_bytes(instance_uuid); 125 | 126 | // region sizes 127 | let journal_region_size = track_io!(reader.read_u64::())?; 128 | let data_region_size = track_io!(reader.read_u64::())?; 129 | track_assert!( 130 | journal_region_size <= MAX_JOURNAL_REGION_SIZE, 131 | ErrorKind::InvalidInput, 132 | "journal_region_size:{}", 133 | journal_region_size 134 | ); 135 | track_assert!( 136 | data_region_size <= MAX_DATA_REGION_SIZE, 137 | ErrorKind::InvalidInput, 138 | "data_region_size:{}", 139 | data_region_size 140 | ); 141 | 142 | track_assert_eq!(reader.limit(), 0, ErrorKind::InvalidInput); 143 | Ok(StorageHeader { 144 | major_version, 145 | minor_version, 146 | instance_uuid, 147 | block_size, 148 | journal_region_size, 149 | data_region_size, 150 | }) 151 | } 152 | 153 | /// ヘッダ情報を`writer`に書き込む. 154 | pub fn write_to(&self, mut writer: W) -> Result<()> { 155 | track_io!(writer.write_all(&MAGIC_NUMBER[..]))?; 156 | track_io!(writer.write_u16::(HEADER_SIZE))?; 157 | track_io!(writer.write_u16::(self.major_version))?; 158 | track_io!(writer.write_u16::(self.minor_version))?; 159 | track_io!(writer.write_u16::(self.block_size.as_u16()))?; 160 | track_io!(writer.write_all(self.instance_uuid.as_bytes()))?; 161 | track_io!(writer.write_u64::(self.journal_region_size))?; 162 | track_io!(writer.write_u64::(self.data_region_size))?; 163 | Ok(()) 164 | } 165 | 166 | /// ヘッダ領域を`writer`に書き込む. 167 | /// 168 | /// ヘッダ領域(サイズは`self.region_size()`)の未使用部分に0-パディングを行う以外は、 169 | /// `write_to`メソッドと同様. 170 | pub(crate) fn write_header_region_to(&self, mut writer: W) -> Result<()> { 171 | track!(self.write_to(&mut writer))?; 172 | 173 | let padding = vec![0; self.region_size() as usize - FULL_HEADER_SIZE as usize]; 174 | track_io!(writer.write_all(&padding))?; 175 | Ok(()) 176 | } 177 | 178 | /// 指定されたブロックサイズを有するストレージのために必要な、ヘッダ領域のサイズを計算する. 179 | pub(crate) fn calc_region_size(block_size: BlockSize) -> u64 { 180 | block_size.ceil_align(u64::from(FULL_HEADER_SIZE)) 181 | } 182 | 183 | /// 不揮発性メモリ全体の領域を分割して、ジャーナル領域およびデータ領域用のメモリを返す. 184 | pub(crate) fn split_regions(&self, nvm: N) -> Result<(N, N)> { 185 | let header_tail = self.region_size(); 186 | let (_, body_nvm) = track!(nvm.split(header_tail))?; 187 | let (journal_nvm, data_nvm) = track!(body_nvm.split(self.journal_region_size))?; 188 | Ok((journal_nvm, data_nvm)) 189 | } 190 | } 191 | 192 | #[cfg(test)] 193 | mod tests { 194 | use trackable::result::TestResult; 195 | use uuid::Uuid; 196 | 197 | use super::*; 198 | use crate::block::BlockSize; 199 | 200 | #[test] 201 | fn it_works() -> TestResult { 202 | let header = StorageHeader { 203 | major_version: MAJOR_VERSION, 204 | minor_version: MINOR_VERSION, 205 | block_size: BlockSize::min(), 206 | instance_uuid: Uuid::new_v4(), 207 | journal_region_size: 1024, 208 | data_region_size: 4096, 209 | }; 210 | 211 | // size 212 | assert_eq!(header.region_size(), u64::from(BlockSize::MIN)); 213 | assert_eq!( 214 | header.storage_size(), 215 | u64::from(BlockSize::MIN) + 1024 + 4096 216 | ); 217 | 218 | // read/write 219 | let mut buf = Vec::new(); 220 | track!(header.write_to(&mut buf))?; 221 | 222 | let h = track!(StorageHeader::read_from(&buf[..]))?; 223 | assert_eq!(h.major_version, header.major_version); 224 | assert_eq!(h.minor_version, header.minor_version); 225 | assert_eq!(h.block_size, header.block_size); 226 | assert_eq!(h.instance_uuid, header.instance_uuid); 227 | assert_eq!(h.journal_region_size, header.journal_region_size); 228 | assert_eq!(h.data_region_size, header.data_region_size); 229 | Ok(()) 230 | } 231 | 232 | #[test] 233 | fn compatibility_check_works() -> TestResult { 234 | // Lower minor version: OK 235 | let h = header(MAJOR_VERSION, MINOR_VERSION - 1); 236 | let mut buf = Vec::new(); 237 | track!(h.write_to(&mut buf))?; 238 | 239 | let h = track!(StorageHeader::read_from(&buf[..]))?; 240 | assert_eq!(h.major_version, MAJOR_VERSION); 241 | assert_eq!(h.minor_version, MINOR_VERSION - 1); 242 | 243 | // Current version: OK 244 | let h = header(MAJOR_VERSION, MINOR_VERSION); 245 | let mut buf = Vec::new(); 246 | track!(h.write_to(&mut buf))?; 247 | 248 | let h = track!(StorageHeader::read_from(&buf[..]))?; 249 | assert_eq!(h.major_version, MAJOR_VERSION); 250 | assert_eq!(h.minor_version, MINOR_VERSION); 251 | 252 | // Higher minor version: NG 253 | let h = header(MAJOR_VERSION, MINOR_VERSION + 1); 254 | let mut buf = Vec::new(); 255 | track!(h.write_to(&mut buf))?; 256 | 257 | assert!(StorageHeader::read_from(&buf[..]).is_err()); 258 | 259 | // Higher major version: NG 260 | let h = header(MAJOR_VERSION + 1, MINOR_VERSION); 261 | let mut buf = Vec::new(); 262 | track!(h.write_to(&mut buf))?; 263 | 264 | // Lower major version: NG 265 | let h = header(MAJOR_VERSION - 1, MINOR_VERSION); 266 | let mut buf = Vec::new(); 267 | track!(h.write_to(&mut buf))?; 268 | 269 | assert!(StorageHeader::read_from(&buf[..]).is_err()); 270 | 271 | Ok(()) 272 | } 273 | 274 | fn header(major_version: u16, minor_version: u16) -> StorageHeader { 275 | StorageHeader { 276 | major_version, 277 | minor_version, 278 | block_size: BlockSize::min(), 279 | instance_uuid: Uuid::new_v4(), 280 | journal_region_size: 1024, 281 | data_region_size: 4096, 282 | } 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/storage/index.rs: -------------------------------------------------------------------------------- 1 | //! デバイスに格納されているlump群の情報を管理するためのインデックス. 2 | use std::collections::{btree_map, BTreeMap}; 3 | use std::ops; 4 | 5 | use crate::block::BlockSize; 6 | use crate::lump::LumpId; 7 | use crate::storage::portion::{DataPortion, Portion, PortionU64}; 8 | use crate::storage::StorageUsage; 9 | 10 | /// Lump群の位置情報を保持するインデックス. 11 | /// 12 | /// デバイスに格納されているlumpのID群と、それぞれのデータの格納先の情報、を保持している. 13 | /// 14 | /// このインデックス自体は永続化されることはないメモリ上のデータ構造であり、 15 | /// デバイスの起動時に、ジャーナルの情報を用いて毎回再構築される. 16 | #[derive(Debug, Clone, Default)] 17 | pub struct LumpIndex { 18 | // `BTreeMap`の方が`HashMap`よりもメモリ効率が良いので、こちらを採用 19 | map: BTreeMap, 20 | } 21 | impl LumpIndex { 22 | /// 新しい`LumpIndex`インスタンスを生成する. 23 | pub fn new() -> Self { 24 | LumpIndex { 25 | map: BTreeMap::new(), 26 | } 27 | } 28 | 29 | /// 渡された範囲オブジェクトrangeを用いて、 30 | /// 登録されているlumpのうちrangeに含まれるもののストレージ使用量を返す。 31 | pub fn usage_range(&self, range: ops::Range, block_size: BlockSize) -> StorageUsage { 32 | StorageUsage::approximate(self.map.range(range).fold(0, |acc, (_, p)| { 33 | acc + Portion::from(*p).len(block_size) as u64 34 | })) 35 | } 36 | 37 | /// 指定されたlumpを検索する. 38 | pub fn get(&self, lump_id: &LumpId) -> Option { 39 | self.map.get(lump_id).map(|p| (*p).into()) 40 | } 41 | 42 | /// 新規lumpを登録する. 43 | pub fn insert(&mut self, lump_id: LumpId, portion: Portion) { 44 | self.map.insert(lump_id, portion.into()); 45 | } 46 | 47 | /// インデックスのサイズ(i.e., 登録lump数)を返す. 48 | /// 49 | /// 結果は昇順にソートされている. 50 | pub fn remove(&mut self, lump_id: &LumpId) -> Option { 51 | self.map.remove(lump_id).map(std::convert::Into::into) 52 | } 53 | 54 | /// 登録されているlumpのID一覧を返す. 55 | pub fn list(&self) -> Vec { 56 | self.map.keys().cloned().collect() 57 | } 58 | 59 | /// インデックスのサイズ(i.e., 登録lump数)を返す. 60 | pub fn len(&self) -> u64 { 61 | self.map.len() as u64 62 | } 63 | 64 | /// 割当済みのデータ部分領域を操作するためのイテレータを返す. 65 | pub fn data_portions(&self) -> DataPortions { 66 | DataPortions(self.map.values()) 67 | } 68 | 69 | /// 渡された範囲オブジェクトrangeを用いて、 70 | /// 登録されているlumpのうちrangeに含まれるものの一覧を返す。 71 | pub fn list_range(&self, range: ops::Range) -> Vec { 72 | let btree_range = self.map.range(range); 73 | btree_range.map(|(k, _)| *k).collect() 74 | } 75 | } 76 | 77 | #[derive(Debug)] 78 | pub struct DataPortions<'a>(btree_map::Values<'a, LumpId, PortionU64>); 79 | impl<'a> Iterator for DataPortions<'a> { 80 | type Item = DataPortion; 81 | fn next(&mut self) -> Option { 82 | for &portion in &mut self.0 { 83 | if let Portion::Data(portion) = portion.into() { 84 | return Some(portion); 85 | } 86 | } 87 | None 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/storage/journal/header.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use std::io::{Read, SeekFrom, Write}; 3 | 4 | use crate::Result; 5 | 6 | use crate::block::{AlignedBytes, BlockSize}; 7 | use crate::nvm::NonVolatileMemory; 8 | 9 | /// ジャーナルのヘッダ. 10 | #[derive(Debug, PartialEq, Eq)] 11 | pub struct JournalHeader { 12 | /// ジャーナルのリングバッファの始端位置. 13 | pub ring_buffer_head: u64, 14 | } 15 | impl JournalHeader { 16 | /// ストレージ初期化時のヘッダを生成する. 17 | pub fn new() -> Self { 18 | JournalHeader { 19 | ring_buffer_head: 0, 20 | } 21 | } 22 | 23 | /// ヘッダを書き込む. 24 | pub fn write_to(&self, mut writer: W, block_size: BlockSize) -> Result<()> { 25 | let padding = vec![0; JournalHeader::region_size(block_size) - 8]; 26 | track_io!(writer.write_u64::(self.ring_buffer_head))?; 27 | track_io!(writer.write_all(&padding))?; 28 | Ok(()) 29 | } 30 | 31 | /// ヘッダを読み込む. 32 | pub fn read_from(mut reader: R, block_size: BlockSize) -> Result { 33 | let mut padding = vec![0; JournalHeader::region_size(block_size) - 8]; 34 | let ring_buffer_head = track_io!(reader.read_u64::())?; 35 | track_io!(reader.read_exact(&mut padding))?; 36 | Ok(JournalHeader { ring_buffer_head }) 37 | } 38 | 39 | /// ヘッダ領域のサイズ(バイト数). 40 | pub fn region_size(block_size: BlockSize) -> usize { 41 | block_size.as_u16() as usize 42 | } 43 | } 44 | 45 | /// ジャーナルのヘッダ領域. 46 | /// 47 | /// 先頭の一ブロックがヘッダ用に割り当てられる. 48 | #[derive(Debug)] 49 | pub struct JournalHeaderRegion { 50 | /// ヘッダ用の領域. 51 | nvm: N, 52 | 53 | /// ストレージが採用しているブロックサイズ. 54 | block_size: BlockSize, 55 | } 56 | impl JournalHeaderRegion { 57 | /// ヘッダ領域管理用のインスタンスを生成する. 58 | pub fn new(nvm: N, block_size: BlockSize) -> Self { 59 | JournalHeaderRegion { nvm, block_size } 60 | } 61 | 62 | /// ヘッダを書き込む. 63 | pub fn write_header(&mut self, header: &JournalHeader) -> Result<()> { 64 | let mut buf = 65 | AlignedBytes::new(JournalHeader::region_size(self.block_size), self.block_size); 66 | track!(header.write_to(&mut buf[..], self.block_size))?; 67 | 68 | track_io!(self.nvm.seek(SeekFrom::Start(0)))?; 69 | track_io!(self.nvm.write_all(&buf))?; 70 | track!(self.nvm.sync())?; 71 | Ok(()) 72 | } 73 | 74 | /// ヘッダを読み込む. 75 | pub fn read_header(&mut self) -> Result { 76 | let mut buf = 77 | AlignedBytes::new(JournalHeader::region_size(self.block_size), self.block_size); 78 | track_io!(self.nvm.seek(SeekFrom::Start(0)))?; 79 | track_io!(self.nvm.read_exact(&mut buf))?; 80 | 81 | let header = track!(JournalHeader::read_from(&buf[..], self.block_size))?; 82 | Ok(header) 83 | } 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | use trackable::result::TestResult; 89 | 90 | use super::*; 91 | use crate::block::BlockSize; 92 | 93 | #[test] 94 | fn it_works() -> TestResult { 95 | let block_size = BlockSize::min(); 96 | let header = JournalHeader { 97 | ring_buffer_head: 1234, 98 | }; 99 | 100 | let mut buf = Vec::new(); 101 | track!(header.write_to(&mut buf, block_size))?; 102 | assert_eq!( 103 | JournalHeader::read_from(&buf[..], block_size).ok(), 104 | Some(header) 105 | ); 106 | Ok(()) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/storage/journal/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::header::{JournalHeader, JournalHeaderRegion}; 2 | pub use self::nvm_buffer::JournalNvmBuffer; 3 | pub use self::options::JournalRegionOptions; 4 | pub use self::record::{JournalEntry, JournalRecord}; 5 | pub use self::region::JournalRegion; 6 | 7 | mod header; 8 | mod nvm_buffer; 9 | mod options; 10 | mod record; 11 | mod region; 12 | mod ring_buffer; 13 | 14 | /// ジャーナル領域のスナップショット。 15 | pub struct JournalSnapshot { 16 | /// ジャーナル領域の未開放開始位置。 17 | pub unreleased_head: u64, 18 | 19 | /// ジャーナル領域の開始位置。 20 | /// 21 | /// 古いジャーナルエントリを指す。 22 | pub head: u64, 23 | 24 | /// ジャーナル領域の末尾位置。 25 | /// 26 | /// 最新のジャーナルエントリの一つ後ろを指している。 27 | /// エントリはここ追記される。 28 | pub tail: u64, 29 | 30 | /// 開始位置から末尾位置まで順に読んで得られたジャーナルエントリ群。 31 | pub entries: Vec, 32 | } 33 | -------------------------------------------------------------------------------- /src/storage/journal/nvm_buffer.rs: -------------------------------------------------------------------------------- 1 | use std::cmp; 2 | use std::io::{self, Read, Seek, SeekFrom, Write}; 3 | use std::ptr; 4 | 5 | use crate::block::{AlignedBytes, BlockSize}; 6 | use crate::nvm::NonVolatileMemory; 7 | use crate::{ErrorKind, Result}; 8 | 9 | /// ジャーナル領域用のバッファ. 10 | /// 11 | /// 内部の`NonVolatileMemory`実装のアライメント制約(i.e., ストレージのブロック境界に揃っている)を満たしつつ、 12 | /// ジャーナル領域への追記を効率化するのが目的. 13 | #[derive(Debug)] 14 | pub struct JournalNvmBuffer { 15 | // ジャーナル領域のデータを、実際に永続化するために使用される内部のNVMインスタンス 16 | inner: N, 17 | 18 | // 現在の読み書きカーソルの位置 19 | position: u64, 20 | 21 | // 書き込みバッファ 22 | // 23 | // ジャーナル領域から発行された書き込み要求は、 24 | // 以下のいずれかの条件を満たすまでは、メモリ上の本バッファに保持されており、 25 | // 内部NVMには反映されないままとなる: 26 | // - `sync`メソッドが呼び出された: 27 | // - ジャーナル領域は定期的に本メソッドを呼び出す 28 | // - 書き込みバッファのカバー範囲に重複する領域に対して、読み込み要求が発行された場合: 29 | // - 書き込みバッファの内容をフラッシュして、内部NVMに同期した後に、該当読み込み命令を処理 30 | // - 書き込みバッファのカバー範囲に重複しない領域に対して、書き込み要求が発行された場合: 31 | // - 現状の書き込みバッファのデータ構造では、ギャップ(i.e., 連続しない複数部分領域)を表現することはできない 32 | // - そのため、一度古いバッファの内容をフラッシュした後に、該当書き込み要求を処理するためのバッファを作成する 33 | // 34 | // ジャーナル領域が発行した書き込み要求を、 35 | // 内部NVMのブロック境界に合うようにアライメントする役目も担っている。 36 | write_buf: AlignedBytes, 37 | 38 | // `write_buf`の始端が、内部NVM上のどの位置に対応するかを保持するためのフィールド 39 | // 40 | // 「内部NVM上での位置を指す」という点では`position`フィールドと似ているが、 41 | // `position`は読み書きやシーク操作の度に値が更新されるのに対して、 42 | // `write_buf_offset`は、書き込みバッファの内容がフラッシュされるまでは、 43 | // 固定の値が使用され続ける。 44 | write_buf_offset: u64, 45 | 46 | // 書き込みバッファ内にデータが溜まっているかどうかを判定するためのフラグ 47 | // 48 | // 一度でも書き込みバッファにデータが書かれたら`true`に設定され、 49 | // 内部NVMにバッファ内のデータがフラッシュされた後は`false`に設定される。 50 | maybe_dirty: bool, 51 | 52 | // 読み込みバッファ 53 | // 54 | // ジャーナル領域が発行した読み込み要求を、 55 | // 内部NVMのブロック境界に合うようにアライメントするために使用される。 56 | read_buf: AlignedBytes, 57 | } 58 | impl JournalNvmBuffer { 59 | /// 新しい`JournalNvmBuffer`インスタンスを生成する. 60 | /// 61 | /// これは実際に読み書きには`nvm`を使用する. 62 | /// 63 | /// なお`nvm`へのアクセス時に、それが`nvm`が要求するブロック境界にアライメントされていることは、 64 | /// `JournalNvmBuffer`が保証するため、利用者が気にする必要はない. 65 | /// 66 | /// ただし、シーク時には、シーク地点を含まない次のブロック境界までのデータは 67 | /// 上書きされてしまうので注意が必要. 68 | pub fn new(nvm: N) -> Self { 69 | let block_size = nvm.block_size(); 70 | JournalNvmBuffer { 71 | inner: nvm, 72 | position: 0, 73 | maybe_dirty: false, 74 | write_buf_offset: 0, 75 | write_buf: AlignedBytes::new(0, block_size), 76 | read_buf: AlignedBytes::new(0, block_size), 77 | } 78 | } 79 | 80 | #[cfg(test)] 81 | pub fn nvm(&self) -> &N { 82 | &self.inner 83 | } 84 | 85 | fn is_dirty_area(&self, offset: u64, length: usize) -> bool { 86 | if !self.maybe_dirty || length == 0 || self.write_buf.is_empty() { 87 | return false; 88 | } 89 | if self.write_buf_offset < offset { 90 | let buf_end = self.write_buf_offset + self.write_buf.len() as u64; 91 | offset < buf_end 92 | } else { 93 | let end = offset + length as u64; 94 | self.write_buf_offset < end 95 | } 96 | } 97 | 98 | fn flush_write_buf(&mut self) -> Result<()> { 99 | if self.write_buf.is_empty() || !self.maybe_dirty { 100 | return Ok(()); 101 | } 102 | 103 | track_io!(self.inner.seek(SeekFrom::Start(self.write_buf_offset)))?; 104 | track_io!(self.inner.write(&self.write_buf))?; 105 | if self.write_buf.len() > self.block_size().as_u16() as usize { 106 | // このif節では、 107 | // バッファに末端のalignmentバイト分(= new_len)の情報を残す。 108 | // write_buf_offsetは、write_buf.len() - new_len(= drop_len)分だけ進められる。 109 | // 110 | // write_buf_offsetを、書き出しに成功したwrite_buf.len()分だけ進めて、 111 | // write_bufをクリアすることもできるが、 112 | // ブロック長でしか書き出すことができないため、その場合は次回の書き込み時に 113 | // NVMに一度アクセスしてブロック全体を取得しなくてはならない。 114 | // この読み込みを避けるため、現在の実装の形をとっている。 115 | let new_len = self.block_size().as_u16() as usize; 116 | let drop_len = self.write_buf.len() - new_len; 117 | unsafe { 118 | // This nonoverlappingness is guranteed by the callers. 119 | ptr::copy( 120 | self.write_buf.as_ptr().add(drop_len), // src 121 | self.write_buf.as_mut_ptr(), // dst 122 | new_len, 123 | ); 124 | } 125 | self.write_buf.truncate(new_len); 126 | 127 | self.write_buf_offset += drop_len as u64; 128 | } 129 | self.maybe_dirty = false; 130 | Ok(()) 131 | } 132 | 133 | fn check_overflow(&self, write_len: usize) -> Result<()> { 134 | let next_position = self.position() + write_len as u64; 135 | track_assert!( 136 | next_position <= self.capacity(), 137 | ErrorKind::InconsistentState, 138 | "self.position={}, write_len={}, self.len={}", 139 | self.position(), 140 | write_len, 141 | self.capacity() 142 | ); 143 | Ok(()) 144 | } 145 | } 146 | impl NonVolatileMemory for JournalNvmBuffer { 147 | fn sync(&mut self) -> Result<()> { 148 | track!(self.flush_write_buf())?; 149 | self.inner.sync() 150 | } 151 | 152 | fn position(&self) -> u64 { 153 | self.position 154 | } 155 | 156 | fn capacity(&self) -> u64 { 157 | self.inner.capacity() 158 | } 159 | 160 | fn block_size(&self) -> BlockSize { 161 | self.inner.block_size() 162 | } 163 | 164 | fn split(self, _: u64) -> Result<(Self, Self)> { 165 | unreachable!() 166 | } 167 | } 168 | impl Drop for JournalNvmBuffer { 169 | fn drop(&mut self) { 170 | let _ = self.sync(); 171 | } 172 | } 173 | impl Seek for JournalNvmBuffer { 174 | fn seek(&mut self, pos: SeekFrom) -> io::Result { 175 | let offset = track!(self.convert_to_offset(pos))?; 176 | self.position = offset; 177 | Ok(offset) 178 | } 179 | } 180 | impl Read for JournalNvmBuffer { 181 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 182 | if self.is_dirty_area(self.position, buf.len()) { 183 | track!(self.flush_write_buf())?; 184 | } 185 | 186 | let aligned_start = self.block_size().floor_align(self.position); 187 | let aligned_end = self 188 | .block_size() 189 | .ceil_align(self.position + buf.len() as u64); 190 | 191 | self.read_buf 192 | .aligned_resize((aligned_end - aligned_start) as usize); 193 | self.inner.seek(SeekFrom::Start(aligned_start))?; 194 | let inner_read_size = self.inner.read(&mut self.read_buf)?; 195 | 196 | let start = (self.position - aligned_start) as usize; 197 | let end = cmp::min(inner_read_size, start + buf.len()); 198 | let read_size = end - start; 199 | (&mut buf[..read_size]).copy_from_slice(&self.read_buf[start..end]); 200 | self.position += read_size as u64; 201 | Ok(read_size) 202 | } 203 | } 204 | impl Write for JournalNvmBuffer { 205 | fn write(&mut self, buf: &[u8]) -> io::Result { 206 | track!(self.check_overflow(buf.len()))?; 207 | 208 | let write_buf_start = self.write_buf_offset; 209 | let write_buf_end = write_buf_start + self.write_buf.len() as u64; 210 | if write_buf_start <= self.position && self.position <= write_buf_end { 211 | // 領域が重複しており、バッファの途中から追記可能 212 | // (i.e., 書き込みバッファのフラッシュが不要) 213 | let start = (self.position - self.write_buf_offset) as usize; 214 | let end = start + buf.len(); 215 | self.write_buf.aligned_resize(end); 216 | (&mut self.write_buf[start..end]).copy_from_slice(buf); 217 | self.position += buf.len() as u64; 218 | self.maybe_dirty = true; 219 | Ok(buf.len()) 220 | } else { 221 | // 領域に重複がないので、一度バッファの中身を書き戻す 222 | track!(self.flush_write_buf())?; 223 | 224 | if self.block_size().is_aligned(self.position) { 225 | self.write_buf_offset = self.position; 226 | self.write_buf.aligned_resize(0); 227 | } else { 228 | // シーク位置より前方の既存データが破棄されてしまわないように、一度読み込みを行う. 229 | let size = self.block_size().as_u16(); 230 | self.write_buf_offset = self.block_size().floor_align(self.position); 231 | self.write_buf.aligned_resize(size as usize); 232 | self.inner.seek(SeekFrom::Start(self.write_buf_offset))?; 233 | self.inner.read_exact(&mut self.write_buf)?; 234 | } 235 | self.write(buf) 236 | } 237 | } 238 | 239 | fn flush(&mut self) -> io::Result<()> { 240 | track!(self.flush_write_buf())?; 241 | Ok(()) 242 | } 243 | } 244 | 245 | #[cfg(test)] 246 | mod tests { 247 | use std::io::{Read, Seek, SeekFrom, Write}; 248 | use trackable::result::TestResult; 249 | 250 | use super::*; 251 | use crate::nvm::MemoryNvm; 252 | 253 | #[test] 254 | fn write_write_flush() -> TestResult { 255 | // 連続領域の書き込みは`flush`するまでバッファに残り続ける 256 | let mut buffer = new_buffer(); 257 | track_io!(buffer.write_all(b"foo"))?; 258 | assert_eq!(&buffer.nvm().as_bytes()[0..3], &[0; 3][..]); 259 | 260 | track_io!(buffer.write_all(b"bar"))?; 261 | assert_eq!(&buffer.nvm().as_bytes()[0..3], &[0; 3][..]); 262 | assert_eq!(&buffer.nvm().as_bytes()[3..6], &[0; 3][..]); 263 | 264 | track_io!(buffer.flush())?; 265 | assert_eq!(&buffer.nvm().as_bytes()[0..6], b"foobar"); 266 | Ok(()) 267 | } 268 | 269 | #[test] 270 | fn write_seek_write_flush() -> TestResult { 271 | // "連続"の判定は、ブロック単位で行われる 272 | // (シークしてもブロックを跨がないと"連続していない"と判定されない) 273 | let mut buffer = new_buffer(); 274 | track_io!(buffer.write_all(b"foo"))?; 275 | assert_eq!(&buffer.nvm().as_bytes()[0..3], &[0; 3][..]); 276 | 277 | track_io!(buffer.seek(SeekFrom::Current(1)))?; 278 | track_io!(buffer.write_all(b"bar"))?; 279 | assert_eq!(&buffer.nvm().as_bytes()[0..3], &[0; 3][..]); 280 | assert_eq!(&buffer.nvm().as_bytes()[4..7], &[0; 3][..]); 281 | 282 | track_io!(buffer.flush())?; 283 | assert_eq!(&buffer.nvm().as_bytes()[0..3], b"foo"); 284 | assert_eq!(&buffer.nvm().as_bytes()[4..7], b"bar"); 285 | 286 | // シーク先を遠くした場合でも、連続するブロック内に収まっているなら同様 287 | let mut buffer = new_buffer(); 288 | track_io!(buffer.write_all(b"foo"))?; 289 | assert_eq!(&buffer.nvm().as_bytes()[0..3], &[0; 3][..]); 290 | 291 | track_io!(buffer.seek(SeekFrom::Start(512)))?; 292 | track_io!(buffer.write_all(b"bar"))?; 293 | assert_eq!(&buffer.nvm().as_bytes()[0..3], &[0; 3][..]); 294 | assert_eq!(&buffer.nvm().as_bytes()[512..515], &[0; 3][..]); 295 | 296 | track_io!(buffer.flush())?; 297 | assert_eq!(&buffer.nvm().as_bytes()[0..3], b"foo"); 298 | assert_eq!(&buffer.nvm().as_bytes()[512..515], b"bar"); 299 | 300 | // 書き込み領域が重なっている場合も同様 301 | let mut buffer = new_buffer(); 302 | track_io!(buffer.write_all(b"foo"))?; 303 | assert_eq!(&buffer.nvm().as_bytes()[0..3], &[0; 3][..]); 304 | 305 | track_io!(buffer.seek(SeekFrom::Current(-1)))?; 306 | track_io!(buffer.write_all(b"bar"))?; 307 | assert_eq!(&buffer.nvm().as_bytes()[0..3], &[0; 3][..]); 308 | assert_eq!(&buffer.nvm().as_bytes()[2..5], &[0; 3][..]); 309 | 310 | track_io!(buffer.flush())?; 311 | assert_eq!(&buffer.nvm().as_bytes()[0..5], b"fobar"); 312 | Ok(()) 313 | } 314 | 315 | #[test] 316 | fn write_seek_write() -> TestResult { 317 | // 書き込み先が(ブロック単位で)隣接しなくなった場合は、現在のバッファの中身がNVMに書き戻される 318 | let mut buffer = new_buffer(); 319 | track_io!(buffer.write_all(b"foo"))?; 320 | assert_eq!(&buffer.nvm().as_bytes()[0..3], &[0; 3][..]); 321 | 322 | track_io!(buffer.seek(SeekFrom::Start(513)))?; 323 | track_io!(buffer.write_all(b"bar"))?; 324 | assert_eq!(&buffer.nvm().as_bytes()[0..3], b"foo"); 325 | assert_eq!(&buffer.nvm().as_bytes()[513..516], &[0; 3][..]); 326 | Ok(()) 327 | } 328 | 329 | #[test] 330 | fn write_seek_read() -> TestResult { 331 | // 読み込み先が、書き込みバッファと重なっている場合には、バッファの中身がNVMに書き戻される 332 | let mut buffer = new_buffer(); 333 | track_io!(buffer.write_all(b"foo"))?; 334 | assert_eq!(&buffer.nvm().as_bytes()[0..3], &[0; 3][..]); 335 | 336 | track_io!(buffer.read_exact(&mut [0; 1][..]))?; 337 | assert_eq!(&buffer.nvm().as_bytes()[0..3], b"foo"); 338 | 339 | // 読み込み先が、書き込みバッファと重なっていない場合には、書き戻されない 340 | let mut buffer = new_buffer(); 341 | track_io!(buffer.write_all(b"foo"))?; 342 | assert_eq!(&buffer.nvm().as_bytes()[0..3], &[0; 3][..]); 343 | 344 | track_io!(buffer.seek(SeekFrom::Start(512)))?; 345 | track_io!(buffer.read_exact(&mut [0; 1][..]))?; 346 | assert_eq!(&buffer.nvm().as_bytes()[0..3], &[0; 3][..]); 347 | Ok(()) 348 | } 349 | 350 | #[test] 351 | fn overwritten() -> TestResult { 352 | // シーク地点よりも前方のデータは保持される. 353 | // (後方の、次のブロック境界までのデータがどうなるかは未定義) 354 | let mut buffer = new_buffer(); 355 | track_io!(buffer.write_all(&[b'a'; 512]))?; 356 | track_io!(buffer.flush())?; 357 | assert_eq!(&buffer.nvm().as_bytes()[0..512], &[b'a'; 512][..]); 358 | 359 | track_io!(buffer.seek(SeekFrom::Start(256)))?; 360 | track_io!(buffer.write_all(&[b'b'; 1]))?; 361 | track_io!(buffer.flush())?; 362 | assert_eq!(&buffer.nvm().as_bytes()[0..256], &[b'a'; 256][..]); 363 | assert_eq!(buffer.nvm().as_bytes()[256], b'b'); 364 | Ok(()) 365 | } 366 | 367 | fn new_buffer() -> JournalNvmBuffer { 368 | let nvm = MemoryNvm::new(vec![0; 10 * 1024]); 369 | JournalNvmBuffer::new(nvm) 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /src/storage/journal/options.rs: -------------------------------------------------------------------------------- 1 | use crate::block::BlockSize; 2 | 3 | /// ジャーナル領域の挙動を調整するためのパラメータ群. 4 | /// 5 | /// 各オプションの説明は`StorageBuilder'のドキュメントを参照のこと. 6 | #[derive(Debug, Clone)] 7 | pub struct JournalRegionOptions { 8 | pub gc_queue_size: usize, 9 | pub sync_interval: usize, 10 | pub block_size: BlockSize, 11 | } 12 | impl Default for JournalRegionOptions { 13 | fn default() -> Self { 14 | JournalRegionOptions { 15 | gc_queue_size: 0x1000, 16 | sync_interval: 0x1000, 17 | block_size: BlockSize::min(), 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/storage/journal/record.rs: -------------------------------------------------------------------------------- 1 | use adler32::RollingAdler32; 2 | use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt}; 3 | use std::io::{Read, Write}; 4 | use std::ops::Range; 5 | 6 | use crate::lump::LumpId; 7 | use crate::storage::portion::DataPortion; 8 | use crate::storage::Address; 9 | use crate::{ErrorKind, Result}; 10 | 11 | pub const TAG_SIZE: usize = 1; 12 | pub const CHECKSUM_SIZE: usize = 4; 13 | pub const LENGTH_SIZE: usize = 2; 14 | pub const PORTION_SIZE: usize = 5; 15 | pub const END_OF_RECORDS_SIZE: usize = CHECKSUM_SIZE + TAG_SIZE; 16 | pub const EMBEDDED_DATA_OFFSET: usize = CHECKSUM_SIZE + TAG_SIZE + LumpId::SIZE + LENGTH_SIZE; 17 | 18 | const TAG_END_OF_RECORDS: u8 = 0; 19 | const TAG_GO_TO_FRONT: u8 = 1; 20 | const TAG_PUT: u8 = 3; 21 | const TAG_EMBED: u8 = 4; 22 | const TAG_DELETE: u8 = 5; 23 | const TAG_DELETE_RANGE: u8 = 6; 24 | 25 | /// ジャーナル領域のリングバッファのエントリ. 26 | #[derive(Debug)] 27 | pub struct JournalEntry { 28 | /// ジャーナル内でのレコードの開始位置. 29 | pub start: Address, 30 | 31 | /// レコード. 32 | pub record: JournalRecord>, 33 | } 34 | impl JournalEntry { 35 | /// ジャーナル内でのレコードの終端位置を返す. 36 | pub fn end(&self) -> Address { 37 | self.start + Address::from(self.record.external_size() as u32) 38 | } 39 | } 40 | 41 | /// ジャーナル領域のリングバッファに追記されていくレコード. 42 | #[allow(missing_docs)] 43 | #[derive(Debug, PartialEq, Eq)] 44 | pub enum JournalRecord { 45 | EndOfRecords, 46 | GoToFront, 47 | Put(LumpId, DataPortion), 48 | Embed(LumpId, T), 49 | Delete(LumpId), 50 | DeleteRange(Range), 51 | } 52 | impl> JournalRecord { 53 | /// 読み書き時のサイズ(バイト数)を返す. 54 | pub(crate) fn external_size(&self) -> usize { 55 | let record_size = match *self { 56 | JournalRecord::EndOfRecords | JournalRecord::GoToFront => 0, 57 | JournalRecord::Put(..) => LumpId::SIZE + LENGTH_SIZE + PORTION_SIZE, 58 | JournalRecord::Embed(_, ref data) => LumpId::SIZE + LENGTH_SIZE + data.as_ref().len(), 59 | JournalRecord::Delete(..) => LumpId::SIZE, 60 | JournalRecord::DeleteRange(..) => LumpId::SIZE * 2, 61 | }; 62 | CHECKSUM_SIZE + TAG_SIZE + record_size 63 | } 64 | 65 | /// `writer`にレコードを書き込む. 66 | pub(crate) fn write_to(&self, mut writer: W) -> Result<()> { 67 | track_io!(writer.write_u32::(self.checksum()))?; 68 | match *self { 69 | JournalRecord::EndOfRecords => { 70 | track_io!(writer.write_u8(TAG_END_OF_RECORDS))?; 71 | } 72 | JournalRecord::GoToFront => { 73 | track_io!(writer.write_u8(TAG_GO_TO_FRONT))?; 74 | } 75 | JournalRecord::Put(ref lump_id, portion) => { 76 | track_io!(writer.write_u8(TAG_PUT))?; 77 | track_io!(writer.write_u128::(lump_id.as_u128()))?; 78 | track_io!(writer.write_u16::(portion.len))?; 79 | track_io!(writer.write_uint::(portion.start.as_u64(), PORTION_SIZE))?; 80 | } 81 | JournalRecord::Embed(ref lump_id, ref data) => { 82 | debug_assert!(data.as_ref().len() <= 0xFFFF); 83 | track_io!(writer.write_u8(TAG_EMBED))?; 84 | track_io!(writer.write_u128::(lump_id.as_u128()))?; 85 | track_io!(writer.write_u16::(data.as_ref().len() as u16))?; 86 | track_io!(writer.write_all(data.as_ref()))?; 87 | } 88 | JournalRecord::Delete(ref lump_id) => { 89 | track_io!(writer.write_u8(TAG_DELETE))?; 90 | track_io!(writer.write_u128::(lump_id.as_u128()))?; 91 | } 92 | JournalRecord::DeleteRange(ref range) => { 93 | track_io!(writer.write_u8(TAG_DELETE_RANGE))?; 94 | track_io!(writer.write_u128::(range.start.as_u128()))?; 95 | track_io!(writer.write_u128::(range.end.as_u128()))?; 96 | } 97 | } 98 | Ok(()) 99 | } 100 | 101 | fn checksum(&self) -> u32 { 102 | let mut adler32 = RollingAdler32::new(); 103 | match *self { 104 | JournalRecord::EndOfRecords => { 105 | adler32.update(TAG_END_OF_RECORDS); 106 | } 107 | JournalRecord::GoToFront => { 108 | adler32.update(TAG_GO_TO_FRONT); 109 | } 110 | JournalRecord::Put(ref lump_id, portion) => { 111 | adler32.update(TAG_PUT); 112 | adler32.update_buffer(&lump_id_to_u128(lump_id)[..]); 113 | let mut buf = [0; 7]; 114 | BigEndian::write_u16(&mut buf, portion.len); 115 | BigEndian::write_uint(&mut buf[2..], portion.start.as_u64(), PORTION_SIZE); 116 | adler32.update_buffer(&buf); 117 | } 118 | JournalRecord::Embed(ref lump_id, ref data) => { 119 | debug_assert!(data.as_ref().len() <= 0xFFFF); 120 | adler32.update(TAG_EMBED); 121 | adler32.update_buffer(&lump_id_to_u128(lump_id)[..]); 122 | let mut buf = [0; 2]; 123 | BigEndian::write_u16(&mut buf, data.as_ref().len() as u16); 124 | adler32.update_buffer(&buf); 125 | adler32.update_buffer(data.as_ref()); 126 | } 127 | JournalRecord::Delete(ref lump_id) => { 128 | adler32.update(TAG_DELETE); 129 | adler32.update_buffer(&lump_id_to_u128(lump_id)[..]); 130 | } 131 | JournalRecord::DeleteRange(ref range) => { 132 | adler32.update(TAG_DELETE_RANGE); 133 | adler32.update_buffer(&lump_id_to_u128(&range.start)[..]); 134 | adler32.update_buffer(&lump_id_to_u128(&range.end)[..]); 135 | } 136 | } 137 | adler32.hash() 138 | } 139 | } 140 | impl JournalRecord> { 141 | /// `reader`からレコードを読み込む. 142 | pub(crate) fn read_from(mut reader: R) -> Result { 143 | let checksum = track_io!(reader.read_u32::())?; 144 | let tag = track_io!(reader.read_u8())?; 145 | let record = match tag { 146 | TAG_END_OF_RECORDS => JournalRecord::EndOfRecords, 147 | TAG_GO_TO_FRONT => JournalRecord::GoToFront, 148 | TAG_PUT => { 149 | let lump_id = track!(read_lump_id(&mut reader))?; 150 | let data_len = track_io!(reader.read_u16::())?; 151 | let data_offset = track_io!(reader.read_uint::(PORTION_SIZE))?; 152 | let portion = DataPortion { 153 | start: Address::from_u64(data_offset).unwrap(), 154 | len: data_len, 155 | }; 156 | JournalRecord::Put(lump_id, portion) 157 | } 158 | TAG_EMBED => { 159 | let lump_id = track!(read_lump_id(&mut reader))?; 160 | let data_len = track_io!(reader.read_u16::())?; 161 | let mut data = vec![0; data_len as usize]; 162 | track_io!(reader.read_exact(&mut data))?; 163 | JournalRecord::Embed(lump_id, data) 164 | } 165 | TAG_DELETE => { 166 | let lump_id = track!(read_lump_id(&mut reader))?; 167 | JournalRecord::Delete(lump_id) 168 | } 169 | TAG_DELETE_RANGE => { 170 | let start = track!(read_lump_id(&mut reader))?; 171 | let end = track!(read_lump_id(&mut reader))?; 172 | JournalRecord::DeleteRange(Range { start, end }) 173 | } 174 | _ => track_panic!( 175 | ErrorKind::StorageCorrupted, 176 | "Unknown journal record tag: {}", 177 | tag 178 | ), 179 | }; 180 | track_assert_eq!(record.checksum(), checksum, ErrorKind::StorageCorrupted); 181 | Ok(record) 182 | } 183 | } 184 | 185 | fn read_lump_id(reader: &mut R) -> Result { 186 | let id = track_io!(reader.read_u128::())?; 187 | Ok(LumpId::new(id)) 188 | } 189 | 190 | fn lump_id_to_u128(id: &LumpId) -> [u8; LumpId::SIZE] { 191 | let mut bytes = [0; LumpId::SIZE]; 192 | BigEndian::write_u128(&mut bytes, id.as_u128()); 193 | bytes 194 | } 195 | 196 | #[cfg(test)] 197 | mod tests { 198 | use trackable::result::TestResult; 199 | 200 | use super::*; 201 | use crate::lump::LumpId; 202 | use crate::storage::portion::DataPortion; 203 | use crate::storage::Address; 204 | 205 | #[test] 206 | fn read_write_works() -> TestResult { 207 | let records = vec![ 208 | JournalRecord::EndOfRecords, 209 | JournalRecord::GoToFront, 210 | JournalRecord::Put( 211 | lump_id("000"), 212 | DataPortion { 213 | start: Address::from(0), 214 | len: 10, 215 | }, 216 | ), 217 | JournalRecord::Put( 218 | lump_id("000"), 219 | DataPortion { 220 | start: Address::from_u64((1 << 40) - 1).unwrap(), 221 | len: 0xFFFF, 222 | }, 223 | ), 224 | JournalRecord::Embed(lump_id("111"), b"222".to_vec()), 225 | JournalRecord::Embed(lump_id("111"), vec![0; 0xFFFF]), 226 | JournalRecord::Delete(lump_id("333")), 227 | JournalRecord::DeleteRange(Range { 228 | start: lump_id("123"), 229 | end: lump_id("456"), 230 | }), 231 | ]; 232 | for e0 in records { 233 | let mut buf = Vec::new(); 234 | track!(e0.write_to(&mut buf))?; 235 | let e1 = track!(JournalRecord::read_from(&buf[..]))?; 236 | assert_eq!(e1, e0); 237 | } 238 | Ok(()) 239 | } 240 | 241 | #[test] 242 | fn checksum_works() -> TestResult { 243 | let e: JournalRecord> = JournalRecord::Put( 244 | lump_id("000"), 245 | DataPortion { 246 | start: Address::from(0), 247 | len: 10, 248 | }, 249 | ); 250 | let mut buf = Vec::new(); 251 | track!(e.write_to(&mut buf))?; 252 | buf[6] += 1; // Tampers a byte 253 | 254 | let result = JournalRecord::read_from(&buf[..]); 255 | assert!(result.is_err()); 256 | Ok(()) 257 | } 258 | 259 | fn lump_id(id: &str) -> LumpId { 260 | id.parse().unwrap() 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/storage/journal/region.rs: -------------------------------------------------------------------------------- 1 | use prometrics::metrics::MetricBuilder; 2 | use std::collections::VecDeque; 3 | use std::io::Write; 4 | use std::ops::Range; 5 | 6 | use super::options::JournalRegionOptions; 7 | use super::record::{JournalEntry, JournalRecord, EMBEDDED_DATA_OFFSET}; 8 | use super::ring_buffer::JournalRingBuffer; 9 | use super::{JournalHeader, JournalHeaderRegion}; 10 | use crate::block::BlockSize; 11 | use crate::lump::LumpId; 12 | use crate::metrics::JournalRegionMetrics; 13 | use crate::nvm::NonVolatileMemory; 14 | use crate::storage::index::LumpIndex; 15 | use crate::storage::portion::{DataPortion, JournalPortion, Portion}; 16 | use crate::storage::Address; 17 | use crate::{ErrorKind, Result}; 18 | 19 | // 一回の空き時間処理で実行するGC回数 20 | const GC_COUNT_IN_SIDE_JOB: usize = 64; 21 | 22 | /// デバイスに操作を記録するためのジャーナル領域. 23 | /// 24 | /// ジャーナル領域はリングバッファ形式で管理されている. 25 | /// 26 | /// # 参考 27 | /// 28 | /// - [ストレージフォーマット(v1.0)][format] 29 | /// - [ストレージのジャーナル領域のGC方法][gc] 30 | /// 31 | /// [format]: https://github.com/frugalos/cannyls/wiki/Storage-Format 32 | /// [gc]: https://github.com/frugalos/cannyls/wiki/Journal-Region-GC 33 | #[derive(Debug)] 34 | pub struct JournalRegion { 35 | header_region: JournalHeaderRegion, 36 | ring_buffer: JournalRingBuffer, 37 | metrics: JournalRegionMetrics, 38 | gc_queue: VecDeque, 39 | sync_countdown: usize, // `0`になったら`sync()`を呼び出す 40 | options: JournalRegionOptions, 41 | gc_after_append: bool, 42 | } 43 | impl JournalRegion 44 | where 45 | N: NonVolatileMemory, 46 | { 47 | pub fn journal_entries(&mut self) -> Result<(u64, u64, u64, Vec)> { 48 | self.ring_buffer.journal_entries() 49 | } 50 | 51 | /// ジャーナル領域の初期化を行う. 52 | /// 53 | /// 具体的には`nmヘッダと最初のエントリ(EndOfEntries)を書き込む 54 | pub fn initialize(mut writer: W, block_size: BlockSize) -> Result<()> { 55 | track!(JournalHeader::new().write_to(&mut writer, block_size))?; 56 | track!(JournalRecord::EndOfRecords::<[_; 0]>.write_to(&mut writer))?; 57 | Ok(()) 58 | } 59 | 60 | /// ジャーナル領域を開く。 61 | /// 62 | /// この関数の中で`index`の再構築も行われる. 63 | pub fn open( 64 | nvm: N, 65 | index: &mut LumpIndex, 66 | metric_builder: &MetricBuilder, 67 | options: JournalRegionOptions, 68 | ) -> Result> 69 | where 70 | N: NonVolatileMemory, 71 | { 72 | track_assert!( 73 | options.block_size.contains(nvm.block_size()), 74 | ErrorKind::InvalidInput; options.block_size, nvm.block_size() 75 | ); 76 | let block_size = options.block_size; 77 | 78 | let (header_nvm, ring_buffer_nvm) = 79 | track!(nvm.split(JournalHeader::region_size(block_size) as u64))?; 80 | 81 | let mut header_region = JournalHeaderRegion::new(header_nvm, block_size); 82 | let header = track!(header_region.read_header())?; 83 | let ring_buffer = 84 | JournalRingBuffer::new(ring_buffer_nvm, header.ring_buffer_head, metric_builder); 85 | 86 | let metrics = JournalRegionMetrics::new(metric_builder, ring_buffer.metrics().clone()); 87 | let mut journal = JournalRegion { 88 | header_region, 89 | ring_buffer, 90 | metrics, 91 | gc_queue: VecDeque::new(), 92 | sync_countdown: options.sync_interval, 93 | options, 94 | gc_after_append: true, 95 | }; 96 | track!(journal.restore(index))?; 97 | Ok(journal) 98 | } 99 | 100 | /// PUT操作をジャーナルに記録する. 101 | pub fn records_put( 102 | &mut self, 103 | index: &mut LumpIndex, 104 | lump_id: &LumpId, 105 | portion: DataPortion, 106 | ) -> Result<()> { 107 | let record = JournalRecord::Put(*lump_id, portion); 108 | track!(self.append_record_with_gc::<[_; 0]>(index, &record))?; 109 | Ok(()) 110 | } 111 | 112 | /// 埋め込みPUT操作をジャーナルに記録する. 113 | pub fn records_embed( 114 | &mut self, 115 | index: &mut LumpIndex, 116 | lump_id: &LumpId, 117 | data: &[u8], 118 | ) -> Result<()> { 119 | let record = JournalRecord::Embed(*lump_id, data); 120 | track!(self.append_record_with_gc(index, &record))?; 121 | Ok(()) 122 | } 123 | 124 | /// DELETE操作をジャーナルに記録する. 125 | pub fn records_delete(&mut self, index: &mut LumpIndex, lump_id: &LumpId) -> Result<()> { 126 | let record = JournalRecord::Delete(*lump_id); 127 | track!(self.append_record_with_gc::<[_; 0]>(index, &record))?; 128 | Ok(()) 129 | } 130 | 131 | // RANGE-DELETE操作をジャーナルに記録する。 132 | pub fn records_delete_range( 133 | &mut self, 134 | index: &mut LumpIndex, 135 | range: Range, 136 | ) -> Result<()> { 137 | let record = JournalRecord::DeleteRange(range); 138 | track!(self.append_record_with_gc::<[_; 0]>(index, &record))?; 139 | Ok(()) 140 | } 141 | 142 | /// ジャーナル領域に埋め込まれたデータを取得する. 143 | pub fn get_embedded_data(&mut self, portion: JournalPortion) -> Result> { 144 | let offset = portion.start.as_u64(); 145 | let mut buf = vec![0; portion.len as usize]; 146 | track!(self.ring_buffer.read_embedded_data(offset, &mut buf))?; 147 | Ok(buf) 148 | } 149 | 150 | /// 補助タスクを一単位実行する. 151 | pub fn run_side_job_once(&mut self, index: &mut LumpIndex) -> Result<()> { 152 | if self.gc_queue.is_empty() { 153 | track!(self.fill_gc_queue())?; 154 | } else if self.sync_countdown != self.options.sync_interval { 155 | track!(self.sync())?; 156 | } else { 157 | for _ in 0..GC_COUNT_IN_SIDE_JOB { 158 | track!(self.gc_once(index))?; 159 | } 160 | track!(self.try_sync())?; 161 | } 162 | Ok(()) 163 | } 164 | 165 | /// ジャーナル領域用のメトリクスを返す. 166 | pub fn metrics(&self) -> &JournalRegionMetrics { 167 | &self.metrics 168 | } 169 | 170 | /// GC処理を一単位実行する. 171 | fn gc_once(&mut self, index: &mut LumpIndex) -> Result<()> { 172 | if self.gc_queue.is_empty() && self.ring_buffer.capacity() < self.ring_buffer.usage() * 2 { 173 | // 空き領域が半分を切った場合には、`run_side_job_once()`以外でもGCを開始する 174 | // ("半分"という閾値に深い意味はない) 175 | track!(self.fill_gc_queue())?; 176 | } 177 | while let Some(entry) = self.gc_queue.pop_front() { 178 | self.metrics.gc_dequeued_records.increment(); 179 | if !self.is_garbage(index, &entry) { 180 | // まだ回収できない場合には、ジャーナル領域の「末尾に」追加する 181 | track!(self.append_record(index, &entry.record))?; 182 | break; 183 | } 184 | } 185 | 186 | Ok(()) 187 | } 188 | 189 | fn between(x: u64, y: u64, z: u64) -> bool { 190 | (x <= y && y <= z) || (z <= x && x <= y) || (y <= z && z <= x) 191 | } 192 | 193 | fn gc_all_entries_in_queue(&mut self, index: &mut LumpIndex) -> Result<()> { 194 | while !self.gc_queue.is_empty() { 195 | track!(self.gc_once(index))?; 196 | } 197 | Ok(()) 198 | } 199 | 200 | pub fn gc_all_entries(&mut self, index: &mut LumpIndex) -> Result<()> { 201 | let current_tail_position = self.ring_buffer.tail(); 202 | 203 | loop { 204 | let before_head = self.ring_buffer.head(); 205 | if self.gc_queue.is_empty() { 206 | track!(self.fill_gc_queue())?; 207 | } 208 | track!(self.gc_all_entries_in_queue(index))?; 209 | if Self::between(before_head, current_tail_position, self.ring_buffer.head()) { 210 | break; 211 | } 212 | } 213 | 214 | // `gc_all_entries_in_queue`は`gc_queue`が空になるまで処理を行うため、 215 | // 上のloopを抜けた後では 216 | // `unreleased_head`と`head`の間のエントリは全て再配置済みである。 217 | // そこで現在の`head`の値をジャーナルエントリ開始位置として永続化し、 218 | // `unreleased_head`も更新する。 219 | let ring_buffer_head = self.ring_buffer.head(); 220 | track!(self.write_journal_header(ring_buffer_head))?; 221 | 222 | Ok(()) 223 | } 224 | 225 | /// `ring_buffer_head`をジャーナルエントリ開始位置として永続化し、 226 | /// `unreleased_head`を`ring_buffer_head`に移動する。 227 | fn write_journal_header(&mut self, ring_buffer_head: u64) -> Result<()> { 228 | let header = JournalHeader { ring_buffer_head }; 229 | track!(self.header_region.write_header(&header))?; 230 | self.ring_buffer.release_bytes_until(ring_buffer_head); 231 | Ok(()) 232 | } 233 | 234 | pub fn set_automatic_gc_mode(&mut self, enable: bool) { 235 | self.gc_after_append = enable; 236 | } 237 | 238 | fn append_record_with_gc( 239 | &mut self, 240 | index: &mut LumpIndex, 241 | record: &JournalRecord, 242 | ) -> Result<()> 243 | where 244 | B: AsRef<[u8]>, 245 | { 246 | track!(self.append_record(index, record))?; 247 | if self.gc_after_append { 248 | track!(self.gc_once(index))?; // レコード追記に合わせてGCを一単位行うことでコストを償却する 249 | } 250 | track!(self.try_sync())?; 251 | Ok(()) 252 | } 253 | 254 | fn append_record(&mut self, index: &mut LumpIndex, record: &JournalRecord) -> Result<()> 255 | where 256 | B: AsRef<[u8]>, 257 | { 258 | let embedded = track!(self.ring_buffer.enqueue(record))?; 259 | if let Some((lump_id, portion)) = embedded { 260 | index.insert(lump_id, Portion::Journal(portion)); 261 | } 262 | Ok(()) 263 | } 264 | 265 | fn try_sync(&mut self) -> Result<()> { 266 | if self.sync_countdown == 0 { 267 | track!(self.sync())?; 268 | } else { 269 | self.sync_countdown -= 1; 270 | } 271 | Ok(()) 272 | } 273 | 274 | /// ジャーナルバッファをディスクへ確実に書き出すために 275 | /// 同期命令を発行する。 276 | /// 277 | /// FIXME: 最適化として、 278 | /// 既に同期済みで必要のない場合は、同期命令を発行しないようにする。 279 | pub fn sync(&mut self) -> Result<()> { 280 | track!(self.ring_buffer.sync())?; 281 | self.sync_countdown = self.options.sync_interval; 282 | self.metrics.syncs.increment(); 283 | Ok(()) 284 | } 285 | 286 | /// エントリが回収可能かどうかを判定する. 287 | fn is_garbage(&self, index: &LumpIndex, entry: &JournalEntry) -> bool { 288 | match entry.record { 289 | JournalRecord::Put(ref lump_id, ref portion) => { 290 | index.get(lump_id) != Some(Portion::Data(*portion)) 291 | } 292 | JournalRecord::Embed(ref lump_id, ref data) => { 293 | let portion = JournalPortion { 294 | start: entry.start + Address::from(EMBEDDED_DATA_OFFSET as u32), 295 | len: data.len() as u16, 296 | }; 297 | index.get(lump_id) != Some(Portion::Journal(portion)) 298 | } 299 | _ => true, 300 | } 301 | } 302 | 303 | /// GC用のキューの内容を補填する. 304 | /// 305 | /// 必要に応じて、ジャーナルヘッダの更新も行う. 306 | fn fill_gc_queue(&mut self) -> Result<()> { 307 | assert!(self.gc_queue.is_empty()); 308 | 309 | // GCキューが空 `gc_queue.is_empty() == true` 310 | // すなわち `unreleased_head` と `head` の間のレコード群は全て再配置済みであるため、 311 | // 現在のhead位置をジャーナルエントリの開始位置として永続化し、 312 | // `unreleased_head`の位置も更新する。 313 | let ring_buffer_head = self.ring_buffer.head(); 314 | track!(self.write_journal_header(ring_buffer_head))?; 315 | 316 | if self.ring_buffer.is_empty() { 317 | return Ok(()); 318 | } 319 | 320 | for result in track!(self.ring_buffer.dequeue_iter())?.take(self.options.gc_queue_size) { 321 | let entry = track!(result)?; 322 | self.gc_queue.push_back(entry); 323 | } 324 | 325 | self.metrics 326 | .gc_enqueued_records 327 | .add_u64(self.gc_queue.len() as u64); 328 | Ok(()) 329 | } 330 | 331 | /// リングバッファおよびインデックスを前回の状態に復元する. 332 | fn restore(&mut self, index: &mut LumpIndex) -> Result<()> { 333 | for result in track!(self.ring_buffer.restore_entries())? { 334 | let JournalEntry { start, record } = track!(result)?; 335 | match record { 336 | JournalRecord::Put(lump_id, portion) => { 337 | index.insert(lump_id, Portion::Data(portion)); 338 | } 339 | JournalRecord::Embed(lump_id, data) => { 340 | let portion = JournalPortion { 341 | start: start + Address::from(EMBEDDED_DATA_OFFSET as u32), 342 | len: data.len() as u16, 343 | }; 344 | index.insert(lump_id, Portion::Journal(portion)); 345 | } 346 | JournalRecord::Delete(lump_id) => { 347 | index.remove(&lump_id); 348 | } 349 | JournalRecord::DeleteRange(range) => { 350 | for lump_id in index.list_range(range) { 351 | index.remove(&lump_id); 352 | } 353 | } 354 | JournalRecord::EndOfRecords | JournalRecord::GoToFront => unreachable!(), 355 | } 356 | } 357 | Ok(()) 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /src/storage/journal/ring_buffer.rs: -------------------------------------------------------------------------------- 1 | use prometrics::metrics::MetricBuilder; 2 | use std::io::{BufReader, Read, Seek, SeekFrom}; 3 | 4 | use super::record::{EMBEDDED_DATA_OFFSET, END_OF_RECORDS_SIZE}; 5 | use super::{JournalEntry, JournalNvmBuffer, JournalRecord}; 6 | use crate::lump::LumpId; 7 | use crate::metrics::JournalQueueMetrics; 8 | use crate::nvm::NonVolatileMemory; 9 | use crate::storage::portion::JournalPortion; 10 | use crate::storage::Address; 11 | use crate::{ErrorKind, Result}; 12 | 13 | /// ジャーナル領域用のリングバッファ. 14 | #[derive(Debug)] 15 | pub struct JournalRingBuffer { 16 | nvm: JournalNvmBuffer, 17 | 18 | /// 未解放分の含めた場合の、リングバッファの始端位置. 19 | /// 20 | /// `unreleased_head`から`head`の間に位置するレコード群は、 21 | /// `JournalRegion`によってデキューはされているが、 22 | /// まだGCによる再配置は終わっていない可能性があるので、 23 | /// 安全に上書きすることができない. 24 | unreleased_head: u64, 25 | 26 | /// リングバッファの始端位置. 27 | head: u64, 28 | 29 | /// リングバッファの終端位置. 30 | /// 31 | /// ここが次の追記開始位置となる. 32 | /// 33 | /// 不変項: `unreleased_head <= head <= tail` 34 | tail: u64, 35 | 36 | metrics: JournalQueueMetrics, 37 | } 38 | impl JournalRingBuffer { 39 | pub fn head(&self) -> u64 { 40 | self.head 41 | } 42 | pub fn tail(&self) -> u64 { 43 | self.tail 44 | } 45 | 46 | pub fn journal_entries(&mut self) -> Result<(u64, u64, u64, Vec)> { 47 | track_io!(self.nvm.seek(SeekFrom::Start(self.head)))?; 48 | let result: Result> = 49 | ReadEntries::new(&mut self.nvm, self.head).collect(); 50 | result.map(|r| (self.unreleased_head, self.head, self.tail, r)) 51 | } 52 | 53 | /// `JournalRingBuffer`インスタンスを生成する. 54 | pub fn new(nvm: N, head: u64, metric_builder: &MetricBuilder) -> Self { 55 | let metrics = JournalQueueMetrics::new(metric_builder); 56 | metrics.capacity_bytes.set(nvm.capacity() as f64); 57 | JournalRingBuffer { 58 | nvm: JournalNvmBuffer::new(nvm), 59 | unreleased_head: head, 60 | head, 61 | tail: head, 62 | metrics, 63 | } 64 | } 65 | 66 | /// NVMから以前のエントリ群を復元し、それらを操作するためのイテレータを返す. 67 | /// 68 | /// インスタンス生成直後に一度だけ呼ばれることを想定. 69 | pub fn restore_entries(&mut self) -> Result> { 70 | track!(RestoredEntries::new(self)) 71 | } 72 | 73 | /// リングバッファ内に要素が存在するかどうかを判定する. 74 | pub fn is_empty(&self) -> bool { 75 | self.head == self.tail 76 | } 77 | 78 | /// リングバッファの使用量(バイト単位)を返す. 79 | pub fn usage(&self) -> u64 { 80 | if self.unreleased_head <= self.tail { 81 | self.tail - self.unreleased_head 82 | } else { 83 | (self.tail + self.capacity()) - self.unreleased_head 84 | } 85 | } 86 | 87 | /// リングバッファの容量(バイト単位)を返す. 88 | pub fn capacity(&self) -> u64 { 89 | self.nvm.capacity() 90 | } 91 | 92 | /// リングバッファのメトリクスを返す. 93 | pub fn metrics(&self) -> &JournalQueueMetrics { 94 | &self.metrics 95 | } 96 | 97 | /// 指定位置に埋め込まれたlumpデータの読み込みを行う. 98 | /// 99 | /// データの妥当性検証は`cannyls`内では行わない. 100 | pub fn read_embedded_data(&mut self, position: u64, buf: &mut [u8]) -> Result<()> { 101 | track_io!(self.nvm.seek(SeekFrom::Start(position)))?; 102 | track_io!(self.nvm.read_exact(buf))?; 103 | Ok(()) 104 | } 105 | 106 | /// 物理デバイスに同期命令を発行する. 107 | pub fn sync(&mut self) -> Result<()> { 108 | track!(self.nvm.sync()) 109 | } 110 | 111 | /// レコードをジャーナルの末尾に追記する. 112 | /// 113 | /// レコードが`JournalRecord::Embed`だった場合には、データを埋め込んだ位置を結果として返す. 114 | pub fn enqueue>( 115 | &mut self, 116 | record: &JournalRecord, 117 | ) -> Result> { 118 | // 1. 十分な空き領域が存在するかをチェック 119 | track!(self.check_free_space(record))?; 120 | 121 | // 2. リングバッファの終端チェック 122 | if self.will_overflow(record) { 123 | track_io!(self.nvm.seek(SeekFrom::Start(self.tail)))?; 124 | track!(JournalRecord::GoToFront::<[_; 0]>.write_to(&mut self.nvm))?; 125 | 126 | // 先頭に戻って再試行 127 | self.metrics 128 | .consumed_bytes_at_running 129 | .add_u64(self.nvm.capacity() - self.tail); 130 | self.tail = 0; 131 | debug_assert!(!self.will_overflow(record)); 132 | return self.enqueue(record); 133 | } 134 | 135 | // 3. レコードを書き込む 136 | let prev_tail = self.tail; 137 | track_io!(self.nvm.seek(SeekFrom::Start(self.tail)))?; 138 | track!(record.write_to(&mut self.nvm))?; 139 | self.metrics.enqueued_records_at_running.increment(record); 140 | 141 | // 4. 終端を示すレコードも書き込む 142 | self.tail = self.nvm.position(); // 次回の追記開始位置を保存 (`EndOfRecords`の直前) 143 | self.metrics 144 | .consumed_bytes_at_running 145 | .add_u64(self.tail - prev_tail); 146 | track!(JournalRecord::EndOfRecords::<[_; 0]>.write_to(&mut self.nvm))?; 147 | 148 | // 5. 埋め込みPUTの場合には、インデックスに位置情報を返す 149 | if let JournalRecord::Embed(ref lump_id, ref data) = *record { 150 | let portion = JournalPortion { 151 | start: Address::from_u64(prev_tail + EMBEDDED_DATA_OFFSET as u64).unwrap(), 152 | len: data.as_ref().len() as u16, 153 | }; 154 | Ok(Some((*lump_id, portion))) 155 | } else { 156 | Ok(None) 157 | } 158 | } 159 | 160 | /// リングバッファの先頭からエントリ群を取り出す. 161 | /// 162 | /// `EndOfRecords`に到達した時点で走査は終了する. 163 | /// 164 | /// `EndOfRecords`および`GoToFront`は、走査対象には含まれない. 165 | pub fn dequeue_iter(&mut self) -> Result> { 166 | track!(DequeuedEntries::new(self)) 167 | } 168 | 169 | pub fn release_bytes_until(&mut self, point: u64) { 170 | let released_bytes = if self.unreleased_head <= point { 171 | point - self.unreleased_head 172 | } else { 173 | (point + self.nvm.capacity()) - self.unreleased_head 174 | }; 175 | self.metrics.released_bytes.add_u64(released_bytes); 176 | 177 | self.unreleased_head = point; 178 | } 179 | 180 | /// `record`を書き込んだら、リングバッファ用の領域を超えてしまうかどうかを判定する. 181 | fn will_overflow>(&self, record: &JournalRecord) -> bool { 182 | let mut next_tail = self.tail + record.external_size() as u64; 183 | 184 | // `EndOfRecords`は常に末尾に書き込まれるので、その分のサイズも考慮する 185 | next_tail += END_OF_RECORDS_SIZE as u64; 186 | 187 | next_tail > self.nvm.capacity() 188 | } 189 | 190 | /// `record`の書き込みを行うことで、リングバッファのTAILがHEADを追い越してしまう危険性がないかを確認する. 191 | fn check_free_space>(&mut self, record: &JournalRecord) -> Result<()> { 192 | // 書き込みの物理的な終端位置を計算 193 | let write_end = self.tail + (record.external_size() + END_OF_RECORDS_SIZE) as u64; 194 | 195 | // 次のブロック境界までのデータは上書きされる 196 | let write_end = self.nvm.block_size().ceil_align(write_end); 197 | 198 | // 安全に書き込み可能な位置の終端 199 | let free_end = if self.tail < self.unreleased_head { 200 | self.unreleased_head 201 | } else { 202 | self.nvm.capacity() + self.unreleased_head 203 | }; 204 | track_assert!( 205 | write_end <= free_end, 206 | ErrorKind::StorageFull, 207 | "journal region is full: unreleased_head={}, head={}, tail={}, write_end={}, free_end={}", 208 | self.unreleased_head, 209 | self.head, 210 | self.tail, 211 | write_end, 212 | free_end 213 | ); 214 | Ok(()) 215 | } 216 | } 217 | 218 | #[derive(Debug)] 219 | pub struct RestoredEntries<'a, N: 'a + NonVolatileMemory> { 220 | entries: ReadEntries<'a, N>, 221 | head: u64, 222 | tail: &'a mut u64, 223 | capacity: u64, 224 | metrics: &'a JournalQueueMetrics, 225 | } 226 | impl<'a, N: 'a + NonVolatileMemory> RestoredEntries<'a, N> { 227 | #[allow(clippy::new_ret_no_self)] 228 | fn new(ring: &'a mut JournalRingBuffer) -> Result { 229 | // 生成直後の呼び出しかどうかを簡易チェック 230 | track_assert_eq!( 231 | ring.unreleased_head, 232 | ring.head, 233 | ErrorKind::InconsistentState 234 | ); 235 | track_assert_eq!(ring.head, ring.tail, ErrorKind::InconsistentState); 236 | 237 | track_io!(ring.nvm.seek(SeekFrom::Start(ring.head)))?; 238 | let capacity = ring.nvm.capacity(); 239 | Ok(RestoredEntries { 240 | entries: ReadEntries::with_capacity(&mut ring.nvm, ring.head, 1024 * 1024), 241 | head: ring.head, 242 | tail: &mut ring.tail, 243 | capacity, 244 | metrics: &ring.metrics, 245 | }) 246 | } 247 | } 248 | impl<'a, N: 'a + NonVolatileMemory> Iterator for RestoredEntries<'a, N> { 249 | type Item = Result; 250 | fn next(&mut self) -> Option { 251 | let next = self.entries.next(); 252 | match next { 253 | Some(Ok(ref entry)) => { 254 | self.metrics 255 | .enqueued_records_at_starting 256 | .increment(&entry.record); 257 | *self.tail = entry.end().as_u64(); 258 | } 259 | None => { 260 | let size = if self.head <= *self.tail { 261 | *self.tail - self.head 262 | } else { 263 | (*self.tail + self.capacity) - self.head 264 | }; 265 | self.metrics.consumed_bytes_at_starting.add_u64(size); 266 | } 267 | _ => {} 268 | } 269 | next 270 | } 271 | } 272 | 273 | #[derive(Debug)] 274 | pub struct DequeuedEntries<'a, N: 'a + NonVolatileMemory> { 275 | entries: ReadEntries<'a, N>, 276 | head: &'a mut u64, 277 | metrics: &'a JournalQueueMetrics, 278 | } 279 | impl<'a, N: 'a + NonVolatileMemory> DequeuedEntries<'a, N> { 280 | #[allow(clippy::new_ret_no_self)] 281 | fn new(ring: &'a mut JournalRingBuffer) -> Result { 282 | track_io!(ring.nvm.seek(SeekFrom::Start(ring.head)))?; 283 | Ok(DequeuedEntries { 284 | entries: ReadEntries::new(&mut ring.nvm, ring.head), 285 | head: &mut ring.head, 286 | metrics: &ring.metrics, 287 | }) 288 | } 289 | } 290 | impl<'a, N: 'a + NonVolatileMemory> Iterator for DequeuedEntries<'a, N> { 291 | type Item = Result; 292 | fn next(&mut self) -> Option { 293 | let next = self.entries.next(); 294 | if let Some(Ok(ref entry)) = next { 295 | self.metrics.dequeued_records.increment(&entry.record); 296 | *self.head = entry.end().as_u64(); 297 | } 298 | next 299 | } 300 | } 301 | 302 | #[derive(Debug)] 303 | struct ReadEntries<'a, N: 'a + NonVolatileMemory> { 304 | reader: BufReader<&'a mut JournalNvmBuffer>, 305 | current: u64, 306 | is_second_lap: bool, 307 | } 308 | impl<'a, N: 'a + NonVolatileMemory> ReadEntries<'a, N> { 309 | fn new(nvm: &'a mut JournalNvmBuffer, head: u64) -> Self { 310 | ReadEntries { 311 | reader: BufReader::new(nvm), 312 | current: head, 313 | is_second_lap: false, 314 | } 315 | } 316 | fn with_capacity(nvm: &'a mut JournalNvmBuffer, head: u64, capacity: usize) -> Self { 317 | ReadEntries { 318 | reader: BufReader::with_capacity(capacity, nvm), 319 | current: head, 320 | is_second_lap: false, 321 | } 322 | } 323 | fn read_record(&mut self) -> Result>>> { 324 | match track!(JournalRecord::read_from(&mut self.reader))? { 325 | JournalRecord::EndOfRecords => Ok(None), 326 | JournalRecord::GoToFront => { 327 | track_assert!(!self.is_second_lap, ErrorKind::StorageCorrupted); 328 | track_io!(self.reader.seek(SeekFrom::Start(0)))?; 329 | self.current = 0; 330 | self.is_second_lap = true; 331 | self.read_record() 332 | } 333 | record => Ok(Some(record)), 334 | } 335 | } 336 | } 337 | impl<'a, N: 'a + NonVolatileMemory> Iterator for ReadEntries<'a, N> { 338 | type Item = Result; 339 | fn next(&mut self) -> Option { 340 | match self.read_record() { 341 | Err(e) => Some(Err(e)), 342 | Ok(None) => None, 343 | Ok(Some(record)) => { 344 | let start = Address::from_u64(self.current).expect("Never fails"); 345 | self.current += record.external_size() as u64; 346 | let entry = JournalEntry { start, record }; 347 | Some(Ok(entry)) 348 | } 349 | } 350 | } 351 | } 352 | 353 | #[cfg(test)] 354 | mod tests { 355 | use prometrics::metrics::MetricBuilder; 356 | use trackable::result::TestResult; 357 | 358 | use super::*; 359 | use crate::nvm::MemoryNvm; 360 | use crate::storage::portion::DataPortion; 361 | use crate::storage::{Address, JournalRecord}; 362 | use crate::ErrorKind; 363 | 364 | #[test] 365 | fn append_and_read_records() -> TestResult { 366 | let nvm = MemoryNvm::new(vec![0; 1024]); 367 | let mut ring = JournalRingBuffer::new(nvm, 0, &MetricBuilder::new()); 368 | 369 | let records = vec![ 370 | record_put("000", 30, 5), 371 | record_put("111", 100, 300), 372 | record_delete("222"), 373 | record_embed("333", b"foo"), 374 | record_delete("444"), 375 | record_delete_range("000", "999"), 376 | ]; 377 | for record in &records { 378 | assert!(ring.enqueue(record).is_ok()); 379 | } 380 | 381 | let mut position = Address::from(0); 382 | for (entry, record) in track!(ring.dequeue_iter())?.zip(records.iter()) { 383 | let entry = track!(entry)?; 384 | assert_eq!(entry.record, *record); 385 | assert_eq!(entry.start, position); 386 | position = position + Address::from(record.external_size() as u32); 387 | } 388 | 389 | assert_eq!(ring.unreleased_head, 0); 390 | assert_eq!(ring.head, position.as_u64()); 391 | assert_eq!(ring.tail, position.as_u64()); 392 | 393 | assert_eq!(track!(ring.dequeue_iter())?.count(), 0); 394 | Ok(()) 395 | } 396 | 397 | #[test] 398 | fn read_embedded_data() -> TestResult { 399 | let nvm = MemoryNvm::new(vec![0; 1024]); 400 | let mut ring = JournalRingBuffer::new(nvm, 0, &MetricBuilder::new()); 401 | 402 | track!(ring.enqueue(&record_put("000", 30, 5)))?; 403 | track!(ring.enqueue(&record_delete("111")))?; 404 | 405 | let (lump_id, portion) = 406 | track!(ring.enqueue(&record_embed("222", b"foo")))?.expect("Some(_)"); 407 | assert_eq!(lump_id, track_any_err!("222".parse())?); 408 | 409 | let mut buf = vec![0; portion.len as usize]; 410 | track!(ring.read_embedded_data(portion.start.as_u64(), &mut buf))?; 411 | assert_eq!(buf, b"foo"); 412 | Ok(()) 413 | } 414 | 415 | #[test] 416 | fn go_round_ring_buffer() -> TestResult { 417 | let nvm = MemoryNvm::new(vec![0; 1024]); 418 | let mut ring = JournalRingBuffer::new(nvm, 512, &MetricBuilder::new()); 419 | assert_eq!(ring.head, 512); 420 | assert_eq!(ring.tail, 512); 421 | 422 | let record = record_delete("000"); 423 | for _ in 0..(512 / record.external_size()) { 424 | track!(ring.enqueue(&record))?; 425 | } 426 | assert_eq!(ring.tail, 1016); 427 | 428 | track!(ring.enqueue(&record))?; 429 | assert_eq!(ring.tail, 21); 430 | Ok(()) 431 | } 432 | 433 | #[test] 434 | fn full() -> TestResult { 435 | let nvm = MemoryNvm::new(vec![0; 1024]); 436 | let mut ring = JournalRingBuffer::new(nvm, 0, &MetricBuilder::new()); 437 | 438 | let record = record_put("000", 1, 2); 439 | while ring.tail <= 1024 - record.external_size() as u64 { 440 | track!(ring.enqueue(&record))?; 441 | } 442 | assert_eq!(ring.tail, 1008); 443 | 444 | assert_eq!( 445 | ring.enqueue(&record).err().map(|e| *e.kind()), 446 | Some(ErrorKind::StorageFull) 447 | ); 448 | assert_eq!(ring.tail, 1008); 449 | 450 | ring.unreleased_head = 511; 451 | ring.head = 511; 452 | assert_eq!( 453 | ring.enqueue(&record).err().map(|e| *e.kind()), 454 | Some(ErrorKind::StorageFull) 455 | ); 456 | 457 | ring.unreleased_head = 512; 458 | ring.head = 512; 459 | assert!(ring.enqueue(&record).is_ok()); 460 | assert_eq!(ring.tail, record.external_size() as u64); 461 | Ok(()) 462 | } 463 | 464 | #[test] 465 | fn too_large_record() { 466 | let nvm = MemoryNvm::new(vec![0; 1024]); 467 | let mut ring = JournalRingBuffer::new(nvm, 0, &MetricBuilder::new()); 468 | 469 | let record = record_embed("000", &[0; 997]); 470 | assert_eq!(record.external_size(), 1020); 471 | assert_eq!( 472 | ring.enqueue(&record).err().map(|e| *e.kind()), 473 | Some(ErrorKind::StorageFull) 474 | ); 475 | 476 | let record = record_embed("000", &[0; 996]); 477 | assert_eq!(record.external_size(), 1019); 478 | assert!(ring.enqueue(&record).is_ok()); 479 | assert_eq!(ring.tail, 1019); 480 | } 481 | 482 | fn record_put(lump_id: &str, start: u32, len: u16) -> JournalRecord> { 483 | JournalRecord::Put( 484 | lump_id.parse().unwrap(), 485 | DataPortion { 486 | start: Address::from(start), 487 | len, 488 | }, 489 | ) 490 | } 491 | 492 | fn lump_id(id: &str) -> LumpId { 493 | id.parse().unwrap() 494 | } 495 | 496 | fn record_embed(id: &str, data: &[u8]) -> JournalRecord> { 497 | JournalRecord::Embed(lump_id(id), data.to_owned()) 498 | } 499 | 500 | fn record_delete(id: &str) -> JournalRecord> { 501 | JournalRecord::Delete(lump_id(id)) 502 | } 503 | 504 | fn record_delete_range(start: &str, end: &str) -> JournalRecord> { 505 | use std::ops::Range; 506 | JournalRecord::DeleteRange(Range { 507 | start: lump_id(start), 508 | end: lump_id(end), 509 | }) 510 | } 511 | } 512 | -------------------------------------------------------------------------------- /src/storage/portion.rs: -------------------------------------------------------------------------------- 1 | //! Data Portion, Journal Portion, and Portion 2 | 3 | use crate::block::BlockSize; 4 | use crate::storage::Address; 5 | 6 | /// データ領域内の部分領域を示すための構造体. 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 8 | pub struct DataPortion { 9 | /// 部分領域の開始位置(ブロック単位) 10 | pub start: Address, 11 | 12 | /// 部分領域の長さ(ブロック単位) 13 | pub len: u16, 14 | } 15 | impl DataPortion { 16 | /// 部分領域の終端位置を返す. 17 | /// **注意**: DataPortionは [start, end) の領域を用いるため、 18 | /// end部には書き込みは行われていない。 19 | pub fn end(&self) -> Address { 20 | self.start + Address::from(u32::from(self.len)) 21 | } 22 | } 23 | 24 | /// ジャーナル領域内の部分領域を示すための構造体. 25 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 26 | pub struct JournalPortion { 27 | /// 部分領域の開始位置(バイト単位) 28 | pub start: Address, 29 | 30 | /// 部分領域の長さ(バイト単位) 31 | pub len: u16, 32 | } 33 | 34 | /// 部分領域. 35 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 36 | pub enum Portion { 37 | /// ジャーナル領域内の部分領域. 38 | Journal(JournalPortion), 39 | 40 | /// データ領域内の部分領域. 41 | Data(DataPortion), 42 | } 43 | impl Portion { 44 | /// 部分領域の長さをバイト単位で返す. 45 | pub fn len(&self, block_size: BlockSize) -> u32 { 46 | match *self { 47 | Portion::Journal(ref p) => u32::from(p.len), 48 | Portion::Data(ref p) => u32::from(p.len) * u32::from(block_size.as_u16()), 49 | } 50 | } 51 | } 52 | 53 | /// `Portion`の内部表現のサイズを64bitにした構造体. 54 | /// 55 | /// `LumpIndex`のような、数百万~数千万オーダーの部分領域を保持する 56 | /// データ構造では、各要素のメモリ使用量を節約することが 57 | /// 重要となるので、そのような目的でこの構造体が提供されている. 58 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 59 | pub struct PortionU64(u64); 60 | impl From for PortionU64 { 61 | fn from(f: Portion) -> Self { 62 | let (kind, offset, len) = match f { 63 | Portion::Journal(p) => (0, p.start.as_u64(), u64::from(p.len)), 64 | Portion::Data(p) => (1, p.start.as_u64(), u64::from(p.len)), 65 | }; 66 | PortionU64(offset | (len << 40) | (kind << 63)) 67 | } 68 | } 69 | impl From for Portion { 70 | fn from(f: PortionU64) -> Self { 71 | let is_journal = (f.0 >> 63) == 0; 72 | let len = (f.0 >> 40) as u16; 73 | let start = Address::from_u64(f.0 & Address::MAX).unwrap(); 74 | if is_journal { 75 | Portion::Journal(JournalPortion { start, len }) 76 | } else { 77 | Portion::Data(DataPortion { start, len }) 78 | } 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use std::mem; 85 | 86 | use super::*; 87 | use crate::storage::Address; 88 | 89 | #[test] 90 | fn it_works() { 91 | // DataPortion 92 | let p0 = Portion::Data(DataPortion { 93 | start: Address::from(10), 94 | len: 30, 95 | }); 96 | let p1 = PortionU64::from(p0); 97 | assert_eq!(mem::size_of_val(&p1), 8); 98 | 99 | let p2 = Portion::from(p1); 100 | assert_eq!(p0, p2); 101 | 102 | // JournalPortion 103 | let p0 = Portion::Journal(JournalPortion { 104 | start: Address::from(10), 105 | len: 30, 106 | }); 107 | let p1 = PortionU64::from(p0); 108 | assert_eq!(mem::size_of_val(&p1), 8); 109 | 110 | let p2 = Portion::from(p1); 111 | assert_eq!(p0, p2); 112 | } 113 | } 114 | --------------------------------------------------------------------------------