1use crate::storage::buffer_pool::ArenaBuffer;
2use crate::storage::sqlite3_ondisk::WAL_FRAME_HEADER_SIZE;
3use crate::sync::Arc;
4use crate::turso_assert;
5use crate::{BufferPool, Result};
6use bitflags::bitflags;
7use cfg_block::cfg_block;
8use rand::{Rng, RngCore};
9use std::cell::RefCell;
10use std::collections::HashMap;
11use std::fmt;
12use std::ptr::NonNull;
13use std::sync::LazyLock;
14use std::{fmt::Debug, pin::Pin};
15use turso_macros::AtomicEnum;
16
17cfg_block! {
18 #[cfg(all(target_os = "linux", clt_turso_feature = "io_uring", not(miri)))] {
19 mod io_uring;
20 #[cfg(clt_turso_feature = "fs")]
21 pub use io_uring::UringIO;
22 }
23
24 #[cfg(all(target_family = "unix", not(miri)))] {
25 mod unix;
26 #[cfg(clt_turso_feature = "fs")]
27 pub use unix::UnixIO;
28 pub use unix::UnixIO as PlatformIO;
29 pub use PlatformIO as SyscallIO;
30 }
31
32 #[cfg(all(target_os = "windows", not(miri)))] {
33 mod windows;
34 #[cfg(clt_turso_feature = "fs")]
35 pub use windows::WindowsIO;
36 pub use windows::WindowsIO as PlatformIO;
37 pub use PlatformIO as SyscallIO;
38 }
39
40 #[cfg(all(target_os = "windows", clt_turso_feature = "experimental_win_iocp", not(miri)))] {
41 mod win_iocp;
42 #[cfg(clt_turso_feature = "fs")]
43 pub use win_iocp::WindowsIOCP;
44 }
45
46 #[cfg(any(not(any(target_family = "unix", target_os = "windows")), miri))] {
47 mod generic;
48 pub use generic::GenericIO as PlatformIO;
49 pub use PlatformIO as SyscallIO;
50 }
51}
52
53mod memory;
54#[cfg(clt_turso_feature = "io_memory_yield")]
55mod memory_yield;
56#[cfg(clt_turso_feature = "fs")]
57mod vfs;
58pub use memory::MemoryIO;
59#[cfg(clt_turso_feature = "io_memory_yield")]
60pub use memory_yield::MemoryYieldIO;
61pub mod clock;
62mod common;
63mod completions;
64pub use clock::Clock;
65pub use completions::*;
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
71pub struct FileId {
72 pub dev: u64,
73 pub ino: u64,
74}
75
76impl FileId {
77 pub fn from_path_hash(path: &str) -> Self {
80 use std::hash::{Hash, Hasher};
81 let mut hasher = std::collections::hash_map::DefaultHasher::new();
82 path.hash(&mut hasher);
83 FileId {
84 dev: 0,
85 ino: hasher.finish(),
86 }
87 }
88}
89
90#[cfg(unix)]
92pub fn get_file_id(path: &str) -> Result<FileId, std::io::Error> {
93 use std::os::unix::fs::MetadataExt;
94 let m = std::fs::metadata(path)?;
95 Ok(FileId {
96 dev: m.dev(),
97 ino: m.ino(),
98 })
99}
100
101#[cfg(windows)]
102pub fn get_file_id(path: &str) -> Result<FileId, std::io::Error> {
103 use std::os::windows::io::AsRawHandle;
104 use windows_sys::Win32::Storage::FileSystem::{
105 GetFileInformationByHandle, BY_HANDLE_FILE_INFORMATION,
106 };
107 let file = std::fs::File::open(path)?;
108 let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
109 let ret = unsafe { GetFileInformationByHandle(file.as_raw_handle() as _, &mut info) };
110 if ret == 0 {
111 return Err(std::io::Error::last_os_error());
112 }
113 Ok(FileId {
114 dev: info.dwVolumeSerialNumber as u64,
115 ino: (info.nFileIndexHigh as u64) << 32 | info.nFileIndexLow as u64,
116 })
117}
118
119#[cfg(not(any(unix, windows)))]
120pub fn get_file_id(path: &str) -> Result<FileId, std::io::Error> {
121 Ok(FileId::from_path_hash(path))
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, AtomicEnum)]
128pub enum FileSyncType {
129 Fsync,
131 FullFsync,
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub enum SharedWalLockKind {
138 LinuxOfd,
139 ProcessScopedFcntl,
140}
141
142pub trait SharedWalMappedRegion: Send + Sync {
143 fn ptr(&self) -> NonNull<u8>;
144 fn len(&self) -> usize;
145
146 fn is_empty(&self) -> bool {
147 self.len() == 0
148 }
149}
150
151pub trait File: Send + Sync {
152 fn lock_file(&self, exclusive: bool) -> Result<()>;
153 fn unlock_file(&self) -> Result<()>;
154 fn pread(&self, pos: u64, c: Completion) -> Result<Completion>;
155 fn pwrite(&self, pos: u64, buffer: Arc<Buffer>, c: Completion) -> Result<Completion>;
156 fn sync(&self, c: Completion, sync_type: FileSyncType) -> Result<Completion>;
158 fn pwritev(&self, pos: u64, buffers: Vec<Arc<Buffer>>, c: Completion) -> Result<Completion> {
159 use crate::sync::atomic::{AtomicUsize, Ordering};
160 if buffers.is_empty() {
161 c.complete(0);
162 return Ok(c);
163 }
164 if buffers.len() == 1 {
165 return self.pwrite(pos, buffers[0].clone(), c);
166 }
167 let mut pos = pos;
169 let outstanding = Arc::new(AtomicUsize::new(buffers.len()));
170 let total_written = Arc::new(AtomicUsize::new(0));
171
172 for buf in buffers {
173 let len = buf.len();
174 let child_c = {
175 let c_main = c.clone();
176 let outstanding = outstanding.clone();
177 let total_written = total_written.clone();
178 Completion::new_write(move |n| {
179 if let Ok(n) = n {
180 total_written.fetch_add(n as usize, Ordering::SeqCst);
182 if outstanding.fetch_sub(1, Ordering::AcqRel) == 1 {
183 c_main.complete(total_written.load(Ordering::Acquire) as i32);
185 }
186 }
187 })
188 };
189 if let Err(e) = self.pwrite(pos, buf.clone(), child_c) {
190 c.abort();
191 return Err(e);
192 }
193 pos += len as u64;
194 }
195 Ok(c)
196 }
197 fn size(&self) -> Result<u64>;
198 fn truncate(&self, len: u64, c: Completion) -> Result<Completion>;
199
200 fn has_hole(&self, _pos: usize, _len: usize) -> Result<bool> {
207 panic!("has_hole is not supported for the given IO implementation")
208 }
209 fn punch_hole(&self, _pos: usize, _len: usize) -> Result<()> {
213 panic!("punch_hole is not supported for the given IO implementation")
214 }
215
216 fn shared_wal_lock_byte(
217 &self,
218 _offset: u64,
219 _exclusive: bool,
220 _kind: SharedWalLockKind,
221 ) -> Result<()> {
222 Err(crate::LimboError::InternalError(
223 "shared WAL coordination byte locking is not supported for this file".into(),
224 ))
225 }
226
227 fn shared_wal_try_lock_byte(
228 &self,
229 _offset: u64,
230 _exclusive: bool,
231 _kind: SharedWalLockKind,
232 ) -> Result<bool> {
233 Err(crate::LimboError::InternalError(
234 "shared WAL coordination byte locking is not supported for this file".into(),
235 ))
236 }
237
238 fn shared_wal_unlock_byte(&self, _offset: u64, _kind: SharedWalLockKind) -> Result<()> {
239 Err(crate::LimboError::InternalError(
240 "shared WAL coordination byte unlocking is not supported for this file".into(),
241 ))
242 }
243
244 fn shared_wal_set_len(&self, _len: u64) -> Result<()> {
245 Err(crate::LimboError::InternalError(
246 "shared WAL coordination resizing is not supported for this file".into(),
247 ))
248 }
249
250 fn shared_wal_map(&self, _offset: u64, _len: usize) -> Result<Box<dyn SharedWalMappedRegion>> {
251 Err(crate::LimboError::InternalError(
252 "shared WAL coordination memory mapping is not supported for this file".into(),
253 ))
254 }
255}
256
257pub struct TempFile {
258 _temp_dir: Option<tempfile::TempDir>,
261 pub(crate) file: Arc<dyn File>,
262}
263
264impl TempFile {
265 pub fn new(io: &Arc<dyn IO>) -> Result<Self> {
266 #[cfg(not(target_family = "wasm"))]
267 {
268 let temp_dir = tempfile::tempdir().map_err(|e| crate::error::io_error(e, "tempdir"))?;
269 let chunk_file_path = temp_dir.as_ref().join("tursodb_temp_file");
270 let chunk_file_path_str = chunk_file_path.to_str().ok_or_else(|| {
271 crate::LimboError::InternalError("temp file path is not valid UTF-8".to_string())
272 })?;
273 let chunk_file = io.open_file(chunk_file_path_str, OpenFlags::Create, false)?;
274 Ok(TempFile {
275 _temp_dir: Some(temp_dir),
276 file: chunk_file.clone(),
277 })
278 }
279 #[cfg(target_family = "wasm")]
282 {
283 use crate::MemoryIO;
284
285 let memory_io = Arc::new(MemoryIO::new());
286 let memory_file = memory_io.open_file("tursodb_temp_file", OpenFlags::Create, false)?;
287 Ok(TempFile {
288 _temp_dir: None,
289 file: memory_file,
290 })
291 }
292 }
293
294 pub fn with_temp_store(io: &Arc<dyn IO>, temp_store: crate::TempStore) -> Result<Self> {
299 #[cfg(not(target_family = "wasm"))]
300 {
301 #[cfg(not(clt_turso_feature = "fs"))]
302 {
303 let _ = (io, temp_store);
304 let memory_io = Arc::new(MemoryIO::new());
305 let memory_file =
306 memory_io.open_file("tursodb_temp_file", OpenFlags::Create, false)?;
307 Ok(TempFile {
308 _temp_dir: None,
309 file: memory_file,
310 })
311 }
312 #[cfg(clt_turso_feature = "fs")]
313 {
314 if matches!(temp_store, crate::TempStore::Memory) {
315 let memory_io = Arc::new(MemoryIO::new());
316 let memory_file =
317 memory_io.open_file("tursodb_temp_file", OpenFlags::Create, false)?;
318 return Ok(TempFile {
319 _temp_dir: None,
320 file: memory_file,
321 });
322 }
323 Self::new(io)
325 }
326 }
327 #[cfg(target_family = "wasm")]
328 {
329 let _ = temp_store;
331 Self::new(io)
332 }
333 }
334}
335
336impl core::ops::Deref for TempFile {
337 type Target = Arc<dyn File>;
338
339 fn deref(&self) -> &Self::Target {
340 &self.file
341 }
342}
343
344#[derive(Debug, Copy, Clone, PartialEq)]
345pub struct OpenFlags(i32);
346
347crate::assert::assert_send_sync!(OpenFlags);
350
351bitflags! {
352 impl OpenFlags: i32 {
353 const None = 0b00000000;
354 const Create = 0b0000001;
355 const ReadOnly = 0b0000010;
356 const NoLock = 0b0000100;
357 }
358}
359
360impl Default for OpenFlags {
361 fn default() -> Self {
362 Self::Create
363 }
364}
365
366pub trait IO: Clock + Send + Sync {
367 fn open_file(&self, path: &str, flags: OpenFlags, direct: bool) -> Result<Arc<dyn File>>;
368
369 fn open_shared_wal_file(&self, path: &str) -> Result<Arc<dyn File>> {
370 self.open_file(path, OpenFlags::Create | OpenFlags::NoLock, false)
371 }
372
373 fn remove_file(&self, path: &str) -> Result<()>;
375
376 fn supports_shared_wal_coordination(&self) -> bool {
378 false
379 }
380
381 fn step(&self) -> Result<()> {
382 Ok(())
383 }
384
385 fn cancel(&self, c: &[Completion]) -> Result<()> {
386 c.iter().for_each(|c| c.abort());
387 Ok(())
388 }
389
390 fn drain_completions(&self, completions: &[Completion]) -> Result<()> {
403 while completions.iter().any(|c| !c.finished()) {
404 self.step()?;
405 }
406 Ok(())
407 }
408
409 fn wait_for_completion(&self, c: Completion) -> Result<()> {
410 while !c.finished() {
411 self.step()?
412 }
413 if let Some(inner) = &c.inner {
414 if let Some(Some(err)) = inner.result.get().copied() {
415 return Err(err.into());
416 }
417 }
418 Ok(())
419 }
420
421 fn generate_random_number(&self) -> i64 {
422 rand::rng().random()
423 }
424
425 fn fill_bytes(&self, dest: &mut [u8]) {
427 rand::rng().fill_bytes(dest);
428 }
429
430 fn get_memory_io(&self) -> Arc<MemoryIO> {
431 Arc::new(MemoryIO::new())
432 }
433
434 fn register_fixed_buffer(&self, _ptr: NonNull<u8>, _len: usize) -> Result<u32> {
435 Err(crate::LimboError::InternalError(
436 "unsupported operation".to_string(),
437 ))
438 }
439
440 fn yield_now(&self) {
443 crate::thread::yield_now();
444 }
445
446 fn sleep(&self, duration: std::time::Duration) {
449 crate::thread::sleep(duration);
450 }
451
452 fn file_id(&self, path: &str) -> Result<FileId> {
456 get_file_id(path).map_err(|e| {
457 crate::LimboError::InternalError(format!(
458 "failed to get file identity for '{path}': {e}"
459 ))
460 })
461 }
462}
463
464pub struct WriteBatch<'a> {
466 file: Arc<dyn File>,
467 ops: Vec<WriteOp<'a>>,
468}
469
470struct WriteOp<'a> {
471 pos: u64,
472 bufs: &'a [Arc<Buffer>],
473}
474
475impl<'a> WriteBatch<'a> {
476 pub fn new(file: Arc<dyn File>) -> Self {
477 Self {
478 file,
479 ops: Vec::new(),
480 }
481 }
482
483 #[inline]
484 pub fn writev(&mut self, pos: u64, bufs: &'a [Arc<Buffer>]) {
485 if !bufs.is_empty() {
486 self.ops.push(WriteOp { pos, bufs });
487 }
488 }
489
490 #[inline]
492 pub fn total_bytes(&self) -> usize {
493 self.ops
494 .iter()
495 .map(|op| op.bufs.iter().map(|b| b.len()).sum::<usize>())
496 .sum()
497 }
498
499 #[inline]
501 pub fn submit(self) -> Result<Vec<Completion>> {
502 let mut completions = Vec::with_capacity(self.ops.len());
503 for WriteOp { pos, bufs } in self.ops {
504 let total_len = bufs.iter().map(|b| b.len()).sum::<usize>() as i32;
505 let c = Completion::new_write(move |res| {
506 let Ok(bytes_written) = res else {
507 return;
508 };
509 turso_assert!(
510 bytes_written == total_len,
511 "pwritev wrote {bytes_written} bytes, expected {total_len}"
512 );
513 });
514 completions.push(self.file.pwritev(pos, bufs.to_vec(), c)?);
515 }
516 Ok(completions)
517 }
518
519 #[inline]
521 pub const fn file(&self) -> &Arc<dyn File> {
522 &self.file
523 }
524}
525
526pub type BufferData = Pin<Box<[u8]>>;
527
528#[derive(Clone)]
529pub enum SharedBufferData {
530 Full(Arc<Box<[u8]>>),
531 View(SharedBufferView),
532}
533
534#[derive(Clone)]
535pub struct SharedBufferView {
536 data: Arc<Box<[u8]>>,
537 start: usize,
538}
539
540impl SharedBufferView {
541 fn new(data: Arc<Box<[u8]>>, start: usize) -> Self {
542 assert!(
543 start <= data.len(),
544 "SharedBufferData::new_view: start ({start}) > data.len() ({})",
545 data.len()
546 );
547 Self { data, start }
548 }
549
550 pub fn len(&self) -> usize {
551 self.data.len() - self.start
552 }
553
554 pub fn is_empty(&self) -> bool {
555 self.len() == 0
556 }
557
558 pub fn as_slice(&self) -> &[u8] {
559 &self.data.as_ref().as_ref()[self.start..]
560 }
561
562 pub fn as_ptr(&self) -> *const u8 {
563 unsafe { self.data.as_ref().as_ptr().add(self.start) }
564 }
565}
566
567impl SharedBufferData {
568 pub fn new(data: Arc<Box<[u8]>>) -> Self {
569 Self::Full(data)
570 }
571
572 pub fn new_view(data: Arc<Box<[u8]>>, start: usize) -> Self {
573 Self::View(SharedBufferView::new(data, start))
574 }
575
576 pub fn len(&self) -> usize {
577 match self {
578 Self::Full(data) => data.len(),
579 Self::View(view) => view.len(),
580 }
581 }
582
583 pub fn is_empty(&self) -> bool {
584 self.len() == 0
585 }
586
587 pub fn as_slice(&self) -> &[u8] {
588 match self {
589 Self::Full(data) => data.as_ref().as_ref(),
590 Self::View(view) => view.as_slice(),
591 }
592 }
593
594 pub fn as_ptr(&self) -> *const u8 {
595 match self {
596 Self::Full(data) => data.as_ref().as_ptr(),
597 Self::View(view) => view.as_ptr(),
598 }
599 }
600}
601
602pub enum Buffer {
603 Heap(BufferData),
604 Shared(SharedBufferData),
605 HeapView {
609 data: BufferData,
610 start: usize,
611 },
612 Pooled(ArenaBuffer),
613}
614
615impl Debug for Buffer {
616 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
617 match self {
618 Self::Pooled(p) => write!(f, "Pooled(len={})", p.logical_len()),
619 Self::Heap(buf) => write!(f, "{buf:?}: {}", buf.len()),
620 Self::Shared(buf) => write!(f, "Shared(len={})", buf.len()),
621 Self::HeapView { data, start } => {
622 write!(
623 f,
624 "HeapView({start}..{}, view_len={})",
625 data.len(),
626 data.len() - start
627 )
628 }
629 }
630 }
631}
632
633impl Drop for Buffer {
634 fn drop(&mut self) {
635 match self {
636 Self::Heap(buf) | Self::HeapView { data: buf, .. } => {
637 let underlying_len = buf.len();
638 TEMP_BUFFER_CACHE.with(|cache| {
639 let mut cache = cache.borrow_mut();
640 let buffer = std::mem::replace(buf, Pin::new(vec![].into_boxed_slice()));
642 cache.return_buffer(buffer, underlying_len);
643 });
644 }
645 Self::Pooled(_) | Self::Shared(_) => {}
646 }
647 }
648}
649
650impl Buffer {
651 pub fn new(data: Vec<u8>) -> Self {
652 tracing::trace!("buffer::new({:?})", data);
653 Self::Heap(Pin::new(data.into_boxed_slice()))
654 }
655
656 pub fn new_shared(data: Arc<Box<[u8]>>) -> Self {
657 Self::Shared(SharedBufferData::new(data))
658 }
659
660 pub fn new_shared_data(data: SharedBufferData) -> Self {
661 Self::Shared(data)
662 }
663
664 pub fn new_with_start(data: Vec<u8>, start: usize) -> Self {
670 assert!(
671 start <= data.len(),
672 "Buffer::new_with_start: start ({start}) > data.len() ({})",
673 data.len()
674 );
675 Self::HeapView {
676 data: Pin::new(data.into_boxed_slice()),
677 start,
678 }
679 }
680
681 pub fn fixed_id(&self) -> Option<u32> {
684 match self {
685 Self::Heap(..) | Self::HeapView { .. } | Self::Shared(..) => None,
686 Self::Pooled(buf) => buf.fixed_id(),
687 }
688 }
689
690 pub fn new_pooled(buf: ArenaBuffer) -> Self {
691 Self::Pooled(buf)
692 }
693
694 pub fn new_temporary(size: usize) -> Self {
695 TEMP_BUFFER_CACHE.with(|cache| {
696 if let Some(buffer) = cache.borrow_mut().get_buffer(size) {
697 Self::Heap(buffer)
698 } else {
699 Self::Heap(Pin::new(vec![0; size].into_boxed_slice()))
700 }
701 })
702 }
703
704 pub fn len(&self) -> usize {
705 match self {
706 Self::Heap(buf) => buf.len(),
707 Self::Shared(buf) => buf.len(),
708 Self::HeapView { data, start } => data.len() - *start,
709 Self::Pooled(buf) => buf.logical_len(),
710 }
711 }
712
713 pub fn is_empty(&self) -> bool {
714 self.len() == 0
715 }
716
717 pub fn as_slice(&self) -> &[u8] {
718 match self {
719 Self::Heap(buf) => {
720 unsafe { std::slice::from_raw_parts(buf.as_ptr(), buf.len()) }
722 }
723 Self::Shared(buf) => buf.as_slice(),
724 Self::HeapView { data, start } => {
725 unsafe {
728 std::slice::from_raw_parts(data.as_ptr().add(*start), data.len() - *start)
729 }
730 }
731 Self::Pooled(buf) => buf,
732 }
733 }
734
735 #[allow(clippy::mut_from_ref)]
736 pub fn as_mut_slice(&self) -> &mut [u8] {
737 unsafe { std::slice::from_raw_parts_mut(self.as_mut_ptr(), self.len()) }
738 }
739 #[inline]
740 pub fn as_ptr(&self) -> *const u8 {
741 match self {
742 Self::Heap(buf) => buf.as_ptr(),
743 Self::Shared(buf) => buf.as_ptr(),
744 Self::HeapView { data, start } => unsafe { data.as_ptr().add(*start) },
745 Self::Pooled(buf) => buf.as_ptr(),
746 }
747 }
748 #[inline]
749 pub fn as_mut_ptr(&self) -> *mut u8 {
750 match self {
751 Self::Heap(buf) => buf.as_ptr() as *mut u8,
752 Self::Shared(_) => panic!("Buffer::Shared is immutable"),
753 Self::HeapView { data, start } => unsafe { (data.as_ptr() as *mut u8).add(*start) },
754 Self::Pooled(buf) => buf.as_ptr() as *mut u8,
755 }
756 }
757
758 #[inline]
759 pub fn is_pooled(&self) -> bool {
760 matches!(self, Self::Pooled(..))
761 }
762
763 #[inline]
764 pub fn is_heap(&self) -> bool {
765 matches!(self, Self::Heap(..) | Self::HeapView { .. })
766 }
767}
768
769crate::thread::thread_local! {
770 pub static TEMP_BUFFER_CACHE: RefCell<TempBufferCache> = RefCell::new(TempBufferCache::new());
772}
773
774#[cfg(clt_turso_tests)]
775mod buffer_tests {
776 use super::*;
777
778 #[test]
779 fn shared_buffer_exposes_arc_bytes() {
780 let data = Arc::new(vec![1, 2, 3, 4].into_boxed_slice());
781 let buffer = Buffer::new_shared(data.clone());
782
783 assert_eq!(buffer.len(), 4);
784 assert_eq!(buffer.as_slice(), &[1, 2, 3, 4]);
785 assert_eq!(buffer.as_ptr(), data.as_ref().as_ptr());
786 assert!(!buffer.is_heap());
787 assert!(!buffer.is_pooled());
788 }
789
790 #[test]
791 fn shared_buffer_view_exposes_tail_without_copying() {
792 let data = Arc::new(vec![0, 1, 2, 3, 4].into_boxed_slice());
793 let shared = SharedBufferData::new_view(data.clone(), 2);
794 let buffer = Buffer::new_shared_data(shared.clone());
795
796 assert_eq!(shared.len(), 3);
797 assert_eq!(shared.as_slice(), &[2, 3, 4]);
798 assert_eq!(shared.as_ptr(), unsafe { data.as_ref().as_ptr().add(2) });
799 assert_eq!(buffer.len(), 3);
800 assert_eq!(buffer.as_slice(), &[2, 3, 4]);
801 assert_eq!(buffer.as_ptr(), shared.as_ptr());
802 }
803}
804
805pub(crate) struct TempBufferCache {
809 page_size: usize,
811 page_buffers: Vec<BufferData>,
813 wal_frame_buffers: Vec<BufferData>,
815 max_cached: usize,
817}
818
819impl TempBufferCache {
820 const DEFAULT_MAX_CACHE_SIZE: usize = 256;
821
822 fn new() -> Self {
823 Self {
824 page_size: BufferPool::DEFAULT_PAGE_SIZE,
825 page_buffers: Vec::with_capacity(8),
826 wal_frame_buffers: Vec::with_capacity(8),
827 max_cached: Self::DEFAULT_MAX_CACHE_SIZE,
828 }
829 }
830
831 pub fn reinit_cache(&mut self, page_size: usize) {
834 self.page_buffers.clear();
835 self.wal_frame_buffers.clear();
836 self.page_size = page_size;
837 }
838
839 fn get_buffer(&mut self, size: usize) -> Option<BufferData> {
840 match size {
841 sz if sz == self.page_size => self.page_buffers.pop(),
842 sz if sz == (self.page_size + WAL_FRAME_HEADER_SIZE) => self.wal_frame_buffers.pop(),
843 _ => None,
844 }
845 }
846
847 fn return_buffer(&mut self, buff: BufferData, len: usize) {
848 let sz = self.page_size;
849 let cache = match len {
850 n if n.eq(&sz) => &mut self.page_buffers,
851 n if n.eq(&(sz + WAL_FRAME_HEADER_SIZE)) => &mut self.wal_frame_buffers,
852 _ => return,
853 };
854 if self.max_cached > cache.len() {
855 cache.push(buff);
856 }
857 }
858}
859
860#[allow(clippy::type_complexity)]
862static IO_REGISTRY: LazyLock<parking_lot::Mutex<HashMap<String, Arc<dyn IO>>>> =
863 LazyLock::new(|| parking_lot::Mutex::new(HashMap::new()));
864
865const BUILTIN_VFS_NAMES: &[&str] = &["memory", "syscall", "io_uring", "experimental_win_iocp"];
866
867pub fn register_io(name: &str, io: Arc<dyn IO>) -> crate::Result<()> {
881 if name.is_empty() {
882 return Err(crate::LimboError::InvalidArgument(
883 "IO backend name must not be empty".into(),
884 ));
885 }
886 if BUILTIN_VFS_NAMES.contains(&name) {
887 tracing::warn!("registered IO backend \"{name}\" shadows a built-in VFS");
888 }
889 IO_REGISTRY.lock().insert(name.to_string(), io);
890 Ok(())
891}
892
893pub fn unregister_io(name: &str) -> bool {
897 IO_REGISTRY.lock().remove(name).is_some()
898}
899
900pub fn get_registered_io(name: &str) -> Option<Arc<dyn IO>> {
902 IO_REGISTRY.lock().get(name).cloned()
903}
904
905pub fn list_registered_io() -> Vec<String> {
907 IO_REGISTRY.lock().keys().cloned().collect()
908}
909
910#[cfg(clt_turso_tests)]
911mod io_registry_tests {
912 use super::*;
913
914 #[test]
915 fn register_and_retrieve() {
916 let io = Arc::new(MemoryIO::new());
917 register_io("ioreg::retrieve", io).unwrap();
918 assert!(get_registered_io("ioreg::retrieve").is_some());
919 assert!(get_registered_io("nonexistent").is_none());
920 unregister_io("ioreg::retrieve");
921 }
922
923 #[test]
924 fn re_register_replaces() {
925 let io1 = Arc::new(MemoryIO::new());
926 let io2 = Arc::new(MemoryIO::new());
927 register_io("ioreg::replace", io1).unwrap();
928 register_io("ioreg::replace", io2).unwrap();
929 let count = list_registered_io()
930 .into_iter()
931 .filter(|n| n == "ioreg::replace")
932 .count();
933 assert_eq!(count, 1, "should not duplicate entries");
934 unregister_io("ioreg::replace");
935 }
936
937 #[test]
938 fn unregister_returns_false_for_missing() {
939 assert!(!unregister_io("ioreg::never_registered"));
940 }
941
942 #[test]
943 fn unregister_removes() {
944 let io = Arc::new(MemoryIO::new());
945 register_io("ioreg::removable", io).unwrap();
946 assert!(unregister_io("ioreg::removable"));
947 assert!(get_registered_io("ioreg::removable").is_none());
948 }
949
950 #[test]
951 fn list_includes_registered() {
952 let io = Arc::new(MemoryIO::new());
953 register_io("ioreg::listed", io).unwrap();
954 assert!(list_registered_io().contains(&"ioreg::listed".to_string()));
955 unregister_io("ioreg::listed");
956 }
957
958 #[test]
959 fn empty_name_returns_error() {
960 let result = register_io("", Arc::new(MemoryIO::new()));
961 assert!(result.is_err());
962 }
963}
964
965#[cfg(all(shuttle, clt_turso_tests))]
966mod shuttle_tests {
967 use std::path::PathBuf;
968
969 use super::*;
970 use crate::io::{Buffer, Completion, OpenFlags, IO};
971 use crate::sync::atomic::{AtomicUsize, Ordering};
972 use crate::sync::Arc;
973 use crate::thread;
974
975 trait IOFactory: Send + Sync + 'static {
978 fn create(&self) -> Arc<dyn IO>;
979 fn temp_dir(&self) -> PathBuf;
981 }
982
983 struct MemoryIOFactory {
984 id: u64,
985 }
986
987 impl MemoryIOFactory {
988 fn new() -> Self {
989 use crate::sync::atomic::AtomicU64;
990 static COUNTER: AtomicU64 = AtomicU64::new(0);
991 Self {
992 id: COUNTER.fetch_add(1, Ordering::SeqCst),
993 }
994 }
995 }
996
997 impl IOFactory for MemoryIOFactory {
998 fn create(&self) -> Arc<dyn IO> {
999 Arc::new(MemoryIO::new())
1000 }
1001 fn temp_dir(&self) -> PathBuf {
1002 format!("mem_{}", self.id).into()
1003 }
1004 }
1005
1006 #[cfg(all(target_family = "unix", clt_turso_feature = "fs", not(miri)))]
1007 struct PlatformIOFactory {
1008 temp_dir: tempfile::TempDir,
1009 }
1010
1011 #[cfg(all(target_family = "unix", clt_turso_feature = "fs", not(miri)))]
1012 impl PlatformIOFactory {
1013 fn new() -> Self {
1014 Self {
1015 temp_dir: tempfile::tempdir().unwrap(),
1016 }
1017 }
1018 }
1019
1020 #[cfg(all(target_family = "unix", clt_turso_feature = "fs", not(miri)))]
1021 impl IOFactory for PlatformIOFactory {
1022 fn create(&self) -> Arc<dyn IO> {
1023 Arc::new(PlatformIO::new().unwrap())
1024 }
1025 fn temp_dir(&self) -> PathBuf {
1026 self.temp_dir.path().to_path_buf()
1027 }
1028 }
1029
1030 #[cfg(all(target_os = "linux", clt_turso_feature = "io_uring", clt_turso_feature = "fs", not(miri)))]
1031 struct UringIOFactory {
1032 temp_dir: tempfile::TempDir,
1033 }
1034
1035 #[cfg(all(target_os = "linux", clt_turso_feature = "io_uring", clt_turso_feature = "fs", not(miri)))]
1036 impl UringIOFactory {
1037 fn new() -> Self {
1038 Self {
1039 temp_dir: tempfile::tempdir().unwrap(),
1040 }
1041 }
1042 }
1043
1044 #[cfg(all(target_os = "linux", clt_turso_feature = "io_uring", clt_turso_feature = "fs", not(miri)))]
1045 impl IOFactory for UringIOFactory {
1046 fn create(&self) -> Arc<dyn IO> {
1047 Arc::new(UringIO::new().unwrap())
1048 }
1049 fn temp_dir(&self) -> PathBuf {
1050 self.temp_dir.path().to_path_buf()
1051 }
1052 }
1053
1054 #[cfg(all(
1055 target_os = "windows",
1056 clt_turso_feature = "experimental_win_iocp",
1057 clt_turso_feature = "fs",
1058 not(miri)
1059 ))]
1060 struct WinIOCPFactory {
1061 temp_dir: tempfile::TempDir,
1062 }
1063
1064 #[cfg(all(
1065 target_os = "windows",
1066 clt_turso_feature = "experimental_win_iocp",
1067 clt_turso_feature = "fs",
1068 not(miri)
1069 ))]
1070 impl WinIOCPFactory {
1071 fn new() -> Self {
1072 Self {
1073 temp_dir: tempfile::tempdir().unwrap(),
1074 }
1075 }
1076 }
1077
1078 #[cfg(all(
1079 target_os = "windows",
1080 clt_turso_feature = "experimental_win_iocp",
1081 clt_turso_feature = "fs",
1082 not(miri)
1083 ))]
1084 impl IOFactory for WinIOCPFactory {
1085 fn create(&self) -> Arc<dyn IO> {
1086 Arc::new(WindowsIOCP::new().unwrap())
1087 }
1088 fn temp_dir(&self) -> PathBuf {
1089 self.temp_dir.path().to_path_buf()
1090 }
1091 }
1092
1093 macro_rules! shuttle_io_test {
1096 ($test_name:ident, $test_impl:ident) => {
1097 pastey::paste! {
1098 #[test]
1099 fn [<shuttle_ $test_name _memory>]() {
1100 shuttle::check_random(|| $test_impl(MemoryIOFactory::new()), 1000);
1101 }
1102
1103 #[cfg(all(target_family = "unix", clt_turso_feature = "fs", not(miri)))]
1104 #[test]
1105 fn [<shuttle_ $test_name _platform>]() {
1106 shuttle::check_random(|| $test_impl(PlatformIOFactory::new()), 1000);
1107 }
1108
1109 #[cfg(all(target_os = "linux", clt_turso_feature = "io_uring", clt_turso_feature = "fs", not(miri)))]
1110 #[test]
1111 fn [<shuttle_ $test_name _uring>]() {
1112 shuttle::check_random(|| $test_impl(UringIOFactory::new()), 1000);
1113 }
1114
1115 #[cfg(all(target_os = "windows", clt_turso_feature = "experimental_win_iocp", clt_turso_feature = "fs", not(miri)))]
1116 #[test]
1117 fn [<shuttle_ $test_name _win_iocp>]() {
1118 shuttle::check_random(|| $test_impl(WinIOCPFactory::new()), 1000);
1119 }
1120
1121 }
1122 };
1123 }
1124
1125 fn wait_completion_ok(io: &dyn IO, c: &Completion) {
1127 io.wait_for_completion(c.clone()).unwrap();
1128 assert!(c.succeeded(), "completion failed: {:?}", c.get_error());
1129 assert!(!c.failed());
1130 assert!(c.finished());
1131 assert!(c.get_error().is_none());
1132 }
1133
1134 #[allow(dead_code)]
1136 fn wait_completion(io: &dyn IO, c: &Completion) {
1137 io.wait_for_completion(c.clone()).unwrap();
1138 assert!(c.finished());
1139 }
1140
1141 fn test_concurrent_file_creation_impl<F: IOFactory>(factory: F) {
1143 let io = factory.create();
1144 let base = factory.temp_dir();
1145 let mut handles = vec![];
1146 const NUM_THREADS: usize = 3;
1147
1148 for i in 0..NUM_THREADS {
1149 let io = io.clone();
1150 let base = base.clone();
1151 handles.push(thread::spawn(move || {
1152 let path = base.join(format!("test_file_{}.db", i));
1153 let file = io
1154 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1155 .unwrap();
1156 assert!(file.size().unwrap() == 0);
1157 }));
1158 }
1159
1160 for h in handles {
1161 h.join().unwrap();
1162 }
1163 }
1164
1165 shuttle_io_test!(concurrent_file_creation, test_concurrent_file_creation_impl);
1166
1167 fn test_concurrent_writes_different_offsets_impl<F: IOFactory>(factory: F) {
1169 let io = factory.create();
1170 let path = factory.temp_dir().join("test.db");
1171 let file = io
1172 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1173 .unwrap();
1174
1175 let mut handles = vec![];
1176 const NUM_THREADS: usize = 3;
1177
1178 for i in 0..NUM_THREADS {
1179 let file = file.clone();
1180 let io = io.clone();
1181 handles.push(thread::spawn(move || {
1182 let data = vec![i as u8; 100];
1183 let buf = Arc::new(Buffer::new(data));
1184 let pos = (i * 100) as u64;
1185
1186 let c = Completion::new_write(|_| {});
1187 let c = file.pwrite(pos, buf, c).unwrap();
1188 wait_completion_ok(io.as_ref(), &c);
1189 }));
1190 }
1191
1192 for h in handles {
1193 h.join().unwrap();
1194 }
1195
1196 let expected_size = (NUM_THREADS * 100) as u64;
1198 assert_eq!(file.size().unwrap(), expected_size);
1199
1200 for i in 0..NUM_THREADS {
1202 let read_buf = Arc::new(Buffer::new_temporary(100));
1203 let pos = (i * 100) as u64;
1204 let c = Completion::new_read(read_buf.clone(), |_| None);
1205 let c = file.pread(pos, c).unwrap();
1206 wait_completion_ok(io.as_ref(), &c);
1207
1208 let expected = vec![i as u8; 100];
1209 assert_eq!(
1210 read_buf.as_slice(),
1211 expected.as_slice(),
1212 "data mismatch at offset {}",
1213 pos
1214 );
1215 }
1216 }
1217
1218 shuttle_io_test!(
1219 concurrent_writes_different_offsets,
1220 test_concurrent_writes_different_offsets_impl
1221 );
1222
1223 fn test_concurrent_read_write_impl<F: IOFactory>(factory: F) {
1225 let io = factory.create();
1226 let path = factory.temp_dir().join("test.db");
1227 let file = io
1228 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1229 .unwrap();
1230
1231 let initial_data = vec![0xAA; 1000];
1233 let buf = Arc::new(Buffer::new(initial_data));
1234 let c = Completion::new_write(|_| {});
1235 let c = file.pwrite(0, buf, c).unwrap();
1236 wait_completion_ok(io.as_ref(), &c);
1237
1238 let mut handles = vec![];
1239
1240 for _ in 0..2 {
1242 let file = file.clone();
1243 let io = io.clone();
1244 handles.push(thread::spawn(move || {
1245 let read_buf = Arc::new(Buffer::new_temporary(100));
1246 let c = Completion::new_read(read_buf.clone(), |_| None);
1247 let c = file.pread(0, c).unwrap();
1248 wait_completion_ok(io.as_ref(), &c);
1249
1250 assert!(
1252 read_buf.as_slice().iter().all(|&b| b == 0xAA),
1253 "read buffer should contain initial data 0xAA"
1254 );
1255 }));
1256 }
1257
1258 {
1260 let file = file.clone();
1261 let io = io.clone();
1262 handles.push(thread::spawn(move || {
1263 let data = vec![0xBB; 100];
1264 let buf = Arc::new(Buffer::new(data));
1265 let c = Completion::new_write(|_| {});
1266 let c = file.pwrite(500, buf, c).unwrap();
1267 wait_completion_ok(io.as_ref(), &c);
1268 }));
1269 }
1270
1271 for h in handles {
1272 h.join().unwrap();
1273 }
1274
1275 let read_buf = Arc::new(Buffer::new_temporary(100));
1277 let c = Completion::new_read(read_buf.clone(), |_| None);
1278 let c = file.pread(500, c).unwrap();
1279 wait_completion_ok(io.as_ref(), &c);
1280 assert!(
1281 read_buf.as_slice().iter().all(|&b| b == 0xBB),
1282 "data at offset 500 should be 0xBB"
1283 );
1284 }
1285
1286 shuttle_io_test!(concurrent_read_write, test_concurrent_read_write_impl);
1287
1288 fn test_completion_callbacks_concurrent_impl<F: IOFactory>(factory: F) {
1290 let io = factory.create();
1291 let path = factory.temp_dir().join("test.db");
1292 let file = io
1293 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1294 .unwrap();
1295
1296 let callback_count = Arc::new(AtomicUsize::new(0));
1297 let mut handles = vec![];
1298 const NUM_WRITES: usize = 3;
1299
1300 for i in 0..NUM_WRITES {
1301 let file = file.clone();
1302 let io = io.clone();
1303 let count = callback_count.clone();
1304 handles.push(thread::spawn(move || {
1305 let data = vec![i as u8; 50];
1306 let buf = Arc::new(Buffer::new(data));
1307 let count_clone = count.clone();
1308 let c = Completion::new_write(move |res| {
1309 assert!(res.is_ok());
1310 count_clone.fetch_add(1, Ordering::SeqCst);
1311 });
1312 let c = file.pwrite((i * 50) as u64, buf, c).unwrap();
1313 wait_completion_ok(io.as_ref(), &c);
1314 }));
1315 }
1316
1317 for h in handles {
1318 h.join().unwrap();
1319 }
1320
1321 assert_eq!(callback_count.load(Ordering::SeqCst), NUM_WRITES);
1322 }
1323
1324 shuttle_io_test!(
1325 completion_callbacks_concurrent,
1326 test_completion_callbacks_concurrent_impl
1327 );
1328
1329 fn test_concurrent_truncate_impl<F: IOFactory>(factory: F) {
1331 let io = factory.create();
1332 let path = factory.temp_dir().join("test.db");
1333 let file = io
1334 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1335 .unwrap();
1336
1337 let initial = vec![0xFF; 5000];
1339 let buf = Arc::new(Buffer::new(initial));
1340 let c = Completion::new_write(|_| {});
1341 let c = file.pwrite(0, buf, c).unwrap();
1342 wait_completion_ok(io.as_ref(), &c);
1343
1344 let mut handles = vec![];
1345
1346 for i in 0..3 {
1348 let file = file.clone();
1349 let io = io.clone();
1350 handles.push(thread::spawn(move || {
1351 let truncate_size = ((i + 1) * 1000) as u64;
1352 let c = Completion::new_trunc(|_| {});
1353 let c = file.truncate(truncate_size, c).unwrap();
1354 wait_completion_ok(io.as_ref(), &c);
1355 }));
1356 }
1357
1358 for h in handles {
1359 h.join().unwrap();
1360 }
1361
1362 let final_size = file.size().unwrap();
1364 assert!(final_size == 1000 || final_size == 2000 || final_size == 3000);
1365 }
1366
1367 shuttle_io_test!(concurrent_truncate, test_concurrent_truncate_impl);
1368
1369 fn test_pwritev_with_concurrent_reads_impl<F: IOFactory>(factory: F) {
1371 let io = factory.create();
1372 let path = factory.temp_dir().join("test.db");
1373 let file = io
1374 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1375 .unwrap();
1376
1377 let initial = vec![0x11; 2000];
1379 let buf = Arc::new(Buffer::new(initial));
1380 let c = Completion::new_write(|_| {});
1381 let c = file.pwrite(0, buf, c).unwrap();
1382 wait_completion_ok(io.as_ref(), &c);
1383
1384 let mut handles = vec![];
1385
1386 {
1388 let file = file.clone();
1389 let io = io.clone();
1390 handles.push(thread::spawn(move || {
1391 let bufs = vec![
1392 Arc::new(Buffer::new(vec![0x22; 100])),
1393 Arc::new(Buffer::new(vec![0x33; 100])),
1394 Arc::new(Buffer::new(vec![0x44; 100])),
1395 ];
1396 let c = Completion::new_write(|_| {});
1397 let c = file.pwritev(0, bufs, c).unwrap();
1398 wait_completion_ok(io.as_ref(), &c);
1399 }));
1400 }
1401
1402 for _ in 0..2 {
1404 let file = file.clone();
1405 let io = io.clone();
1406 handles.push(thread::spawn(move || {
1407 let buf = Arc::new(Buffer::new_temporary(100));
1408 let c = Completion::new_read(buf.clone(), |_| None);
1409 let c = file.pread(0, c).unwrap();
1410 wait_completion_ok(io.as_ref(), &c);
1411
1412 let first_byte = buf.as_slice()[0];
1415 assert!(
1416 first_byte == 0x11 || first_byte == 0x22,
1417 "first byte should be 0x11 or 0x22, got {:#x}",
1418 first_byte
1419 );
1420 assert!(
1422 buf.as_slice().iter().all(|&b| b == first_byte),
1423 "all bytes should be the same value"
1424 );
1425 }));
1426 }
1427
1428 for h in handles {
1429 h.join().unwrap();
1430 }
1431
1432 let read_buf = Arc::new(Buffer::new_temporary(300));
1434 let c = Completion::new_read(read_buf.clone(), |_| None);
1435 let c = file.pread(0, c).unwrap();
1436 wait_completion_ok(io.as_ref(), &c);
1437
1438 assert!(
1440 read_buf.as_slice()[..100].iter().all(|&b| b == 0x22),
1441 "bytes 0-99 should be 0x22"
1442 );
1443 assert!(
1444 read_buf.as_slice()[100..200].iter().all(|&b| b == 0x33),
1445 "bytes 100-199 should be 0x33"
1446 );
1447 assert!(
1448 read_buf.as_slice()[200..300].iter().all(|&b| b == 0x44),
1449 "bytes 200-299 should be 0x44"
1450 );
1451 }
1452
1453 shuttle_io_test!(
1454 pwritev_with_concurrent_reads,
1455 test_pwritev_with_concurrent_reads_impl
1456 );
1457
1458 fn test_concurrent_multifile_access_impl<F: IOFactory>(factory: F) {
1460 let io = factory.create();
1461 let base = factory.temp_dir();
1462
1463 let mut handles = vec![];
1464 const NUM_FILES: usize = 3;
1465
1466 for i in 0..NUM_FILES {
1467 let io = io.clone();
1468 let base = base.clone();
1469 handles.push(thread::spawn(move || {
1470 let path = base.join(format!("file_{}.db", i));
1471 let file = io
1472 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1473 .unwrap();
1474
1475 let data = vec![i as u8; 200];
1477 let buf = Arc::new(Buffer::new(data.clone()));
1478 let c = Completion::new_write(|_| {});
1479 let c = file.pwrite(0, buf, c).unwrap();
1480 wait_completion_ok(io.as_ref(), &c);
1481
1482 let read_buf = Arc::new(Buffer::new_temporary(200));
1484 let c = Completion::new_read(read_buf.clone(), |_| None);
1485 let c = file.pread(0, c).unwrap();
1486 wait_completion_ok(io.as_ref(), &c);
1487
1488 assert_eq!(read_buf.as_slice(), data.as_slice());
1489 }));
1490 }
1491
1492 for h in handles {
1493 h.join().unwrap();
1494 }
1495 }
1496
1497 shuttle_io_test!(
1498 concurrent_multifile_access,
1499 test_concurrent_multifile_access_impl
1500 );
1501
1502 fn test_file_locking_concurrent_impl<F: IOFactory>(factory: F) {
1504 let io = factory.create();
1505 let path = factory.temp_dir().join("test.db");
1506 let file = io
1507 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1508 .unwrap();
1509
1510 let mut handles = vec![];
1511
1512 for _ in 0..3 {
1514 let file = file.clone();
1515 handles.push(thread::spawn(move || {
1516 file.lock_file(true).unwrap();
1518 thread::yield_now();
1519 file.unlock_file().unwrap();
1520
1521 file.lock_file(false).unwrap();
1523 thread::yield_now();
1524 file.unlock_file().unwrap();
1525 }));
1526 }
1527
1528 for h in handles {
1529 h.join().unwrap();
1530 }
1531 }
1532
1533 shuttle_io_test!(file_locking_concurrent, test_file_locking_concurrent_impl);
1534
1535 fn test_read_past_eof_impl<F: IOFactory>(factory: F) {
1537 let io = factory.create();
1538 let path = factory.temp_dir().join("test.db");
1539 let file = io
1540 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1541 .unwrap();
1542
1543 let data = vec![0xAA; 100];
1545 let buf = Arc::new(Buffer::new(data));
1546 let c = Completion::new_write(|_| {});
1547 let c = file.pwrite(0, buf, c).unwrap();
1548 wait_completion_ok(io.as_ref(), &c);
1549
1550 let mut handles = vec![];
1551
1552 for _ in 0..3 {
1554 let file = file.clone();
1555 let io = io.clone();
1556 handles.push(thread::spawn(move || {
1557 let read_buf = Arc::new(Buffer::new_temporary(100));
1558 let bytes_read = Arc::new(AtomicUsize::new(999));
1559 let bytes_read_clone = bytes_read.clone();
1560 let c = Completion::new_read(read_buf, move |res| {
1561 if let Ok((_, n)) = res {
1562 bytes_read_clone.store(n as usize, Ordering::SeqCst);
1563 }
1564 None
1565 });
1566 let c = file.pread(200, c).unwrap(); wait_completion_ok(io.as_ref(), &c);
1569 assert_eq!(bytes_read.load(Ordering::SeqCst), 0);
1570 }));
1571 }
1572
1573 for h in handles {
1574 h.join().unwrap();
1575 }
1576 }
1577
1578 shuttle_io_test!(read_past_eof, test_read_past_eof_impl);
1579
1580 fn test_empty_write_impl<F: IOFactory>(factory: F) {
1582 let io = factory.create();
1583 let path = factory.temp_dir().join("test.db");
1584 let file = io
1585 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1586 .unwrap();
1587
1588 let mut handles = vec![];
1589
1590 for _ in 0..3 {
1591 let file = file.clone();
1592 let io = io.clone();
1593 handles.push(thread::spawn(move || {
1594 let buf = Arc::new(Buffer::new(vec![]));
1596 let c = Completion::new_write(|_| {});
1597 let c = file.pwrite(0, buf, c).unwrap();
1598 wait_completion_ok(io.as_ref(), &c);
1599 }));
1600 }
1601
1602 for h in handles {
1603 h.join().unwrap();
1604 }
1605
1606 assert_eq!(file.size().unwrap(), 0);
1607 }
1608
1609 shuttle_io_test!(empty_write, test_empty_write_impl);
1610
1611 fn test_concurrent_sync_impl<F: IOFactory>(factory: F) {
1613 let io = factory.create();
1614 let path = factory.temp_dir().join("test.db");
1615 let file = io
1616 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1617 .unwrap();
1618
1619 let data = vec![0xFF; 1000];
1621 let buf = Arc::new(Buffer::new(data));
1622 let c = Completion::new_write(|_| {});
1623 let c = file.pwrite(0, buf, c).unwrap();
1624 wait_completion_ok(io.as_ref(), &c);
1625
1626 let mut handles = vec![];
1627
1628 for _ in 0..3 {
1630 let file = file.clone();
1631 let io = io.clone();
1632 handles.push(thread::spawn(move || {
1633 let c = Completion::new_sync(|_| {});
1634 let c = file.sync(c, FileSyncType::Fsync).unwrap();
1635 wait_completion_ok(io.as_ref(), &c);
1636 }));
1637 }
1638
1639 for h in handles {
1640 h.join().unwrap();
1641 }
1642 }
1643
1644 shuttle_io_test!(concurrent_sync, test_concurrent_sync_impl);
1645
1646 fn test_concurrent_open_same_file_impl<F: IOFactory>(factory: F) {
1648 let io = factory.create();
1649 let path = factory.temp_dir().join("shared.db");
1650
1651 let _ = io
1653 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1654 .unwrap();
1655
1656 let mut handles = vec![];
1657
1658 for _ in 0..3 {
1659 let io = io.clone();
1660 let path = path.clone();
1661 handles.push(thread::spawn(move || {
1662 let file = io
1663 .open_file(path.to_str().unwrap(), OpenFlags::None, false)
1664 .unwrap();
1665 thread::yield_now();
1666 let buf = Arc::new(Buffer::new(vec![0xAA]));
1668 let c = Completion::new_write(|_| {});
1669 let c = file.pwrite(0, buf, c).unwrap();
1670 wait_completion_ok(io.as_ref(), &c);
1671 }));
1672 }
1673
1674 for h in handles {
1675 h.join().unwrap();
1676 }
1677 }
1678
1679 shuttle_io_test!(
1680 concurrent_open_same_file,
1681 test_concurrent_open_same_file_impl
1682 );
1683
1684 fn test_file_remove_concurrent_impl<F: IOFactory>(factory: F) {
1686 let io = factory.create();
1687 let base = factory.temp_dir();
1688
1689 for i in 0..3 {
1691 let path = base.join(format!("remove_{}.db", i));
1692 let file = io
1693 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1694 .unwrap();
1695 let buf = Arc::new(Buffer::new(vec![0xFF; 100]));
1696 let c = Completion::new_write(|_| {});
1697 let c = file.pwrite(0, buf, c).unwrap();
1698 wait_completion_ok(io.as_ref(), &c);
1699 }
1700
1701 let mut handles = vec![];
1702
1703 for i in 0..3 {
1705 let io = io.clone();
1706 let base = base.clone();
1707 handles.push(thread::spawn(move || {
1708 let path = base.join(format!("remove_{}.db", i));
1709 io.remove_file(path.to_str().unwrap()).unwrap();
1710 }));
1711 }
1712
1713 for h in handles {
1714 h.join().unwrap();
1715 }
1716 }
1717
1718 shuttle_io_test!(file_remove_concurrent, test_file_remove_concurrent_impl);
1719
1720 fn test_large_write_concurrent_impl<F: IOFactory>(factory: F) {
1722 let io = factory.create();
1723 let path = factory.temp_dir().join("test.db");
1724 let file = io
1725 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1726 .unwrap();
1727
1728 let mut handles = vec![];
1729
1730 for i in 0..2 {
1732 let file = file.clone();
1733 let io = io.clone();
1734 handles.push(thread::spawn(move || {
1735 let data = vec![(i + 1) as u8; 10000];
1737 let buf = Arc::new(Buffer::new(data));
1738 let c = Completion::new_write(|_| {});
1739 let c = file.pwrite((i * 10000) as u64, buf, c).unwrap();
1740 wait_completion_ok(io.as_ref(), &c);
1741 }));
1742 }
1743
1744 for h in handles {
1745 h.join().unwrap();
1746 }
1747
1748 assert_eq!(file.size().unwrap(), 20000);
1749
1750 for i in 0..2 {
1752 let read_buf = Arc::new(Buffer::new_temporary(10000));
1753 let pos = (i * 10000) as u64;
1754 let c = Completion::new_read(read_buf.clone(), |_| None);
1755 let c = file.pread(pos, c).unwrap();
1756 wait_completion_ok(io.as_ref(), &c);
1757
1758 let expected_byte = (i + 1) as u8;
1759 assert!(
1760 read_buf.as_slice().iter().all(|&b| b == expected_byte),
1761 "all bytes at offset {} should be {:#x}",
1762 pos,
1763 expected_byte
1764 );
1765 }
1766 }
1767
1768 shuttle_io_test!(large_write_concurrent, test_large_write_concurrent_impl);
1769
1770 fn test_hole_operations_concurrent_impl<F: IOFactory>(factory: F) {
1773 let io = factory.create();
1774 let path = factory.temp_dir().join("test.db");
1775 let file = io
1776 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1777 .unwrap();
1778
1779 let data = vec![0xFF; 16384];
1781 let buf = Arc::new(Buffer::new(data));
1782 let c = Completion::new_write(|_| {});
1783 let c = file.pwrite(0, buf, c).unwrap();
1784 wait_completion_ok(io.as_ref(), &c);
1785
1786 let mut handles = vec![];
1787
1788 {
1790 let file = file.clone();
1791 handles.push(thread::spawn(move || {
1792 file.punch_hole(4096, 4096).unwrap();
1794 }));
1795 }
1796
1797 {
1799 let file = file.clone();
1800 handles.push(thread::spawn(move || {
1801 let has_hole = file.has_hole(0, 4096).unwrap();
1803 assert!(!has_hole);
1804 let _ = file.has_hole(4096, 4096).unwrap();
1805 let has_hole = file.has_hole(8192, 4096).unwrap();
1806 assert!(!has_hole);
1807 }));
1808 }
1809
1810 for h in handles {
1811 h.join().unwrap();
1812 }
1813 }
1814
1815 #[test]
1817 fn shuttle_hole_operations_concurrent_memory() {
1818 shuttle::check_random(
1819 || test_hole_operations_concurrent_impl(MemoryIOFactory::new()),
1820 1000,
1821 );
1822 }
1823
1824 fn test_partial_read_at_eof_impl<F: IOFactory>(factory: F) {
1826 let io = factory.create();
1827 let path = factory.temp_dir().join("test.db");
1828 let file = io
1829 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1830 .unwrap();
1831
1832 let data = vec![0xAB; 150];
1834 let buf = Arc::new(Buffer::new(data));
1835 let c = Completion::new_write(|_| {});
1836 let c = file.pwrite(0, buf, c).unwrap();
1837 wait_completion_ok(io.as_ref(), &c);
1838
1839 let mut handles = vec![];
1840
1841 for _ in 0..3 {
1844 let file = file.clone();
1845 let io = io.clone();
1846 handles.push(thread::spawn(move || {
1847 let read_buf = Arc::new(Buffer::new_temporary(100));
1848 let bytes_read = Arc::new(AtomicUsize::new(999));
1849 let bytes_read_clone = bytes_read.clone();
1850 let c = Completion::new_read(read_buf.clone(), move |res| {
1851 if let Ok((_, n)) = res {
1852 bytes_read_clone.store(n as usize, Ordering::SeqCst);
1853 }
1854 None
1855 });
1856 let c = file.pread(100, c).unwrap();
1857 wait_completion_ok(io.as_ref(), &c);
1858
1859 assert_eq!(bytes_read.load(Ordering::SeqCst), 50);
1861 assert_eq!(&read_buf.as_slice()[..50], &[0xAB; 50]);
1863 }));
1864 }
1865
1866 for h in handles {
1867 h.join().unwrap();
1868 }
1869 }
1870
1871 shuttle_io_test!(partial_read_at_eof, test_partial_read_at_eof_impl);
1872
1873 fn test_empty_pwritev_impl<F: IOFactory>(factory: F) {
1875 let io = factory.create();
1876 let path = factory.temp_dir().join("test.db");
1877 let file = io
1878 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1879 .unwrap();
1880
1881 let mut handles = vec![];
1882
1883 for _ in 0..3 {
1884 let file = file.clone();
1885 let io = io.clone();
1886 handles.push(thread::spawn(move || {
1887 let bufs: Vec<Arc<Buffer>> = vec![];
1888 let c = Completion::new_write(|_| {});
1889 let c = file.pwritev(0, bufs, c).unwrap();
1890 wait_completion_ok(io.as_ref(), &c);
1891 }));
1892 }
1893
1894 for h in handles {
1895 h.join().unwrap();
1896 }
1897 }
1898
1899 shuttle_io_test!(empty_pwritev, test_empty_pwritev_impl);
1900
1901 fn test_open_nonexistent_without_create_impl<F: IOFactory>(factory: F) {
1903 let io = factory.create();
1904 let base = factory.temp_dir();
1905
1906 let mut handles = vec![];
1907
1908 for i in 0..3 {
1909 let io = io.clone();
1910 let base = base.clone();
1911 handles.push(thread::spawn(move || {
1912 let path = base.join(format!("nonexistent_{}.db", i));
1913 let result = io.open_file(path.to_str().unwrap(), OpenFlags::None, false);
1914 assert!(result.is_err());
1915 }));
1916 }
1917
1918 for h in handles {
1919 h.join().unwrap();
1920 }
1921 }
1922
1923 shuttle_io_test!(
1924 open_nonexistent_without_create,
1925 test_open_nonexistent_without_create_impl
1926 );
1927
1928 fn test_concurrent_overlapping_writes_impl<F: IOFactory>(factory: F) {
1931 let io = factory.create();
1932 let path = factory.temp_dir().join("test.db");
1933 let file = io
1934 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1935 .unwrap();
1936
1937 let write_complete = Arc::new(AtomicUsize::new(0));
1938 let mut handles = vec![];
1939
1940 for i in 0..3 {
1942 let file = file.clone();
1943 let io = io.clone();
1944 let write_complete = write_complete.clone();
1945 handles.push(thread::spawn(move || {
1946 let data = vec![(i + 1) as u8; 100];
1947 let buf = Arc::new(Buffer::new(data));
1948 let write_complete_clone = write_complete.clone();
1949 let c = Completion::new_write(move |_| {
1950 write_complete_clone.fetch_add(1, Ordering::SeqCst);
1951 });
1952 let c = file.pwrite(0, buf, c).unwrap();
1953 wait_completion_ok(io.as_ref(), &c);
1954 }));
1955 }
1956
1957 for h in handles {
1958 h.join().unwrap();
1959 }
1960
1961 assert_eq!(write_complete.load(Ordering::SeqCst), 3);
1963
1964 let read_buf = Arc::new(Buffer::new_temporary(100));
1966 let c = Completion::new_read(read_buf.clone(), |_| None);
1967 let c = file.pread(0, c).unwrap();
1968 wait_completion_ok(io.as_ref(), &c);
1969
1970 let first_byte = read_buf.as_slice()[0];
1971 assert!(first_byte == 1 || first_byte == 2 || first_byte == 3);
1972
1973 assert!(read_buf.as_slice().iter().all(|&b| b == first_byte));
1975 }
1976
1977 shuttle_io_test!(
1978 concurrent_overlapping_writes,
1979 test_concurrent_overlapping_writes_impl
1980 );
1981}