use std::pin::pin;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::task::{Context, Waker};
use tessarikv_types::{KeyspaceId, SequenceNumber, ValueKind};
use super::Db;
use super::block_cache::BlockCache;
use super::flush_tests::{PATH, key, until, value};
use super::iterator::DbIterator;
use super::read::{get_in, scan_in};
use super::read_tests::{file_in, from, user_key};
use super::view::View;
use crate::log::fault::FaultStorage;
use crate::memtable::Memtable;
use crate::storage::counted::CountedStorage;
use crate::{DbOptions, KeyspaceOptions, ReadOptions, WriteBatch, WriteOptions};
const WRITES: u32 = 300;
fn options(cache_bytes: u64) -> DbOptions {
let keyspace = KeyspaceOptions::builder()
.memtable_bytes(64 * 1024)
.build()
.unwrap();
DbOptions::builder()
.default_keyspace(keyspace)
.block_cache_bytes(cache_bytes)
.build()
.unwrap()
}
async fn assert_all_readable(db: &Db) {
let options = ReadOptions::default();
for index in 0..WRITES {
let read = db.get(KeyspaceId::DEFAULT, &key(index), &options).await;
assert_eq!(read.unwrap(), Some(value(index)), "key {index}");
}
let page = db
.scan(KeyspaceId::DEFAULT, b"", None, usize::MAX, &options)
.await
.unwrap();
let expected: Vec<_> = (0..WRITES)
.map(|index| (key(index), value(index)))
.collect();
assert_eq!(page.entries, expected);
let mut iterator = db.iter(KeyspaceId::DEFAULT, &options).unwrap();
iterator.seek_to_first().await.unwrap();
let mut walked = 0;
while iterator.valid() {
assert_eq!(iterator.key(), key(walked).as_slice());
assert_eq!(iterator.value(), value(walked).as_slice());
walked += 1;
iterator.next().await.unwrap();
}
assert_eq!(walked, WRITES);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn table_files_answer_with_no_cache_a_tiny_one_and_a_large_one() {
for cache_bytes in [0, 8 * 1024, 64 << 20] {
let storage = FaultStorage::default();
let open = || Db::open_with_storage(PATH, options(cache_bytes), Arc::new(storage.clone()));
let db = open().await.unwrap();
for index in 0..WRITES {
let mut batch = WriteBatch::new();
batch
.put(KeyspaceId::DEFAULT, &key(index), &value(index))
.unwrap();
db.write(&batch, &WriteOptions::default()).await.unwrap();
}
until("two table files", || db.view.load().files(0).count() >= 2).await;
assert_all_readable(&db).await;
db.close().await.unwrap();
let db = open().await.unwrap();
assert!(db.view.load().files(0).count() >= 2);
assert_all_readable(&db).await;
db.close().await.unwrap();
}
}
fn flushed(cache: &Arc<BlockCache>) -> View {
let table = Memtable::new();
for index in 0..40_u8 {
let sequence = SequenceNumber::new(u64::from(index) + 1).unwrap();
table
.insert(sequence, ValueKind::Value, &user_key(index), &[index; 30])
.unwrap();
}
let file = file_in(&CountedStorage::new(false), &table, 1, cache);
View::new(0, Memtable::new())
.with_next_table()
.after_flush(0, vec![file])
.unwrap()
}
#[tokio::test]
async fn a_read_that_asks_not_to_fill_the_cache_leaves_it_as_it_was() {
let cache = Arc::new(BlockCache::new(
1 << 20,
tessarikv_constants::DEFAULT_BLOCK_BYTES,
));
let view = flushed(&cache);
let (default, at) = (KeyspaceId::DEFAULT, SequenceNumber::new(100).unwrap());
let options = ReadOptions {
fill_cache: false,
..ReadOptions::default()
};
let read = get_in(&view, default, &user_key(7), at, false, None).await;
assert_eq!(read.unwrap(), Some(vec![7; 30]));
let page = scan_in(&view, default, from(b"", usize::MAX), at, false).await;
assert_eq!(page.unwrap().entries.len(), 40);
let mut iterator = DbIterator::new(&view, default, at, &options, None).unwrap();
iterator.seek_to_first().await.unwrap();
while iterator.valid() {
iterator.next().await.unwrap();
}
assert_eq!(cache.used_bytes(), 0);
get_in(&view, default, &user_key(7), at, true, None)
.await
.unwrap();
assert!(cache.used_bytes() > 0);
}
fn assert_send<T: Send>(value: T) -> T {
value
}
#[tokio::test]
async fn read_futures_can_move_between_threads() {
let db = Db::open("cache-send", DbOptions::in_memory())
.await
.unwrap();
let options = ReadOptions::default();
assert_send(db.get(KeyspaceId::DEFAULT, b"k", &options))
.await
.unwrap();
assert_send(db.scan(KeyspaceId::DEFAULT, b"", None, 10, &options))
.await
.unwrap();
let mut iterator = db.iter(KeyspaceId::DEFAULT, &options).unwrap();
assert_send(iterator.seek_to_first()).await.unwrap();
assert_send(iterator.next()).await.unwrap();
assert_send(iterator.prev()).await.unwrap();
drop(iterator);
db.close().await.unwrap();
}
#[tokio::test]
async fn a_move_dropped_while_it_waits_for_storage_leaves_the_iterator_not_valid() {
let cache = Arc::new(BlockCache::new(0, tessarikv_constants::DEFAULT_BLOCK_BYTES));
let view = flushed(&cache);
let at = SequenceNumber::new(100).unwrap();
let options = ReadOptions::default();
let mut iterator = DbIterator::new(&view, KeyspaceId::DEFAULT, at, &options, None).unwrap();
let mut abandoned = false;
for _ in 0..100 {
iterator.seek_to_first().await.unwrap();
assert!(iterator.valid());
let mut last = pin!(iterator.seek_to_last());
if last
.as_mut()
.poll(&mut Context::from_waker(Waker::noop()))
.is_pending()
{
abandoned = true;
break;
}
}
assert!(abandoned, "every move finished without waiting");
assert!(!iterator.valid());
assert!(iterator.key().is_empty());
iterator.seek_to_first().await.unwrap();
assert_eq!(iterator.key(), user_key(0).as_slice());
iterator.seek_to_last().await.unwrap();
assert_eq!(iterator.key(), user_key(39).as_slice());
}
async fn reopened_reads(storage: CountedStorage) -> (u64, u64) {
let db = Db::open_with_storage(PATH, options(1024 * 1024), Arc::new(storage.clone()))
.await
.unwrap();
for index in 0..WRITES {
let mut batch = WriteBatch::new();
batch
.put(KeyspaceId::DEFAULT, &key(index), &value(index))
.unwrap();
db.write(&batch, &WriteOptions::default()).await.unwrap();
}
until("tables flushed", || db.view.load().files(0).count() > 2).await;
db.close().await.unwrap();
let db = Db::open_with_storage(PATH, options(0), Arc::new(storage.clone()))
.await
.unwrap();
let reads = &storage.reads;
let inline = reads.inline.load(Ordering::Relaxed);
let blocking = reads.blocking.load(Ordering::Relaxed);
assert_all_readable(&db).await;
let made = (
reads.inline.load(Ordering::Relaxed) - inline,
reads.blocking.load(Ordering::Relaxed) - blocking,
);
db.close().await.unwrap();
made
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_block_that_can_be_read_without_blocking_is_read_inline_and_any_other_is_handed_off() {
let (inline, blocking) = reopened_reads(CountedStorage::new(true)).await;
assert!(inline > 0, "no block read inline");
assert_eq!(
blocking, 0,
"{blocking} blocks handed off with inline reads available"
);
let (inline, blocking) = reopened_reads(CountedStorage::new(false)).await;
assert_eq!(inline, 0);
assert!(blocking > 0, "no block read where blocking is allowed");
}
#[tokio::test]
async fn blocks_that_must_be_read_where_blocking_is_allowed_are_read_on_one_thread_too() {
let (inline, blocking) = reopened_reads(CountedStorage::new(false)).await;
assert_eq!(inline, 0);
assert!(blocking > 0, "no block read where blocking is allowed");
}