cannyls/storage/
mod.rs

1//! Lump用のストレージ.
2//!
3//! このモジュール自体は、具体的なI/O処理(e.g., ファイル処理)とは切り離されており、データ構造の実装に近い.
4//!
5//! 利用の際には、使用する[NonVolatileMemory]実装を指定した上で、[Device]経由で動作させる必要がある.
6//!
7//! # 参考
8//!
9//! - [ストレージフォーマット(v1.0)][format]
10//! - [ストレージのジャーナル領域のGC方法][gc]
11//!
12//! [NonVolatileMemory]: ../nvm/trait.NonVolatileMemory.html
13//! [Device]: ../device/struct.Device.html
14//! [format]: https://github.com/frugalos/cannyls/wiki/Storage-Format
15//! [gc]: https://github.com/frugalos/cannyls/wiki/Journal-Region-GC
16pub use self::address::Address;
17pub use self::builder::StorageBuilder;
18pub use self::header::StorageHeader;
19pub use self::journal::{JournalEntry, JournalRecord, JournalSnapshot};
20
21pub(crate) use self::data_region::DataRegionLumpData; // `lump`モジュール用に公開
22
23use self::data_region::DataRegion;
24use self::index::LumpIndex;
25use self::journal::JournalRegion;
26use self::portion::Portion;
27use block::BlockSize;
28use lump::{LumpData, LumpDataInner, LumpHeader, LumpId};
29use metrics::StorageMetrics;
30use nvm::NonVolatileMemory;
31use std::ops::Range;
32use Result;
33
34mod address;
35mod allocator;
36mod builder;
37mod data_region;
38mod header;
39mod index;
40mod journal;
41mod portion;
42
43/// ストレージの先頭に書き込まれるマジックナンバー.
44///
45/// "**LU**mp **S**torage **F**ormat"の略.
46pub const MAGIC_NUMBER: [u8; 4] = *b"lusf";
47
48/// ストレージフォーマットの現在のメジャーバージョン.
49///
50/// メジャーバージョンが異なるストレージ同士のデータ形式には互換性が無い.
51pub const MAJOR_VERSION: u16 = 1;
52
53/// ストレージフォーマットの現在のマイナーバージョン.
54///
55/// マイナーバージョンには、後方互換性がある.
56pub const MINOR_VERSION: u16 = 1;
57
58/// ジャーナル領域の最大サイズ(バイト単位).
59///
60/// およそ1TB.
61pub const MAX_JOURNAL_REGION_SIZE: u64 = Address::MAX;
62
63/// データ領域の最大サイズ(バイト単位).
64///
65/// およそ512TB.
66pub const MAX_DATA_REGION_SIZE: u64 = Address::MAX * BlockSize::MIN as u64;
67
68/// Lumpを格納するためのストレージ.
69///
70/// 基本的には、`Storage`インスタンスの構築後は[Device]経由で操作することが想定されている.
71///
72/// ストレージのフォーマットに関しては[ストレージフォーマット(v1.0)][format]を参照のこと.
73///
74/// [Device]: ../device/struct.Device.html
75/// [format]: https://github.com/frugalos/cannyls/wiki/Storage-Format
76#[derive(Debug)]
77pub struct Storage<N>
78where
79    N: NonVolatileMemory,
80{
81    header: StorageHeader,
82    journal_region: JournalRegion<N>,
83    data_region: DataRegion<N>,
84    lump_index: LumpIndex,
85    metrics: StorageMetrics,
86}
87impl<N> Storage<N>
88where
89    N: NonVolatileMemory,
90{
91    pub(crate) fn new(
92        header: StorageHeader,
93        journal_region: JournalRegion<N>,
94        data_region: DataRegion<N>,
95        lump_index: LumpIndex,
96        metrics: StorageMetrics,
97    ) -> Self {
98        Storage {
99            header,
100            journal_region,
101            data_region,
102            lump_index,
103            metrics,
104        }
105    }
106
107    /// デフォルト設定で、新規にストレージを生成する.
108    pub fn create(nvm: N) -> Result<Self> {
109        track!(StorageBuilder::new().create(nvm))
110    }
111
112    /// デフォルト設定で、既に存在するストレージをオープンする.
113    pub fn open(nvm: N) -> Result<Self> {
114        track!(StorageBuilder::new().open(nvm))
115    }
116
117    /// ストレージのヘッダ情報を返す.
118    pub fn header(&self) -> &StorageHeader {
119        &self.header
120    }
121
122    /// ストレージのメトリクスを返す.
123    pub fn metrics(&self) -> &StorageMetrics {
124        &self.metrics
125    }
126
127    /// ストレージに保存されている中で、指定された範囲が占有するバイト数を返す.
128    pub fn usage_range(&self, range: Range<LumpId>) -> StorageUsage {
129        self.lump_index.usage_range(range, self.header.block_size)
130    }
131
132    /// 指定されたIDのlumpを取得する.
133    ///
134    /// # Error Handlings
135    ///
136    /// このメソッドがエラーを返した場合には、
137    /// 不整合ないしI/O周りで致命的な問題が発生している可能性があるので、
138    /// 以後はこのインスタンスの使用を中止するのが望ましい
139    /// (更新系操作とは異なり、何度かリトライを試みても問題はない).
140    pub fn get(&mut self, lump_id: &LumpId) -> Result<Option<LumpData>> {
141        match self.lump_index.get(lump_id) {
142            None => Ok(None),
143            Some(portion) => {
144                let data = match portion {
145                    Portion::Journal(portion) => {
146                        self.metrics.get_journal_lumps.increment();
147                        let bytes = track!(self.journal_region.get_embedded_data(portion))?;
148                        track!(LumpData::new_embedded(bytes))?
149                    }
150                    Portion::Data(portion) => {
151                        self.metrics.get_data_lumps.increment();
152                        track!(self.data_region.get(portion).map(LumpData::from))?
153                    }
154                };
155                Ok(Some(data))
156            }
157        }
158    }
159
160    /// 指定されたIDのlumpのヘッダ情報を取得する.
161    pub fn head(&self, lump_id: &LumpId) -> Option<LumpHeader> {
162        self.lump_index.get(lump_id).map(|portion| LumpHeader {
163            approximate_data_size: portion.len(self.header.block_size),
164        })
165    }
166
167    /// 保存されているlumpのID一覧を返す.
168    ///
169    /// 結果は昇順にソートされている.
170    ///
171    /// # 注意
172    ///
173    /// 例えば巨大なHDDを使用している場合には、lumpの数が数百万以上になることもあるため、
174    /// このメソッドは呼び出す際には注意が必要.
175    pub fn list(&self) -> Vec<LumpId> {
176        self.lump_index.list()
177    }
178
179    /// ストレージに保存されている中で、指定された範囲に含まれるLumpIdの一覧を返す.
180    pub fn list_range(&mut self, range: Range<LumpId>) -> Vec<LumpId> {
181        self.lump_index.list_range(range)
182    }
183
184    /// lumpを保存する.
185    ///
186    /// 既に同じIDのlumpが存在する場合にはデータが上書きされる.
187    ///
188    /// 新規追加の場合には`Ok(true)`が、上書きの場合には`Ok(false)`が返される.
189    ///
190    /// # Error Handlings
191    ///
192    /// このメソッドが`ErrorKind::{Full, InvalidInput}`以外のエラーを返した場合には、
193    /// 不整合ないしI/O周りで致命的な問題が発生している可能性があるので、
194    /// 以後はこのインスタンスの使用を中止するのが望ましい.
195    ///
196    /// # 性能上の注意
197    ///
198    /// 引数に渡される`LumpData`が、`LumpData::new`関数経由で生成されている場合には、
199    /// NVMへの書き込み前に、データをブロック境界にアライメントするためのメモリコピーが余分に発生してしまう.
200    /// それを避けたい場合には、`Storage::allocate_lump_data`メソッドを使用して`LumpData`を生成すると良い.
201    pub fn put(&mut self, lump_id: &LumpId, data: &LumpData) -> Result<bool> {
202        let updated = track!(self.delete_if_exists(lump_id, false))?;
203        match data.as_inner() {
204            LumpDataInner::JournalRegion(data) => {
205                track!(self
206                    .journal_region
207                    .records_embed(&mut self.lump_index, lump_id, data))?;
208            }
209            LumpDataInner::DataRegion(data) => {
210                track!(self.put_lump_to_data_region(lump_id, data))?;
211            }
212            LumpDataInner::DataRegionUnaligned(data) => {
213                let mut aligned_data = DataRegionLumpData::new(data.len(), self.header.block_size);
214                aligned_data.as_bytes_mut().copy_from_slice(data);
215                track!(self.put_lump_to_data_region(lump_id, &aligned_data))?;
216            }
217        }
218        self.metrics.put_lumps_at_running.increment();
219        Ok(!updated)
220    }
221
222    /// 指定されたIDのlumpを削除する.
223    ///
224    /// 削除が行われた場合には`Ok(true)`が、存在しないlumpが指定された場合には`Ok(false)`が、返される.
225    ///
226    /// # Error Handlings
227    ///
228    /// このメソッドがエラーを返した場合には、
229    /// 不整合ないしI/O周りで致命的な問題が発生している可能性があるので、
230    /// 以後はこのインスタンスの使用を中止するのが望ましい.
231    pub fn delete(&mut self, lump_id: &LumpId) -> Result<bool> {
232        track!(self.delete_if_exists(lump_id, true))
233    }
234
235    /// LumpIdのrange [start..end) を用いて、これに含まれるLumpIdを全て削除する。
236    ///
237    /// 返り値がOk(vec)の場合、このvecは実際に削除したlump id全体となっている。
238    /// (注意: rangeには、lusf上にないlump idが一般には含まれている)
239    ///
240    /// # Error Handlings
241    ///
242    /// このメソッドがエラーを返した場合には、
243    /// 不整合ないしI/O周りで致命的な問題が発生している可能性があるので、
244    /// 以後はこのインスタンスの使用を中止するのが望ましい.
245    ///
246    /// # 注意
247    ///
248    /// `range`が大量の要素を含む場合には、
249    /// このメソッドは巨大なLumpIdの配列を返しうることに注意されたい。
250    pub fn delete_range(&mut self, range: Range<LumpId>) -> Result<Vec<LumpId>> {
251        let targets = self.lump_index.list_range(range.clone());
252
253        // ジャーナル領域に範囲削除レコードを一つ書き込むため、一度のディスクアクセスが起こる。
254        // 削除レコードを範囲分書き込むわけ *ではない* ため、複数回のディスクアクセスは発生しない。
255        track!(self
256            .journal_region
257            .records_delete_range(&mut self.lump_index, range))?;
258
259        for lump_id in &targets {
260            if let Some(portion) = self.lump_index.remove(lump_id) {
261                self.metrics.delete_lumps.increment();
262
263                if let Portion::Data(portion) = portion {
264                    // DataRegion::deleteはメモリアロケータに対する解放要求をするのみで
265                    // ディスクにアクセスすることはない。
266                    // (管理領域から外すだけで、例えばディスク上の値を0クリアするようなことはない)
267                    self.data_region.delete(portion);
268                }
269            }
270        }
271
272        Ok(targets)
273    }
274
275    /// ストレージのブロック境界にアライメントされたメモリ領域を保持する`LumpData`インスタンスを返す.
276    ///
277    /// `LumpData::new`関数に比べて、このメソッドが返した`LumpData`インスタンスは、
278    /// 事前に適切なアライメントが行われているため、`Storage::put`による保存時に余計なメモリコピーが
279    /// 発生することがなく、より効率的となる.
280    ///
281    /// # 注意
282    ///
283    /// このストレージが返した`LumpData`インスタンスを、別の(ブロックサイズが異なる)ストレージに
284    /// 保存しようとした場合には、エラーが発生する.
285    ///
286    /// # Errors
287    ///
288    /// 指定されたサイズが`MAX_SIZE`を超えている場合は、`ErrorKind::InvalidInput`エラーが返される.
289    pub fn allocate_lump_data(&self, size: usize) -> Result<LumpData> {
290        track!(LumpData::aligned_allocate(size, self.header.block_size))
291    }
292
293    /// `allocate_lump_data`メソッドにデータの初期化を加えたメソッド.
294    ///
295    /// このメソッドの呼び出しは、以下のコードと等価となる:
296    /// ```ignore
297    /// let mut data = track!(self.allocate_lump_data(bytes.len()))?;
298    /// data.as_bytes_mut().copy_from_slice(bytes);
299    /// ```
300    ///
301    /// 詳細な挙動に関しては`allocate_lump_data`のドキュメントを参照のこと.
302    pub fn allocate_lump_data_with_bytes(&self, bytes: &[u8]) -> Result<LumpData> {
303        let mut data = track!(self.allocate_lump_data(bytes.len()))?;
304        data.as_bytes_mut().copy_from_slice(bytes);
305        Ok(data)
306    }
307
308    /// 補助的な処理を一単位実行する.
309    ///
310    /// このメソッドを呼ばなくても動作上は問題はないが、
311    /// リソースが空いているタイミングで実行することによって、
312    /// 全体的な性能を改善できる可能性がある.
313    pub fn run_side_job_once(&mut self) -> Result<()> {
314        track!(self.journal_region.run_side_job_once(&mut self.lump_index))?;
315        Ok(())
316    }
317
318    /// メモリにバッファされているジャーナルをディスクに書き出す。
319    /// 副作用として、バッファはクリアされる。
320    pub fn journal_sync(&mut self) -> Result<()> {
321        self.journal_region.sync()
322    }
323
324    /// ジャーナル領域に対するGCを実行する。
325    ///
326    /// ここで実行するGCは、ジャーナル領域のHEADからTAILの間の値を全て検査し、
327    /// `journal_gc` を呼び出した段階で破棄できる全エントリを削除する。
328    ///
329    /// 通常のstorageの使用では、各エントリをジャーナルに追加する際に、
330    /// 小規模のGCが走る(正確には `JournalRegion::gc_once`)ので、
331    /// このGCを手動で呼び出す必要はない。
332    pub fn journal_gc(&mut self) -> Result<()> {
333        self.journal_region.gc_all_entries(&mut self.lump_index)
334    }
335
336    /// ジャーナル領域のスナップショットを取得する。
337    pub fn journal_snapshot(&mut self) -> Result<JournalSnapshot> {
338        let (unreleased_head, head, tail, entries) = track!(self.journal_region.journal_entries())?;
339        Ok(JournalSnapshot {
340            unreleased_head,
341            head,
342            tail,
343            entries,
344        })
345    }
346
347    /// ジャーナル領域に対する自動小規模GCの有無を切り替えることができる(ユニットテスト用メソッド)。
348    ///
349    /// デフォルトの設定では、ジャーナル領域への変更操作が行われた際に、
350    /// 1-stepのGC(`JournalRegion::gc_once`)が実行されるが、
351    /// `set_automatic_gc_mode(false)`を呼び出すことで
352    /// この小規模のGCを実行しないようにできる。
353    #[allow(dead_code)]
354    pub(crate) fn set_automatic_gc_mode(&mut self, enable: bool) {
355        self.journal_region.set_automatic_gc_mode(enable);
356    }
357
358    fn put_lump_to_data_region(
359        &mut self,
360        lump_id: &LumpId,
361        data: &DataRegionLumpData,
362    ) -> Result<()> {
363        let portion = track!(self.data_region.put(data))?;
364        track!(self
365            .journal_region
366            .records_put(&mut self.lump_index, lump_id, portion)
367            .map_err(|e| {
368                self.data_region.delete(portion);
369                e
370            }))?;
371        self.lump_index.insert(*lump_id, Portion::Data(portion));
372        Ok(())
373    }
374
375    fn delete_if_exists(&mut self, lump_id: &LumpId, do_record: bool) -> Result<bool> {
376        if let Some(portion) = self.lump_index.remove(lump_id) {
377            self.metrics.delete_lumps.increment();
378            if do_record {
379                track!(self
380                    .journal_region
381                    .records_delete(&mut self.lump_index, lump_id,))?;
382            }
383            if let Portion::Data(portion) = portion {
384                self.data_region.delete(portion);
385            }
386            Ok(true)
387        } else {
388            Ok(false)
389        }
390    }
391}
392
393/// ストレージ使用量。
394#[derive(Debug, Clone)]
395pub enum StorageUsage {
396    /// 取得に失敗したなど不明であることを表す。
397    Unknown,
398    /// 近似値。
399    Approximate(u64),
400}
401impl StorageUsage {
402    /// 近似値として `StorageUsage` を生成する。
403    pub fn approximate(usage: u64) -> Self {
404        StorageUsage::Approximate(usage)
405    }
406
407    /// 使用量不明として `StorageUsage` を生成する。
408    pub fn unknown() -> Self {
409        StorageUsage::Unknown
410    }
411
412    /// バイト数として近似値を返す。
413    pub fn bytecount(&self) -> Option<u64> {
414        match *self {
415            StorageUsage::Unknown => None,
416            StorageUsage::Approximate(bytes) => Some(bytes),
417        }
418    }
419}
420impl Default for StorageUsage {
421    fn default() -> Self {
422        StorageUsage::Unknown
423    }
424}
425
426#[cfg(test)]
427mod tests {
428    use std::fs::OpenOptions;
429    use std::mem;
430    use tempdir::TempDir;
431    use trackable::result::TestResult;
432
433    use super::*;
434    use block::BlockSize;
435    use lump::{LumpData, LumpId};
436    use nvm::{FileNvm, SharedMemoryNvm};
437    use ErrorKind;
438
439    #[test]
440    fn it_works() -> TestResult {
441        let dir = track_io!(TempDir::new("cannyls_test"))?;
442
443        // create
444        let nvm = track!(FileNvm::create(
445            dir.path().join("test.lusf"),
446            BlockSize::min().ceil_align(1024 * 1024)
447        ))?;
448        let mut storage = track!(Storage::create(nvm))?;
449
450        assert!(storage.get(&id("000"))?.is_none());
451        assert!(storage.put(&id("000"), &data("hello"))?);
452        assert!(!storage.put(&id("000"), &data("hello"))?);
453        assert_eq!(storage.get(&id("000"))?, Some(data("hello")));
454        assert_eq!(
455            storage.head(&id("000")).map(|h| h.approximate_data_size),
456            Some(5)
457        );
458        assert!(storage.delete(&id("000"))?);
459        assert!(!storage.delete(&id("000"))?);
460        assert!(storage.get(&id("000"))?.is_none());
461        assert!(storage.head(&id("000")).is_none());
462
463        assert!(storage.put(&id("000"), &data("hello"))?);
464        assert!(storage.put(&id("111"), &data("world"))?);
465        for _ in 0..10 {
466            track!(storage.run_side_job_once())?;
467            assert!(storage.put(&id("222"), &data("quux"))?);
468            assert!(storage.delete(&id("222"))?);
469        }
470        mem::drop(storage);
471
472        // open
473        let nvm = track!(FileNvm::open(dir.path().join("test.lusf")))?;
474        let storage = track!(Storage::open(nvm))?;
475        assert_eq!(storage.list(), vec![id("000"), id("111")]);
476        Ok(())
477    }
478
479    #[test]
480    fn full() -> TestResult {
481        let dir = track_io!(TempDir::new("cannyls_test"))?;
482
483        let nvm = track!(FileNvm::create(
484            dir.path().join("test.lusf"),
485            BlockSize::min().ceil_align(1024 * 1024)
486        ))?;
487        let mut storage = track!(Storage::create(nvm))?;
488
489        assert_eq!(
490            track!(storage.put(&id("000"), &zeroed_data(512 * 1024)))?,
491            true
492        );
493        assert_eq!(
494            storage.put(&id("000"), &zeroed_data(512 * 1024)).ok(),
495            Some(false)
496        );
497        assert_eq!(
498            storage
499                .put(&id("111"), &zeroed_data(512 * 1024))
500                .err()
501                .map(|e| *e.kind()),
502            Some(ErrorKind::StorageFull)
503        );
504
505        assert_eq!(storage.delete(&id("000")).ok(), Some(true));
506        assert_eq!(
507            storage.put(&id("111"), &zeroed_data(512 * 1024)).ok(),
508            Some(true)
509        );
510        Ok(())
511    }
512
513    #[test]
514    fn max_size_lump() -> TestResult {
515        let dir = track_io!(TempDir::new("cannyls_test"))?;
516
517        let nvm = track!(FileNvm::create(
518            dir.path().join("test.lusf"),
519            BlockSize::min().ceil_align(100 * 1024 * 1024)
520        ))?;
521        let mut storage = track!(Storage::create(nvm))?;
522
523        let data = zeroed_data(LumpData::MAX_SIZE);
524        assert_eq!(track!(storage.put(&id("000"), &data))?, true);
525        assert_eq!(track!(storage.get(&id("000")))?, Some(data));
526        Ok(())
527    }
528
529    fn id(id: &str) -> LumpId {
530        id.parse().unwrap()
531    }
532
533    fn data(data: &str) -> LumpData {
534        LumpData::new_embedded(Vec::from(data)).unwrap()
535    }
536
537    fn zeroed_data(size: usize) -> LumpData {
538        let mut data = LumpData::aligned_allocate(size, BlockSize::min()).unwrap();
539        for v in data.as_bytes_mut() {
540            *v = 0;
541        }
542        data
543    }
544
545    #[test]
546    fn open_older_compatible_version_works() -> TestResult {
547        let dir = track_io!(TempDir::new("cannyls_test"))?;
548        let path = dir.path().join("test.lusf");
549
550        // create
551        let mut header = {
552            let nvm = track!(FileNvm::create(&path, 1024 * 1024))?;
553            let storage = track!(Storage::create(nvm))?;
554            let header = storage.header().clone();
555            assert_eq!(header.major_version, MAJOR_VERSION);
556            assert_eq!(header.minor_version, MINOR_VERSION);
557            header
558        };
559
560        // マイナーバージョンを減らして、ヘッダを上書きする
561        {
562            header.minor_version = header
563                .minor_version
564                .checked_sub(1)
565                .expect("このテストは`MINOR_VERSION >= 1`であることを前提としている");
566            let file = track_any_err!(OpenOptions::new().write(true).open(&path))?;
567            track!(header.write_to(file))?;
568        }
569
570        // open: マイナーバージョンが最新のものに調整されている
571        {
572            let nvm = track!(FileNvm::open(&path))?;
573            let storage = track!(Storage::open(nvm))?;
574            let header = storage.header().clone();
575            assert_eq!(header.major_version, MAJOR_VERSION);
576            assert_eq!(header.minor_version, MINOR_VERSION);
577        }
578
579        // ファイル上のヘッダも更新されている
580        {
581            let file = track_any_err!(OpenOptions::new().read(true).open(&path))?;
582            let header = track!(StorageHeader::read_from(file))?;
583            assert_eq!(header.major_version, MAJOR_VERSION);
584            assert_eq!(header.minor_version, MINOR_VERSION);
585        }
586        Ok(())
587    }
588
589    #[test]
590    fn block_size_check_when_create() -> TestResult {
591        // [OK] ストレージとNVMのブロックサイズが等しい
592        let nvm_block_size = track!(BlockSize::new(1024))?;
593        let storage_block_size = track!(BlockSize::new(1024))?;
594
595        let storage = track!(StorageBuilder::new()
596            .block_size(storage_block_size)
597            .create(memory_nvm(nvm_block_size)))?;
598        assert_eq!(storage.header().block_size, storage_block_size);
599
600        // [OK] ストレージがNVMのブロックサイズを包含する
601        let nvm_block_size = track!(BlockSize::new(512))?;
602        let storage_block_size = track!(BlockSize::new(1024))?;
603
604        let storage = track!(StorageBuilder::new()
605            .block_size(storage_block_size)
606            .create(memory_nvm(nvm_block_size)))?;
607        assert_eq!(storage.header().block_size, storage_block_size);
608
609        // [NG] NVMのブロックサイズが、ストレージのブロックサイズよりも大きい
610        let nvm_block_size = track!(BlockSize::new(1024))?;
611        let storage_block_size = track!(BlockSize::new(512))?;
612
613        assert!(StorageBuilder::new()
614            .block_size(storage_block_size)
615            .create(memory_nvm(nvm_block_size))
616            .is_err());
617
618        // [NG] ストレージのブロック境界が、NVMのブロック境界に揃っていない
619        let nvm_block_size = track!(BlockSize::new(1024))?;
620        let storage_block_size = track!(BlockSize::new(1536))?;
621
622        assert!(StorageBuilder::new()
623            .block_size(storage_block_size)
624            .create(memory_nvm(nvm_block_size))
625            .is_err());
626
627        Ok(())
628    }
629
630    #[test]
631    fn block_size_check_when_open() -> TestResult {
632        // 事前準備: ストレージとNVMのブロックサイズを等しくして、ストレージの初期化(生成)を実施
633        let initial_nvm_block_size = track!(BlockSize::new(1536))?;
634        let storage_block_size = track!(BlockSize::new(1536))?;
635        let mut nvm = memory_nvm(initial_nvm_block_size);
636        assert!(StorageBuilder::new()
637            .block_size(storage_block_size)
638            .create(nvm.clone())
639            .is_ok());
640
641        // [OK]: NVMとストレージのブロックサイズが等しい
642        let storage = track!(Storage::open(nvm.clone()))?;
643        assert_eq!(storage.header().block_size, storage_block_size);
644
645        // [OK] ストレージのブロック境界がNVMのブロック境界に揃っている
646        nvm.set_block_size(track!(BlockSize::new(512))?);
647        let storage = track!(Storage::open(nvm.clone()))?;
648        assert_eq!(storage.header().block_size, storage_block_size);
649
650        // [NG] NVMのブロックサイズが、ストレージのブロックサイズよりも大きい
651        nvm.set_block_size(track!(BlockSize::new(2048))?);
652        assert!(Storage::open(nvm.clone()).is_err());
653
654        // [NG] ストレージのブロック境界が、NVMのブロック境界に揃っていない
655        nvm.set_block_size(track!(BlockSize::new(1024))?);
656        assert!(Storage::open(nvm).is_err());
657
658        Ok(())
659    }
660
661    fn memory_nvm(block_size: BlockSize) -> SharedMemoryNvm {
662        SharedMemoryNvm::with_block_size(vec![0; 1024 * 1024], block_size)
663    }
664
665    fn is_put_with(entry: &JournalEntry, id: &LumpId) -> bool {
666        if let JournalRecord::Put(id_, _) = entry.record {
667            id_ == *id
668        } else {
669            false
670        }
671    }
672
673    fn is_delete_with(entry: &JournalEntry, id: &LumpId) -> bool {
674        if let JournalRecord::Delete(id_) = entry.record {
675            id_ == *id
676        } else {
677            false
678        }
679    }
680
681    #[test]
682    fn full_gc_works() -> TestResult {
683        let dir = track_io!(TempDir::new("cannyls_test"))?;
684
685        let nvm = track!(FileNvm::create(
686            dir.path().join("test.lusf"),
687            BlockSize::min().ceil_align(1024 * 1024)
688        ))?;
689        let mut storage = track!(Storage::create(nvm))?;
690
691        // ストレージへの操作で、小規模GCが自動で発生しないようにする
692        storage.set_automatic_gc_mode(false);
693
694        assert!(storage.put(&id("000"), &zeroed_data(42))?);
695        assert!(storage.put(&id("010"), &zeroed_data(42))?);
696
697        let entries = storage.journal_snapshot().unwrap().entries;
698
699        assert_eq!(entries.len(), 2);
700        assert!(is_put_with(entries.get(0).unwrap(), &id("000")));
701        assert!(is_put_with(entries.get(1).unwrap(), &id("010")));
702
703        storage.journal_gc().unwrap();
704
705        let new_entries = storage.journal_snapshot().unwrap().entries;
706
707        for (e1, e2) in entries.iter().zip(new_entries.iter()) {
708            assert_eq!(e1.record, e2.record);
709            // 注意
710            // GCによりジャーナル領域内でのエントリ移動が生じているため、次は成立しない
711            // assert_eq!(e1.start, e2.start);
712        }
713
714        assert!(storage.delete(&id("000"))?);
715        assert!(storage.delete(&id("010"))?);
716
717        let entries = storage.journal_snapshot().unwrap().entries;
718
719        assert_eq!(entries.len(), 4);
720
721        assert!(is_put_with(entries.get(0).unwrap(), &id("000")));
722        assert!(is_put_with(entries.get(1).unwrap(), &id("010")));
723        assert!(is_delete_with(entries.get(2).unwrap(), &id("000")));
724        assert!(is_delete_with(entries.get(3).unwrap(), &id("010")));
725
726        storage.journal_gc().unwrap();
727
728        let entries = storage.journal_snapshot().unwrap().entries;
729
730        assert_eq!(entries.len(), 0);
731
732        Ok(())
733    }
734
735    #[test]
736    fn journal_overflow_example() -> TestResult {
737        let dir = track_io!(TempDir::new("cannyls_test"))?;
738
739        let nvm = track!(FileNvm::create(
740            dir.path().join("test.lusf"),
741            BlockSize::min().ceil_align(1024 * 400)
742        ))?;
743        let mut storage = track!(StorageBuilder::new().journal_region_ratio(0.01).create(nvm))?;
744        storage.set_automatic_gc_mode(false);
745
746        {
747            let header = storage.header();
748            assert_eq!(header.journal_region_size, 4096);
749        }
750
751        for i in 0..60 {
752            assert!(storage.put(&id(&i.to_string()), &zeroed_data(42))?);
753        }
754        for i in 0..20 {
755            assert!(storage.delete(&id(&i.to_string()))?);
756        }
757        {
758            let snapshot = track!(storage.journal_snapshot())?;
759            assert_eq!(snapshot.unreleased_head, 0);
760            assert_eq!(snapshot.head, 0);
761            assert_eq!(snapshot.tail, 2100);
762        }
763
764        track!(storage.journal_gc())?;
765        {
766            let snapshot = track!(storage.journal_snapshot())?;
767            assert_eq!(snapshot.unreleased_head, 2100);
768            assert_eq!(snapshot.head, 2100);
769            assert_eq!(snapshot.tail, 3220);
770        }
771
772        track!(storage.journal_gc())?;
773        {
774            let snapshot = track!(storage.journal_snapshot())?;
775            assert_eq!(snapshot.unreleased_head, 3220);
776            assert_eq!(snapshot.head, 3220);
777            assert_eq!(snapshot.tail, 784);
778        }
779
780        Ok(())
781    }
782
783    #[test]
784    /*
785     * cannyls 0.9.2以前では
786     * PR23 https://github.com/frugalos/cannyls/pull/23
787     * が指摘する問題によりpanicしていた。
788     * その問題が発生しないことを確認するためのテスト。
789     * (発生していた問題というのは、
790     * ジャーナルヘッドに永続化されている`head_position`の値と
791     * これに対応するメモリ上のフィールド`unreleased_head`の値にズレが生じることに起因する。
792     * 詳細についてはPR23を参考にされたい。)
793     */
794    fn confirm_that_the_problem_of_pr23_is_resolved() -> TestResult {
795        let dir = track_io!(TempDir::new("cannyls_test"))?;
796
797        let nvm = track!(FileNvm::create(
798            dir.path().join("test.lusf"),
799            BlockSize::min().ceil_align(1024 * 100 * 4)
800        ))?;
801        let mut storage = track!(StorageBuilder::new().journal_region_ratio(0.01).create(nvm))?;
802        assert_eq!(storage.header().journal_region_size, 4096);
803        // putやdeleteなどに伴う自動GCをoffにする(コードと説明の簡単さのためでonのままでも再現できる)。
804        storage.set_automatic_gc_mode(false);
805
806        let test_lump_id = id("55");
807
808        /*
809         * 下のjournalの状態 (A)
810         * unreleased_head == 33, head == 33, tail == 66
811         * を目指す準備。
812         */
813        let vec: Vec<u8> = vec![42; 10];
814        let lump_data = track!(LumpData::new_embedded(vec))?;
815        track!(storage.put(&test_lump_id, &lump_data))?;
816        track!(storage.run_side_job_once())?; // GCキューを充填。
817        track!(storage.run_side_job_once())?; // syncを行う(今回は意味がない)。
818        track!(storage.run_side_job_once())?; // GCを行う。
819        track!(storage.run_side_job_once())?; // GCキューを充填する段階で、unreleased headを永続化する。
820        {
821            let snapshot = storage.journal_snapshot().unwrap();
822            assert_eq!(snapshot.unreleased_head, 33);
823            assert_eq!(snapshot.head, 66);
824            assert_eq!(snapshot.tail, 66);
825        }
826
827        // (A)が永続化されていることを確認する。
828        std::mem::drop(storage);
829        let nvm = track!(FileNvm::open(dir.path().join("test.lusf")))?;
830        let mut storage = track!(Storage::open(nvm))?;
831        storage.set_automatic_gc_mode(false);
832        {
833            // (A)
834            // ここで重要なのは、unreleased_headが0でない位置に移動していることだけ。
835            let snapshot = storage.journal_snapshot().unwrap();
836            assert_eq!(snapshot.unreleased_head, 33);
837            assert_eq!(snapshot.head, 33); // 再起動後はunreleased_head == headで良い。
838            assert_eq!(snapshot.tail, 66);
839        }
840
841        // journalの状態(B) を目指す。
842        for _ in 0..3 {
843            let vec: Vec<u8> = vec![42; 1000];
844            let lump_data = track!(LumpData::new_embedded(vec))?;
845            track!(storage.put(&test_lump_id, &lump_data))?;
846            track!(storage.delete(&test_lump_id))?;
847        }
848        track!(storage.run_side_job_once())?; // GCキューを充填。
849        track!(storage.run_side_job_once())?; // syncを行う(今回は意味がない)。
850        track!(storage.run_side_job_once())?; // GCを行う。
851        {
852            // (B)
853            // (B)は(C)に入る前準備なので特記するべき状態ではない。
854            let snapshot = storage.journal_snapshot().unwrap();
855            assert_eq!(snapshot.unreleased_head, 3198);
856            assert_eq!(snapshot.head, 3198);
857            assert_eq!(snapshot.tail, 3198);
858        }
859        /*
860         * ジャーナル領域のhead positionはunreleased_headの値と常に等しいため
861         * この段階ではジャーナル領域のhead positionフィールドは位置3198を指している。
862         * これ以降ではPR23と同様に位置33に対して値42を書き込み、不正なtagとして認識させることを試みるが、
863         * cannyls 0.9.2以降では問題にならない。
864         *
865         * 注意:
866         *  cannyls 0.9.2以前では、head positionがこの段階で位置3198を指す保証はない。
867         *  実際として、PR23の段階では古いunreleased_headの値33を指していた。
868         */
869
870        // tailを一周させ、位置33に対して値42を書き込む。
871        let vec: Vec<u8> = vec![42; 2000];
872        let lump_data = track!(LumpData::new_embedded(vec))?;
873        track!(storage.put(&test_lump_id, &lump_data))?;
874        {
875            // (C)
876            // 位置33の周辺を値`42`で上書きした状態。
877            let snapshot = storage.journal_snapshot().unwrap();
878            assert_eq!(snapshot.unreleased_head, 3198);
879            assert_eq!(snapshot.head, 3198);
880            assert_eq!(snapshot.tail, 2023);
881        }
882
883        // storageがcrashして再起動する操作群を模倣する。
884        std::mem::drop(storage);
885        let nvm = track!(FileNvm::open(dir.path().join("test.lusf")))?;
886        let mut storage = track!(Storage::open(nvm))?;
887        {
888            let snapshot = storage.journal_snapshot().unwrap();
889            assert_eq!(snapshot.unreleased_head, 3198);
890            assert_eq!(snapshot.head, 3198);
891            assert_eq!(snapshot.tail, 2023);
892        }
893
894        Ok(())
895    }
896}