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(
1031 target_os = "linux",
1032 clt_turso_feature = "io_uring",
1033 clt_turso_feature = "fs",
1034 not(miri)
1035 ))]
1036 struct UringIOFactory {
1037 temp_dir: tempfile::TempDir,
1038 }
1039
1040 #[cfg(all(
1041 target_os = "linux",
1042 clt_turso_feature = "io_uring",
1043 clt_turso_feature = "fs",
1044 not(miri)
1045 ))]
1046 impl UringIOFactory {
1047 fn new() -> Self {
1048 Self {
1049 temp_dir: tempfile::tempdir().unwrap(),
1050 }
1051 }
1052 }
1053
1054 #[cfg(all(
1055 target_os = "linux",
1056 clt_turso_feature = "io_uring",
1057 clt_turso_feature = "fs",
1058 not(miri)
1059 ))]
1060 impl IOFactory for UringIOFactory {
1061 fn create(&self) -> Arc<dyn IO> {
1062 Arc::new(UringIO::new().unwrap())
1063 }
1064 fn temp_dir(&self) -> PathBuf {
1065 self.temp_dir.path().to_path_buf()
1066 }
1067 }
1068
1069 #[cfg(all(
1070 target_os = "windows",
1071 clt_turso_feature = "experimental_win_iocp",
1072 clt_turso_feature = "fs",
1073 not(miri)
1074 ))]
1075 struct WinIOCPFactory {
1076 temp_dir: tempfile::TempDir,
1077 }
1078
1079 #[cfg(all(
1080 target_os = "windows",
1081 clt_turso_feature = "experimental_win_iocp",
1082 clt_turso_feature = "fs",
1083 not(miri)
1084 ))]
1085 impl WinIOCPFactory {
1086 fn new() -> Self {
1087 Self {
1088 temp_dir: tempfile::tempdir().unwrap(),
1089 }
1090 }
1091 }
1092
1093 #[cfg(all(
1094 target_os = "windows",
1095 clt_turso_feature = "experimental_win_iocp",
1096 clt_turso_feature = "fs",
1097 not(miri)
1098 ))]
1099 impl IOFactory for WinIOCPFactory {
1100 fn create(&self) -> Arc<dyn IO> {
1101 Arc::new(WindowsIOCP::new().unwrap())
1102 }
1103 fn temp_dir(&self) -> PathBuf {
1104 self.temp_dir.path().to_path_buf()
1105 }
1106 }
1107
1108 macro_rules! shuttle_io_test {
1111 ($test_name:ident, $test_impl:ident) => {
1112 pastey::paste! {
1113 #[test]
1114 fn [<shuttle_ $test_name _memory>]() {
1115 shuttle::check_random(|| $test_impl(MemoryIOFactory::new()), 1000);
1116 }
1117
1118 #[cfg(all(target_family = "unix", clt_turso_feature = "fs", not(miri)))]
1119 #[test]
1120 fn [<shuttle_ $test_name _platform>]() {
1121 shuttle::check_random(|| $test_impl(PlatformIOFactory::new()), 1000);
1122 }
1123
1124 #[cfg(all(target_os = "linux", clt_turso_feature = "io_uring", clt_turso_feature = "fs", not(miri)))]
1125 #[test]
1126 fn [<shuttle_ $test_name _uring>]() {
1127 shuttle::check_random(|| $test_impl(UringIOFactory::new()), 1000);
1128 }
1129
1130 #[cfg(all(target_os = "windows", clt_turso_feature = "experimental_win_iocp", clt_turso_feature = "fs", not(miri)))]
1131 #[test]
1132 fn [<shuttle_ $test_name _win_iocp>]() {
1133 shuttle::check_random(|| $test_impl(WinIOCPFactory::new()), 1000);
1134 }
1135
1136 }
1137 };
1138 }
1139
1140 fn wait_completion_ok(io: &dyn IO, c: &Completion) {
1142 io.wait_for_completion(c.clone()).unwrap();
1143 assert!(c.succeeded(), "completion failed: {:?}", c.get_error());
1144 assert!(!c.failed());
1145 assert!(c.finished());
1146 assert!(c.get_error().is_none());
1147 }
1148
1149 #[allow(dead_code)]
1151 fn wait_completion(io: &dyn IO, c: &Completion) {
1152 io.wait_for_completion(c.clone()).unwrap();
1153 assert!(c.finished());
1154 }
1155
1156 fn test_concurrent_file_creation_impl<F: IOFactory>(factory: F) {
1158 let io = factory.create();
1159 let base = factory.temp_dir();
1160 let mut handles = vec![];
1161 const NUM_THREADS: usize = 3;
1162
1163 for i in 0..NUM_THREADS {
1164 let io = io.clone();
1165 let base = base.clone();
1166 handles.push(thread::spawn(move || {
1167 let path = base.join(format!("test_file_{}.db", i));
1168 let file = io
1169 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1170 .unwrap();
1171 assert!(file.size().unwrap() == 0);
1172 }));
1173 }
1174
1175 for h in handles {
1176 h.join().unwrap();
1177 }
1178 }
1179
1180 shuttle_io_test!(concurrent_file_creation, test_concurrent_file_creation_impl);
1181
1182 fn test_concurrent_writes_different_offsets_impl<F: IOFactory>(factory: F) {
1184 let io = factory.create();
1185 let path = factory.temp_dir().join("test.db");
1186 let file = io
1187 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1188 .unwrap();
1189
1190 let mut handles = vec![];
1191 const NUM_THREADS: usize = 3;
1192
1193 for i in 0..NUM_THREADS {
1194 let file = file.clone();
1195 let io = io.clone();
1196 handles.push(thread::spawn(move || {
1197 let data = vec![i as u8; 100];
1198 let buf = Arc::new(Buffer::new(data));
1199 let pos = (i * 100) as u64;
1200
1201 let c = Completion::new_write(|_| {});
1202 let c = file.pwrite(pos, buf, c).unwrap();
1203 wait_completion_ok(io.as_ref(), &c);
1204 }));
1205 }
1206
1207 for h in handles {
1208 h.join().unwrap();
1209 }
1210
1211 let expected_size = (NUM_THREADS * 100) as u64;
1213 assert_eq!(file.size().unwrap(), expected_size);
1214
1215 for i in 0..NUM_THREADS {
1217 let read_buf = Arc::new(Buffer::new_temporary(100));
1218 let pos = (i * 100) as u64;
1219 let c = Completion::new_read(read_buf.clone(), |_| None);
1220 let c = file.pread(pos, c).unwrap();
1221 wait_completion_ok(io.as_ref(), &c);
1222
1223 let expected = vec![i as u8; 100];
1224 assert_eq!(
1225 read_buf.as_slice(),
1226 expected.as_slice(),
1227 "data mismatch at offset {}",
1228 pos
1229 );
1230 }
1231 }
1232
1233 shuttle_io_test!(
1234 concurrent_writes_different_offsets,
1235 test_concurrent_writes_different_offsets_impl
1236 );
1237
1238 fn test_concurrent_read_write_impl<F: IOFactory>(factory: F) {
1240 let io = factory.create();
1241 let path = factory.temp_dir().join("test.db");
1242 let file = io
1243 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1244 .unwrap();
1245
1246 let initial_data = vec![0xAA; 1000];
1248 let buf = Arc::new(Buffer::new(initial_data));
1249 let c = Completion::new_write(|_| {});
1250 let c = file.pwrite(0, buf, c).unwrap();
1251 wait_completion_ok(io.as_ref(), &c);
1252
1253 let mut handles = vec![];
1254
1255 for _ in 0..2 {
1257 let file = file.clone();
1258 let io = io.clone();
1259 handles.push(thread::spawn(move || {
1260 let read_buf = Arc::new(Buffer::new_temporary(100));
1261 let c = Completion::new_read(read_buf.clone(), |_| None);
1262 let c = file.pread(0, c).unwrap();
1263 wait_completion_ok(io.as_ref(), &c);
1264
1265 assert!(
1267 read_buf.as_slice().iter().all(|&b| b == 0xAA),
1268 "read buffer should contain initial data 0xAA"
1269 );
1270 }));
1271 }
1272
1273 {
1275 let file = file.clone();
1276 let io = io.clone();
1277 handles.push(thread::spawn(move || {
1278 let data = vec![0xBB; 100];
1279 let buf = Arc::new(Buffer::new(data));
1280 let c = Completion::new_write(|_| {});
1281 let c = file.pwrite(500, buf, c).unwrap();
1282 wait_completion_ok(io.as_ref(), &c);
1283 }));
1284 }
1285
1286 for h in handles {
1287 h.join().unwrap();
1288 }
1289
1290 let read_buf = Arc::new(Buffer::new_temporary(100));
1292 let c = Completion::new_read(read_buf.clone(), |_| None);
1293 let c = file.pread(500, c).unwrap();
1294 wait_completion_ok(io.as_ref(), &c);
1295 assert!(
1296 read_buf.as_slice().iter().all(|&b| b == 0xBB),
1297 "data at offset 500 should be 0xBB"
1298 );
1299 }
1300
1301 shuttle_io_test!(concurrent_read_write, test_concurrent_read_write_impl);
1302
1303 fn test_completion_callbacks_concurrent_impl<F: IOFactory>(factory: F) {
1305 let io = factory.create();
1306 let path = factory.temp_dir().join("test.db");
1307 let file = io
1308 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1309 .unwrap();
1310
1311 let callback_count = Arc::new(AtomicUsize::new(0));
1312 let mut handles = vec![];
1313 const NUM_WRITES: usize = 3;
1314
1315 for i in 0..NUM_WRITES {
1316 let file = file.clone();
1317 let io = io.clone();
1318 let count = callback_count.clone();
1319 handles.push(thread::spawn(move || {
1320 let data = vec![i as u8; 50];
1321 let buf = Arc::new(Buffer::new(data));
1322 let count_clone = count.clone();
1323 let c = Completion::new_write(move |res| {
1324 assert!(res.is_ok());
1325 count_clone.fetch_add(1, Ordering::SeqCst);
1326 });
1327 let c = file.pwrite((i * 50) as u64, buf, c).unwrap();
1328 wait_completion_ok(io.as_ref(), &c);
1329 }));
1330 }
1331
1332 for h in handles {
1333 h.join().unwrap();
1334 }
1335
1336 assert_eq!(callback_count.load(Ordering::SeqCst), NUM_WRITES);
1337 }
1338
1339 shuttle_io_test!(
1340 completion_callbacks_concurrent,
1341 test_completion_callbacks_concurrent_impl
1342 );
1343
1344 fn test_concurrent_truncate_impl<F: IOFactory>(factory: F) {
1346 let io = factory.create();
1347 let path = factory.temp_dir().join("test.db");
1348 let file = io
1349 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1350 .unwrap();
1351
1352 let initial = vec![0xFF; 5000];
1354 let buf = Arc::new(Buffer::new(initial));
1355 let c = Completion::new_write(|_| {});
1356 let c = file.pwrite(0, buf, c).unwrap();
1357 wait_completion_ok(io.as_ref(), &c);
1358
1359 let mut handles = vec![];
1360
1361 for i in 0..3 {
1363 let file = file.clone();
1364 let io = io.clone();
1365 handles.push(thread::spawn(move || {
1366 let truncate_size = ((i + 1) * 1000) as u64;
1367 let c = Completion::new_trunc(|_| {});
1368 let c = file.truncate(truncate_size, c).unwrap();
1369 wait_completion_ok(io.as_ref(), &c);
1370 }));
1371 }
1372
1373 for h in handles {
1374 h.join().unwrap();
1375 }
1376
1377 let final_size = file.size().unwrap();
1379 assert!(final_size == 1000 || final_size == 2000 || final_size == 3000);
1380 }
1381
1382 shuttle_io_test!(concurrent_truncate, test_concurrent_truncate_impl);
1383
1384 fn test_pwritev_with_concurrent_reads_impl<F: IOFactory>(factory: F) {
1386 let io = factory.create();
1387 let path = factory.temp_dir().join("test.db");
1388 let file = io
1389 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1390 .unwrap();
1391
1392 let initial = vec![0x11; 2000];
1394 let buf = Arc::new(Buffer::new(initial));
1395 let c = Completion::new_write(|_| {});
1396 let c = file.pwrite(0, buf, c).unwrap();
1397 wait_completion_ok(io.as_ref(), &c);
1398
1399 let mut handles = vec![];
1400
1401 {
1403 let file = file.clone();
1404 let io = io.clone();
1405 handles.push(thread::spawn(move || {
1406 let bufs = vec![
1407 Arc::new(Buffer::new(vec![0x22; 100])),
1408 Arc::new(Buffer::new(vec![0x33; 100])),
1409 Arc::new(Buffer::new(vec![0x44; 100])),
1410 ];
1411 let c = Completion::new_write(|_| {});
1412 let c = file.pwritev(0, bufs, c).unwrap();
1413 wait_completion_ok(io.as_ref(), &c);
1414 }));
1415 }
1416
1417 for _ in 0..2 {
1419 let file = file.clone();
1420 let io = io.clone();
1421 handles.push(thread::spawn(move || {
1422 let buf = Arc::new(Buffer::new_temporary(100));
1423 let c = Completion::new_read(buf.clone(), |_| None);
1424 let c = file.pread(0, c).unwrap();
1425 wait_completion_ok(io.as_ref(), &c);
1426
1427 let first_byte = buf.as_slice()[0];
1430 assert!(
1431 first_byte == 0x11 || first_byte == 0x22,
1432 "first byte should be 0x11 or 0x22, got {:#x}",
1433 first_byte
1434 );
1435 assert!(
1437 buf.as_slice().iter().all(|&b| b == first_byte),
1438 "all bytes should be the same value"
1439 );
1440 }));
1441 }
1442
1443 for h in handles {
1444 h.join().unwrap();
1445 }
1446
1447 let read_buf = Arc::new(Buffer::new_temporary(300));
1449 let c = Completion::new_read(read_buf.clone(), |_| None);
1450 let c = file.pread(0, c).unwrap();
1451 wait_completion_ok(io.as_ref(), &c);
1452
1453 assert!(
1455 read_buf.as_slice()[..100].iter().all(|&b| b == 0x22),
1456 "bytes 0-99 should be 0x22"
1457 );
1458 assert!(
1459 read_buf.as_slice()[100..200].iter().all(|&b| b == 0x33),
1460 "bytes 100-199 should be 0x33"
1461 );
1462 assert!(
1463 read_buf.as_slice()[200..300].iter().all(|&b| b == 0x44),
1464 "bytes 200-299 should be 0x44"
1465 );
1466 }
1467
1468 shuttle_io_test!(
1469 pwritev_with_concurrent_reads,
1470 test_pwritev_with_concurrent_reads_impl
1471 );
1472
1473 fn test_concurrent_multifile_access_impl<F: IOFactory>(factory: F) {
1475 let io = factory.create();
1476 let base = factory.temp_dir();
1477
1478 let mut handles = vec![];
1479 const NUM_FILES: usize = 3;
1480
1481 for i in 0..NUM_FILES {
1482 let io = io.clone();
1483 let base = base.clone();
1484 handles.push(thread::spawn(move || {
1485 let path = base.join(format!("file_{}.db", i));
1486 let file = io
1487 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1488 .unwrap();
1489
1490 let data = vec![i as u8; 200];
1492 let buf = Arc::new(Buffer::new(data.clone()));
1493 let c = Completion::new_write(|_| {});
1494 let c = file.pwrite(0, buf, c).unwrap();
1495 wait_completion_ok(io.as_ref(), &c);
1496
1497 let read_buf = Arc::new(Buffer::new_temporary(200));
1499 let c = Completion::new_read(read_buf.clone(), |_| None);
1500 let c = file.pread(0, c).unwrap();
1501 wait_completion_ok(io.as_ref(), &c);
1502
1503 assert_eq!(read_buf.as_slice(), data.as_slice());
1504 }));
1505 }
1506
1507 for h in handles {
1508 h.join().unwrap();
1509 }
1510 }
1511
1512 shuttle_io_test!(
1513 concurrent_multifile_access,
1514 test_concurrent_multifile_access_impl
1515 );
1516
1517 fn test_file_locking_concurrent_impl<F: IOFactory>(factory: F) {
1519 let io = factory.create();
1520 let path = factory.temp_dir().join("test.db");
1521 let file = io
1522 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1523 .unwrap();
1524
1525 let mut handles = vec![];
1526
1527 for _ in 0..3 {
1529 let file = file.clone();
1530 handles.push(thread::spawn(move || {
1531 file.lock_file(true).unwrap();
1533 thread::yield_now();
1534 file.unlock_file().unwrap();
1535
1536 file.lock_file(false).unwrap();
1538 thread::yield_now();
1539 file.unlock_file().unwrap();
1540 }));
1541 }
1542
1543 for h in handles {
1544 h.join().unwrap();
1545 }
1546 }
1547
1548 shuttle_io_test!(file_locking_concurrent, test_file_locking_concurrent_impl);
1549
1550 fn test_read_past_eof_impl<F: IOFactory>(factory: F) {
1552 let io = factory.create();
1553 let path = factory.temp_dir().join("test.db");
1554 let file = io
1555 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1556 .unwrap();
1557
1558 let data = vec![0xAA; 100];
1560 let buf = Arc::new(Buffer::new(data));
1561 let c = Completion::new_write(|_| {});
1562 let c = file.pwrite(0, buf, c).unwrap();
1563 wait_completion_ok(io.as_ref(), &c);
1564
1565 let mut handles = vec![];
1566
1567 for _ in 0..3 {
1569 let file = file.clone();
1570 let io = io.clone();
1571 handles.push(thread::spawn(move || {
1572 let read_buf = Arc::new(Buffer::new_temporary(100));
1573 let bytes_read = Arc::new(AtomicUsize::new(999));
1574 let bytes_read_clone = bytes_read.clone();
1575 let c = Completion::new_read(read_buf, move |res| {
1576 if let Ok((_, n)) = res {
1577 bytes_read_clone.store(n as usize, Ordering::SeqCst);
1578 }
1579 None
1580 });
1581 let c = file.pread(200, c).unwrap(); wait_completion_ok(io.as_ref(), &c);
1584 assert_eq!(bytes_read.load(Ordering::SeqCst), 0);
1585 }));
1586 }
1587
1588 for h in handles {
1589 h.join().unwrap();
1590 }
1591 }
1592
1593 shuttle_io_test!(read_past_eof, test_read_past_eof_impl);
1594
1595 fn test_empty_write_impl<F: IOFactory>(factory: F) {
1597 let io = factory.create();
1598 let path = factory.temp_dir().join("test.db");
1599 let file = io
1600 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1601 .unwrap();
1602
1603 let mut handles = vec![];
1604
1605 for _ in 0..3 {
1606 let file = file.clone();
1607 let io = io.clone();
1608 handles.push(thread::spawn(move || {
1609 let buf = Arc::new(Buffer::new(vec![]));
1611 let c = Completion::new_write(|_| {});
1612 let c = file.pwrite(0, buf, c).unwrap();
1613 wait_completion_ok(io.as_ref(), &c);
1614 }));
1615 }
1616
1617 for h in handles {
1618 h.join().unwrap();
1619 }
1620
1621 assert_eq!(file.size().unwrap(), 0);
1622 }
1623
1624 shuttle_io_test!(empty_write, test_empty_write_impl);
1625
1626 fn test_concurrent_sync_impl<F: IOFactory>(factory: F) {
1628 let io = factory.create();
1629 let path = factory.temp_dir().join("test.db");
1630 let file = io
1631 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1632 .unwrap();
1633
1634 let data = vec![0xFF; 1000];
1636 let buf = Arc::new(Buffer::new(data));
1637 let c = Completion::new_write(|_| {});
1638 let c = file.pwrite(0, buf, c).unwrap();
1639 wait_completion_ok(io.as_ref(), &c);
1640
1641 let mut handles = vec![];
1642
1643 for _ in 0..3 {
1645 let file = file.clone();
1646 let io = io.clone();
1647 handles.push(thread::spawn(move || {
1648 let c = Completion::new_sync(|_| {});
1649 let c = file.sync(c, FileSyncType::Fsync).unwrap();
1650 wait_completion_ok(io.as_ref(), &c);
1651 }));
1652 }
1653
1654 for h in handles {
1655 h.join().unwrap();
1656 }
1657 }
1658
1659 shuttle_io_test!(concurrent_sync, test_concurrent_sync_impl);
1660
1661 fn test_concurrent_open_same_file_impl<F: IOFactory>(factory: F) {
1663 let io = factory.create();
1664 let path = factory.temp_dir().join("shared.db");
1665
1666 let _ = io
1668 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1669 .unwrap();
1670
1671 let mut handles = vec![];
1672
1673 for _ in 0..3 {
1674 let io = io.clone();
1675 let path = path.clone();
1676 handles.push(thread::spawn(move || {
1677 let file = io
1678 .open_file(path.to_str().unwrap(), OpenFlags::None, false)
1679 .unwrap();
1680 thread::yield_now();
1681 let buf = Arc::new(Buffer::new(vec![0xAA]));
1683 let c = Completion::new_write(|_| {});
1684 let c = file.pwrite(0, buf, c).unwrap();
1685 wait_completion_ok(io.as_ref(), &c);
1686 }));
1687 }
1688
1689 for h in handles {
1690 h.join().unwrap();
1691 }
1692 }
1693
1694 shuttle_io_test!(
1695 concurrent_open_same_file,
1696 test_concurrent_open_same_file_impl
1697 );
1698
1699 fn test_file_remove_concurrent_impl<F: IOFactory>(factory: F) {
1701 let io = factory.create();
1702 let base = factory.temp_dir();
1703
1704 for i in 0..3 {
1706 let path = base.join(format!("remove_{}.db", i));
1707 let file = io
1708 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1709 .unwrap();
1710 let buf = Arc::new(Buffer::new(vec![0xFF; 100]));
1711 let c = Completion::new_write(|_| {});
1712 let c = file.pwrite(0, buf, c).unwrap();
1713 wait_completion_ok(io.as_ref(), &c);
1714 }
1715
1716 let mut handles = vec![];
1717
1718 for i in 0..3 {
1720 let io = io.clone();
1721 let base = base.clone();
1722 handles.push(thread::spawn(move || {
1723 let path = base.join(format!("remove_{}.db", i));
1724 io.remove_file(path.to_str().unwrap()).unwrap();
1725 }));
1726 }
1727
1728 for h in handles {
1729 h.join().unwrap();
1730 }
1731 }
1732
1733 shuttle_io_test!(file_remove_concurrent, test_file_remove_concurrent_impl);
1734
1735 fn test_large_write_concurrent_impl<F: IOFactory>(factory: F) {
1737 let io = factory.create();
1738 let path = factory.temp_dir().join("test.db");
1739 let file = io
1740 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1741 .unwrap();
1742
1743 let mut handles = vec![];
1744
1745 for i in 0..2 {
1747 let file = file.clone();
1748 let io = io.clone();
1749 handles.push(thread::spawn(move || {
1750 let data = vec![(i + 1) as u8; 10000];
1752 let buf = Arc::new(Buffer::new(data));
1753 let c = Completion::new_write(|_| {});
1754 let c = file.pwrite((i * 10000) as u64, buf, c).unwrap();
1755 wait_completion_ok(io.as_ref(), &c);
1756 }));
1757 }
1758
1759 for h in handles {
1760 h.join().unwrap();
1761 }
1762
1763 assert_eq!(file.size().unwrap(), 20000);
1764
1765 for i in 0..2 {
1767 let read_buf = Arc::new(Buffer::new_temporary(10000));
1768 let pos = (i * 10000) as u64;
1769 let c = Completion::new_read(read_buf.clone(), |_| None);
1770 let c = file.pread(pos, c).unwrap();
1771 wait_completion_ok(io.as_ref(), &c);
1772
1773 let expected_byte = (i + 1) as u8;
1774 assert!(
1775 read_buf.as_slice().iter().all(|&b| b == expected_byte),
1776 "all bytes at offset {} should be {:#x}",
1777 pos,
1778 expected_byte
1779 );
1780 }
1781 }
1782
1783 shuttle_io_test!(large_write_concurrent, test_large_write_concurrent_impl);
1784
1785 fn test_hole_operations_concurrent_impl<F: IOFactory>(factory: F) {
1788 let io = factory.create();
1789 let path = factory.temp_dir().join("test.db");
1790 let file = io
1791 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1792 .unwrap();
1793
1794 let data = vec![0xFF; 16384];
1796 let buf = Arc::new(Buffer::new(data));
1797 let c = Completion::new_write(|_| {});
1798 let c = file.pwrite(0, buf, c).unwrap();
1799 wait_completion_ok(io.as_ref(), &c);
1800
1801 let mut handles = vec![];
1802
1803 {
1805 let file = file.clone();
1806 handles.push(thread::spawn(move || {
1807 file.punch_hole(4096, 4096).unwrap();
1809 }));
1810 }
1811
1812 {
1814 let file = file.clone();
1815 handles.push(thread::spawn(move || {
1816 let has_hole = file.has_hole(0, 4096).unwrap();
1818 assert!(!has_hole);
1819 let _ = file.has_hole(4096, 4096).unwrap();
1820 let has_hole = file.has_hole(8192, 4096).unwrap();
1821 assert!(!has_hole);
1822 }));
1823 }
1824
1825 for h in handles {
1826 h.join().unwrap();
1827 }
1828 }
1829
1830 #[test]
1832 fn shuttle_hole_operations_concurrent_memory() {
1833 shuttle::check_random(
1834 || test_hole_operations_concurrent_impl(MemoryIOFactory::new()),
1835 1000,
1836 );
1837 }
1838
1839 fn test_partial_read_at_eof_impl<F: IOFactory>(factory: F) {
1841 let io = factory.create();
1842 let path = factory.temp_dir().join("test.db");
1843 let file = io
1844 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1845 .unwrap();
1846
1847 let data = vec![0xAB; 150];
1849 let buf = Arc::new(Buffer::new(data));
1850 let c = Completion::new_write(|_| {});
1851 let c = file.pwrite(0, buf, c).unwrap();
1852 wait_completion_ok(io.as_ref(), &c);
1853
1854 let mut handles = vec![];
1855
1856 for _ in 0..3 {
1859 let file = file.clone();
1860 let io = io.clone();
1861 handles.push(thread::spawn(move || {
1862 let read_buf = Arc::new(Buffer::new_temporary(100));
1863 let bytes_read = Arc::new(AtomicUsize::new(999));
1864 let bytes_read_clone = bytes_read.clone();
1865 let c = Completion::new_read(read_buf.clone(), move |res| {
1866 if let Ok((_, n)) = res {
1867 bytes_read_clone.store(n as usize, Ordering::SeqCst);
1868 }
1869 None
1870 });
1871 let c = file.pread(100, c).unwrap();
1872 wait_completion_ok(io.as_ref(), &c);
1873
1874 assert_eq!(bytes_read.load(Ordering::SeqCst), 50);
1876 assert_eq!(&read_buf.as_slice()[..50], &[0xAB; 50]);
1878 }));
1879 }
1880
1881 for h in handles {
1882 h.join().unwrap();
1883 }
1884 }
1885
1886 shuttle_io_test!(partial_read_at_eof, test_partial_read_at_eof_impl);
1887
1888 fn test_empty_pwritev_impl<F: IOFactory>(factory: F) {
1890 let io = factory.create();
1891 let path = factory.temp_dir().join("test.db");
1892 let file = io
1893 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1894 .unwrap();
1895
1896 let mut handles = vec![];
1897
1898 for _ in 0..3 {
1899 let file = file.clone();
1900 let io = io.clone();
1901 handles.push(thread::spawn(move || {
1902 let bufs: Vec<Arc<Buffer>> = vec![];
1903 let c = Completion::new_write(|_| {});
1904 let c = file.pwritev(0, bufs, c).unwrap();
1905 wait_completion_ok(io.as_ref(), &c);
1906 }));
1907 }
1908
1909 for h in handles {
1910 h.join().unwrap();
1911 }
1912 }
1913
1914 shuttle_io_test!(empty_pwritev, test_empty_pwritev_impl);
1915
1916 fn test_open_nonexistent_without_create_impl<F: IOFactory>(factory: F) {
1918 let io = factory.create();
1919 let base = factory.temp_dir();
1920
1921 let mut handles = vec![];
1922
1923 for i in 0..3 {
1924 let io = io.clone();
1925 let base = base.clone();
1926 handles.push(thread::spawn(move || {
1927 let path = base.join(format!("nonexistent_{}.db", i));
1928 let result = io.open_file(path.to_str().unwrap(), OpenFlags::None, false);
1929 assert!(result.is_err());
1930 }));
1931 }
1932
1933 for h in handles {
1934 h.join().unwrap();
1935 }
1936 }
1937
1938 shuttle_io_test!(
1939 open_nonexistent_without_create,
1940 test_open_nonexistent_without_create_impl
1941 );
1942
1943 fn test_concurrent_overlapping_writes_impl<F: IOFactory>(factory: F) {
1946 let io = factory.create();
1947 let path = factory.temp_dir().join("test.db");
1948 let file = io
1949 .open_file(path.to_str().unwrap(), OpenFlags::Create, false)
1950 .unwrap();
1951
1952 let write_complete = Arc::new(AtomicUsize::new(0));
1953 let mut handles = vec![];
1954
1955 for i in 0..3 {
1957 let file = file.clone();
1958 let io = io.clone();
1959 let write_complete = write_complete.clone();
1960 handles.push(thread::spawn(move || {
1961 let data = vec![(i + 1) as u8; 100];
1962 let buf = Arc::new(Buffer::new(data));
1963 let write_complete_clone = write_complete.clone();
1964 let c = Completion::new_write(move |_| {
1965 write_complete_clone.fetch_add(1, Ordering::SeqCst);
1966 });
1967 let c = file.pwrite(0, buf, c).unwrap();
1968 wait_completion_ok(io.as_ref(), &c);
1969 }));
1970 }
1971
1972 for h in handles {
1973 h.join().unwrap();
1974 }
1975
1976 assert_eq!(write_complete.load(Ordering::SeqCst), 3);
1978
1979 let read_buf = Arc::new(Buffer::new_temporary(100));
1981 let c = Completion::new_read(read_buf.clone(), |_| None);
1982 let c = file.pread(0, c).unwrap();
1983 wait_completion_ok(io.as_ref(), &c);
1984
1985 let first_byte = read_buf.as_slice()[0];
1986 assert!(first_byte == 1 || first_byte == 2 || first_byte == 3);
1987
1988 assert!(read_buf.as_slice().iter().all(|&b| b == first_byte));
1990 }
1991
1992 shuttle_io_test!(
1993 concurrent_overlapping_writes,
1994 test_concurrent_overlapping_writes_impl
1995 );
1996}