Skip to main content

clt_database/io/
mod.rs

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/// Platform-independent file identity, analogous to SQLite's `struct unixFileId`.
68/// On Unix: (st_dev, st_ino). On Windows: (dwVolumeSerialNumber, nFileIndex).
69/// On non-filesystem backends: synthetic hash-based identity.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
71pub struct FileId {
72    pub dev: u64,
73    pub ino: u64,
74}
75
76impl FileId {
77    /// Synthetic identity from a path hash, for backends without real inodes
78    /// (MemoryIO, OPFS, simulators).
79    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/// Return the OS-level file identity for a path.
91#[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/// Controls which sync mechanism to use for durability.
125/// `FullFsync` only has effect on Apple platforms (uses F_FULLFSYNC fcntl).
126/// On other platforms, both variants behave the same (regular fsync).
127#[derive(Debug, Clone, Copy, PartialEq, Eq, AtomicEnum)]
128pub enum FileSyncType {
129    /// Regular fsync - flushes to disk but may not flush disk write cache on macOS.
130    Fsync,
131    /// Full fsync - on macOS uses F_FULLFSYNC to flush disk write cache.
132    /// On other platforms, behaves the same as Fsync.
133    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    /// Sync file data&metadata to disk.
157    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        // naive default implementation can be overridden on backends where it makes sense to
168        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                        // accumulate bytes actually reported by the backend
181                        total_written.fetch_add(n as usize, Ordering::SeqCst);
182                        if outstanding.fetch_sub(1, Ordering::AcqRel) == 1 {
183                            // last one finished
184                            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    /// Optional method implemented by the IO which supports "partial" files (e.g. file with "holes")
201    /// This method is used in sync engine only for now (in partial sync mode) and never used in the core database code
202    ///
203    /// The hole is the contiguous file region which is not allocated by the file-system
204    /// If there is a single byte which is allocated within a given range - method must return false in this case
205    // todo: need to add custom completion type?
206    fn has_hole(&self, _pos: usize, _len: usize) -> Result<bool> {
207        panic!("has_hole is not supported for the given IO implementation")
208    }
209    /// Optional method implemented by the IO which supports "partial" files (e.g. file with "holes")
210    /// This method is used in sync engine only for now (in partial sync mode) and never used in the core database code
211    // todo: need to add custom completion type?
212    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    /// When temp_dir is dropped the folder is deleted
259    /// set to None if tempfile allocated in memory (for example, in case of WASM target)
260    _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        // on WASM in browser we do not support temp files (as we pre-register db files in advance and can't easily create a new one)
280        // so, for now, we use in-memory IO for tempfiles in WASM
281        #[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    /// Creates a TempFile respecting the temp_store setting.
295    /// When temp_store is Memory, uses in-memory storage.
296    /// When temp_store is Default or File, uses file-based storage when
297    /// available. In `no-fs` builds, temp storage always falls back to memory.
298    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                // Fall through to file-based for Default and File modes
324                Self::new(io)
325            }
326        }
327        #[cfg(target_family = "wasm")]
328        {
329            // WASM always uses memory, ignore temp_store setting
330            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
347// OpenFlags is a newtype over i32, which is inherently Send+Sync.
348// The assertion below verifies this at compile time.
349crate::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    // remove_file is used in the sync-engine
374    fn remove_file(&self, path: &str) -> Result<()>;
375
376    /// Whether this IO backend can back host-filesystem shared WAL coordination.
377    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    /// Drive the IO backend until each completion in `completions` is
391    /// `finished()`. Used after `cancel()` (so cancelled ops actually
392    /// release their buffers before the caller returns) and after a
393    /// single `pwrite`/`pwritev`/`sync` that the caller wants to await
394    /// synchronously.
395    ///
396    /// Unlike a global "drain the ring" barrier, this only waits on the
397    /// completions the caller passes in. Other threads can keep
398    /// submitting concurrently — their work doesn't extend or interfere
399    /// with this call. `Completion::finished()` is monotonic
400    /// (`OnceLock`-backed), so the loop will terminate as soon as every
401    /// caller-owned completion has had its CQE processed.
402    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    /// Fill `dest` with random data.
426    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    /// Yield the current thread to the scheduler.
441    /// Used for backoff in contended lock acquisition.
442    fn yield_now(&self) {
443        crate::thread::yield_now();
444    }
445
446    /// Sleep for the specified duration.
447    /// Used for progressive backoff in contended lock acquisition.
448    fn sleep(&self, duration: std::time::Duration) {
449        crate::thread::sleep(duration);
450    }
451
452    /// Return the file identity for the given path.
453    /// Default uses OS-level metadata; non-filesystem backends override
454    /// with synthetic hash-based identity.
455    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
464/// Batches multiple vectored writes for submission.
465pub 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    /// Total bytes across all operations.
491    #[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    /// Submit all writes. Returns completions caller must wait on.
500    #[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    /// Returns the file for fsync after writes complete.
520    #[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    /// A heap buffer with a logical start offset: only `data[start..]` is
606    /// exposed via [`Buffer::as_slice`] / [`Buffer::len`]. Used to skip a
607    /// pre-allocated prefix without shifting bytes in memory before I/O.
608    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                    // take ownership of the buffer by swapping it with a dummy
641                    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    /// Wraps `data` so that only bytes `[start..]` are visible via
665    /// [`Buffer::as_slice`] / [`Buffer::len`]. The skipped prefix lives in
666    /// memory but is never read by the I/O layer — useful when a caller
667    /// has pre-allocated optional framing room at the front of a buffer
668    /// and wants to elide it on a particular write without a memmove.
669    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    /// Returns the index of the underlying `Arena` if it was registered with
682    /// io_uring. Only for use with `UringIO` backend.
683    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                // SAFETY: The buffer is guaranteed to be valid for the lifetime of the slice
721                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                // SAFETY: `start` was bounds-checked at construction; the buffer
726                // is valid for the lifetime of the returned slice.
727                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    /// thread local cache to re-use temporary buffers to prevent churn when pool overflows
771    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
805/// A cache for temporary or any additional `Buffer` allocations beyond
806/// what the `BufferPool` has room for, or for use before the pool is
807/// fully initialized.
808pub(crate) struct TempBufferCache {
809    /// The `[Database::page_size]` at the time the cache is initiated.
810    page_size: usize,
811    /// Cache of buffers of size `self.page_size`.
812    page_buffers: Vec<BufferData>,
813    /// Cache of buffers of size `self.page_size` + WAL_FRAME_HEADER_SIZE.
814    wal_frame_buffers: Vec<BufferData>,
815    /// Maximum number of buffers that will live in each cache.
816    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    /// If the `[Database::page_size]` is set, any temporary buffers that might
832    /// exist prior need to be cleared and new `page_size` needs to be saved.
833    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// Runtime-registrable Rust IO backends, resolved by `Database::io_for_vfs`.
861#[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
867/// Register a named Rust IO backend.
868///
869/// Once registered, it can be used via [`Database::io_for_vfs`] or through
870/// any language binding's `vfs=` parameter (Go DSN, Python kwarg, etc.).
871///
872/// Re-registering the same name replaces the previous backend. Registered
873/// names take precedence over C VFS extensions and built-in backends
874/// (`"memory"`, `"syscall"`, `"io_uring"`), so registering a built-in name
875/// will shadow the default implementation.
876///
877/// # Errors
878///
879/// Returns [`LimboError::InvalidArgument`] if `name` is empty.
880pub 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
893/// Remove a registered Rust IO backend by name.
894///
895/// Returns `true` if an entry was removed, `false` if the name was not found.
896pub fn unregister_io(name: &str) -> bool {
897    IO_REGISTRY.lock().remove(name).is_some()
898}
899
900/// Look up a registered Rust IO backend by name.
901pub fn get_registered_io(name: &str) -> Option<Arc<dyn IO>> {
902    IO_REGISTRY.lock().get(name).cloned()
903}
904
905/// List all registered Rust IO backend names.
906pub 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    /// Factory trait for creating IO implementations in tests.
976    /// Allows the same test logic to run against different IO backends.
977    trait IOFactory: Send + Sync + 'static {
978        fn create(&self) -> Arc<dyn IO>;
979        /// Returns a unique temp directory path for this factory instance.
980        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 to generate shuttle tests for all IO implementations.
1094    /// Creates a test for MemoryIO, and conditionally for PlatformIO and UringIO.
1095    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    /// Helper to wait for a completion synchronously and assert it succeeded.
1126    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    /// Helper to wait for a completion synchronously without asserting success.
1135    #[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    /// Test concurrent file creation from multiple threads.
1142    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    /// Test concurrent writes to different offsets in the same file.
1168    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        // Verify file size accounts for all writes
1197        let expected_size = (NUM_THREADS * 100) as u64;
1198        assert_eq!(file.size().unwrap(), expected_size);
1199
1200        // Read back and verify each segment contains correct data
1201        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    /// Test concurrent reads and writes to the same file.
1224    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        // First write some initial data
1232        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        // Spawn readers
1241        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                // All bytes read should be 0xAA (initial data at offset 0)
1251                assert!(
1252                    read_buf.as_slice().iter().all(|&b| b == 0xAA),
1253                    "read buffer should contain initial data 0xAA"
1254                );
1255            }));
1256        }
1257
1258        // Spawn a writer
1259        {
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        // Verify the write at offset 500 succeeded
1276        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    /// Test that completion callbacks are invoked correctly under concurrency.
1289    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    /// Test concurrent truncate operations.
1330    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        // Write initial data
1338        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        // Spawn threads that truncate to different sizes
1347        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        // Size should be one of the truncate values
1363        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    /// Test pwritev with concurrent reads.
1370    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        // Write initial data so reads have something to return
1378        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        // Spawn a pwritev thread
1387        {
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        // Spawn reader threads
1403        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                // Data should be either initial (0x11) or from pwritev (0x22)
1413                // depending on race ordering
1414                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                // All 100 bytes should be consistent
1421                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        // After all threads complete, verify pwritev data is present
1433        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        // Should have 0x22 for first 100, 0x33 for next 100, 0x44 for last 100
1439        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    /// Test concurrent access to multiple files.
1459    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                // Write to file
1476                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                // Read back and verify
1483                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    /// Test file locking under concurrent access.
1503    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        // Multiple threads try to lock/unlock
1513        for _ in 0..3 {
1514            let file = file.clone();
1515            handles.push(thread::spawn(move || {
1516                // Exclusive lock
1517                file.lock_file(true).unwrap();
1518                thread::yield_now();
1519                file.unlock_file().unwrap();
1520
1521                // Shared lock
1522                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    /// Test reading past end of file returns zero bytes.
1536    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        // Write 100 bytes
1544        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        // Multiple threads try to read past EOF
1553        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(); // Past EOF
1567                                                     // Reading past EOF succeeds with 0 bytes read
1568                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    /// Test empty write operations.
1581    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                // Empty buffer write
1595                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    /// Test sync operations under concurrency.
1612    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        // Write some data first
1620        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        // Multiple sync calls concurrently
1629        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    /// Test concurrent open of the same file returns same file instance.
1647    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        // Create file first
1652        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                // Write a byte to prove we got a valid file
1667                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    /// Test file removal while concurrent access.
1685    fn test_file_remove_concurrent_impl<F: IOFactory>(factory: F) {
1686        let io = factory.create();
1687        let base = factory.temp_dir();
1688
1689        // Create multiple files
1690        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        // Remove files concurrently
1704        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    /// Test write spanning multiple internal pages.
1721    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        // Multiple threads write large buffers that span multiple pages
1731        for i in 0..2 {
1732            let file = file.clone();
1733            let io = io.clone();
1734            handles.push(thread::spawn(move || {
1735                // Write 10000 bytes (spans multiple 4096-byte pages)
1736                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        // Read back and verify each segment contains correct data
1751        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    /// Test has_hole and punch_hole under concurrency.
1771    /// Note: Only runs on MemoryIO as hole operations are not supported on all backends.
1772    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        // Write data spanning multiple pages (at least 3 pages = 12288 bytes)
1780        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        // Thread 1: punch holes
1789        {
1790            let file = file.clone();
1791            handles.push(thread::spawn(move || {
1792                // Punch hole in middle page (page-aligned)
1793                file.punch_hole(4096, 4096).unwrap();
1794            }));
1795        }
1796
1797        // Thread 2: check for holes
1798        {
1799            let file = file.clone();
1800            handles.push(thread::spawn(move || {
1801                // Check various regions
1802                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    // hole_operations only runs on MemoryIO since not all backends support holes
1816    #[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    /// Test that partial reads work correctly at EOF boundary.
1825    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        // Write exactly 150 bytes
1833        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        // Multiple threads try to read 100 bytes starting at offset 100
1842        // Should only get 50 bytes back
1843        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                // Should read exactly 50 bytes (150 - 100)
1860                assert_eq!(bytes_read.load(Ordering::SeqCst), 50);
1861                // Verify the bytes read are correct
1862                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    /// Test empty pwritev.
1874    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    /// Test error case: opening non-existent file without Create flag.
1902    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    /// Test concurrent writes to overlapping regions.
1929    /// This tests that the final state is consistent (one of the writes wins).
1930    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        // Multiple threads write to the same offset
1941        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        // All writes should have completed
1962        assert_eq!(write_complete.load(Ordering::SeqCst), 3);
1963
1964        // Read back and verify we got one of the written values
1965        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        // All 100 bytes should be the same value
1974        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}