1#![allow(clippy::arc_with_non_send_sync)]
2
3use super::{
4 common, Completion, CompletionInner, File, OpenFlags, SharedWalLockKind, SharedWalMappedRegion,
5 IO,
6};
7use crate::error::io_error;
8use crate::io::clock::{Clock, DefaultClock, MonotonicInstant, WallClockInstant};
9use crate::io::unix::{
10 unix_shared_wal_lock_byte, unix_shared_wal_map, unix_shared_wal_unlock_byte,
11};
12use crate::storage::wal::CKPT_BATCH_PAGES;
13use crate::sync::Mutex;
14use crate::turso_assert;
15use crate::{CompletionError, LimboError, Result};
16use rustix::fs::{self, FlockOperation, OFlags};
17use std::ptr::NonNull;
18use std::{
19 collections::{HashMap, VecDeque},
20 io::ErrorKind,
21 ops::Deref,
22 os::{fd::AsFd, unix::io::AsRawFd},
23 sync::Arc,
24};
25use tracing::{debug, trace, warn};
26
27const ENTRIES: u32 = 512;
29
30const SQPOLL_IDLE: u32 = 1000;
34
35const IOVEC_POOL_SIZE: usize = 64;
37
38const MAX_IOVEC_ENTRIES: usize = CKPT_BATCH_PAGES;
41
42const MAX_WAIT: usize = 4;
46
47const ARENA_COUNT: usize = 2;
49
50const CANCEL_TAG: u64 = 1;
52
53struct UringCapabilities {
56 ftruncate: bool,
57}
58
59pub struct UringIO {
60 ring: Arc<io_uring::IoUring>,
67 state: Arc<Mutex<RingState>>,
73 wait_lock: Arc<Mutex<()>>,
82 caps: Arc<UringCapabilities>,
83}
84
85unsafe impl Send for UringIO {}
86unsafe impl Sync for UringIO {}
87crate::assert::assert_send_sync!(UringIO);
88
89struct RingState {
90 pending_ops: usize,
91 writev_states: HashMap<u64, WritevState>,
92 overflow: VecDeque<io_uring::squeue::Entry>,
93 iov_pool: IovecPool,
94 free_arenas: [Option<(NonNull<u8>, usize)>; ARENA_COUNT],
95}
96
97impl RingState {
98 fn empty(&self) -> bool {
99 self.pending_ops == 0 && self.overflow.is_empty()
100 }
101
102 unsafe fn flush_overflow(&mut self, ring: &io_uring::IoUring) {
105 if self.overflow.is_empty() {
106 return;
107 }
108 let mut sq = ring.submission_shared();
109 while !self.overflow.is_empty() {
110 if sq.is_full() {
111 break;
112 }
113 let entry = self.overflow.pop_front().expect("checked not empty");
114 if sq.push(&entry).is_err() {
115 self.overflow.push_front(entry);
116 break;
117 }
118 self.pending_ops += 1;
119 }
120 }
121
122 unsafe fn submit_entry(&mut self, ring: &io_uring::IoUring, entry: &io_uring::squeue::Entry) {
125 trace!("submit_entry({:?})", entry);
126 self.flush_overflow(ring);
127 let pushed = {
128 let mut sq = ring.submission_shared();
129 sq.push(entry).is_ok()
130 };
131 if pushed {
132 self.pending_ops += 1;
133 return;
134 }
135 self.overflow.push_back(entry.clone());
138 let _ = ring.submit();
139 }
140
141 unsafe fn submit_cancel_urgent(
143 &mut self,
144 ring: &io_uring::IoUring,
145 entry: &io_uring::squeue::Entry,
146 ) -> Result<()> {
147 let pushed = {
148 let mut sq = ring.submission_shared();
149 sq.push(entry).is_ok()
150 };
151 if pushed {
152 self.pending_ops += 1;
153 return Ok(());
154 }
155 self.overflow.push_front(entry.clone());
156 ring.submit().map_err(|e| io_error(e, "io_uring_submit"))?;
157 Ok(())
158 }
159}
160
161struct IovecPool {
163 pool: Vec<Box<[libc::iovec; MAX_IOVEC_ENTRIES]>>,
164}
165
166impl IovecPool {
167 fn new() -> Self {
168 let pool = (0..IOVEC_POOL_SIZE)
169 .map(|_| {
170 Box::new(
171 [libc::iovec {
172 iov_base: std::ptr::null_mut(),
173 iov_len: 0,
174 }; MAX_IOVEC_ENTRIES],
175 )
176 })
177 .collect();
178 Self { pool }
179 }
180
181 #[inline(always)]
182 fn acquire(&mut self) -> Option<Box<[libc::iovec; MAX_IOVEC_ENTRIES]>> {
183 self.pool.pop()
184 }
185
186 #[inline(always)]
187 fn release(&mut self, iovec: Box<[libc::iovec; MAX_IOVEC_ENTRIES]>) {
188 if self.pool.len() < IOVEC_POOL_SIZE {
189 self.pool.push(iovec);
190 }
191 }
192}
193
194impl UringIO {
195 pub fn new() -> Result<Self> {
196 let ring = match io_uring::IoUring::builder()
197 .setup_sqpoll(SQPOLL_IDLE)
198 .build(ENTRIES)
199 {
200 Ok(ring) => ring,
201 Err(_) => io_uring::IoUring::new(ENTRIES).map_err(|e| io_error(e, "io_uring_setup"))?,
202 };
203 ring.submitter()
207 .register_buffers_sparse(ARENA_COUNT as u32)
208 .map_err(|e| io_error(e, "register_buffers"))?;
209 let mut probe = io_uring::register::Probe::new();
211 let caps = if ring.submitter().register_probe(&mut probe).is_ok() {
212 UringCapabilities {
213 ftruncate: probe.is_supported(io_uring::opcode::Ftruncate::CODE),
214 }
215 } else {
216 UringCapabilities { ftruncate: false }
217 };
218 if !caps.ftruncate {
219 warn!("io_uring: IORING_OP_FTRUNCATE not supported by kernel, using POSIX fallback");
220 }
221 let state = RingState {
222 overflow: VecDeque::new(),
223 pending_ops: 0,
224 writev_states: HashMap::default(),
225 iov_pool: IovecPool::new(),
226 free_arenas: [const { None }; ARENA_COUNT],
227 };
228 debug!("Using IO backend 'io-uring'");
229 Ok(Self {
230 ring: Arc::new(ring),
231 state: Arc::new(Mutex::new(state)),
232 wait_lock: Arc::new(Mutex::new(())),
233 caps: Arc::new(caps),
234 })
235 }
236}
237
238struct WritevState {
241 file_id: io_uring::types::Fd,
243 file_pos: u64,
245 current_buffer_idx: usize,
247 current_buffer_offset: usize,
249 total_written: usize,
251 total_len: usize,
253 bufs: Vec<Arc<crate::Buffer>>,
255 last_iov_allocation: Option<Box<[libc::iovec; MAX_IOVEC_ENTRIES]>>,
257}
258
259impl WritevState {
260 fn new(file: &UringFile, pos: u64, bufs: Vec<Arc<crate::Buffer>>) -> Self {
261 let file_id = file.file.as_raw_fd();
262 let total_len = bufs.iter().map(|b| b.len()).sum();
263 Self {
264 file_id: io_uring::types::Fd(file_id),
265 file_pos: pos,
266 current_buffer_idx: 0,
267 current_buffer_offset: 0,
268 total_written: 0,
269 bufs,
270 last_iov_allocation: None,
271 total_len,
272 }
273 }
274
275 #[inline(always)]
276 fn remaining(&self) -> usize {
277 self.total_len - self.total_written
278 }
279
280 #[inline(always)]
282 fn advance(&mut self, written: u64) {
283 let mut remaining = written;
284 while remaining > 0 {
285 let current_buf_len = self.bufs[self.current_buffer_idx].len();
286 let left = current_buf_len - self.current_buffer_offset;
287 if remaining < left as u64 {
288 self.current_buffer_offset += remaining as usize;
289 self.file_pos += remaining;
290 remaining = 0;
291 } else {
292 remaining -= left as u64;
293 self.file_pos += left as u64;
294 self.current_buffer_idx += 1;
295 self.current_buffer_offset = 0;
296 }
297 }
298 self.total_written += written as usize;
299 }
300
301 #[inline(always)]
302 fn free_last_iov(&mut self, pool: &mut IovecPool) {
304 if let Some(allocation) = self.last_iov_allocation.take() {
305 pool.release(allocation);
306 }
307 }
308}
309
310impl RingState {
311 #[cfg(debug_assertions)]
312 fn debug_check_fixed(&self, idx: u32, ptr: *const u8, len: usize) {
313 let (base, blen) = self.free_arenas[idx as usize].expect("slot not registered");
314 let start = base.as_ptr() as usize;
315 let end = start + blen;
316 let p = ptr as usize;
317 turso_assert!(
318 p >= start && p + len <= end,
319 "Fixed operation, pointer out of registered range"
320 );
321 }
322
323 unsafe fn submit_writev(&mut self, ring: &io_uring::IoUring, key: u64, mut st: WritevState) {
326 st.free_last_iov(&mut self.iov_pool);
327
328 let mut iov_allocation = self.iov_pool.acquire().unwrap_or_else(|| {
329 Box::new(
330 [libc::iovec {
331 iov_base: std::ptr::null_mut(),
332 iov_len: 0,
333 }; MAX_IOVEC_ENTRIES],
334 )
335 });
336
337 let mut iov_count = 0;
338 let mut last_end: Option<(*const u8, usize)> = None;
339
340 for (idx, buffer) in st.bufs.iter().enumerate().skip(st.current_buffer_idx) {
341 let mut ptr = buffer.as_ptr();
342 let mut len = buffer.len();
343 if idx == st.current_buffer_idx && st.current_buffer_offset != 0 {
344 turso_assert!(
345 st.current_buffer_offset <= len,
346 "writev state offset out of bounds"
347 );
348 ptr = ptr.add(st.current_buffer_offset);
349 len -= st.current_buffer_offset;
350 }
351 if let Some((last_ptr, last_len)) = last_end {
352 if last_ptr.add(last_len) == ptr {
353 iov_allocation[iov_count - 1].iov_len += len;
354 last_end = Some((last_ptr, last_len + len));
355 continue;
356 }
357 }
358 iov_allocation[iov_count] = libc::iovec {
359 iov_base: ptr as *mut _,
360 iov_len: len,
361 };
362 last_end = Some((ptr, len));
363 iov_count += 1;
364 if iov_count >= MAX_IOVEC_ENTRIES {
365 break;
366 }
367 }
368
369 let ptr = iov_allocation.as_ptr() as *mut libc::iovec;
370 st.last_iov_allocation = Some(iov_allocation);
371 let entry = io_uring::opcode::Writev::new(st.file_id, ptr, iov_count as u32)
372 .offset(st.file_pos)
373 .build()
374 .user_data(key);
375 self.writev_states.insert(key, st);
376 self.submit_entry(ring, &entry);
377 }
378
379 unsafe fn handle_writev_completion(
381 &mut self,
382 ring: &io_uring::IoUring,
383 mut state: WritevState,
384 user_data: u64,
385 result: i32,
386 ) {
387 if result < 0 {
388 let err = std::io::Error::from_raw_os_error(-result);
389 tracing::error!("writev failed (user_data: {}): {}", user_data, err);
390 state.free_last_iov(&mut self.iov_pool);
391 completion_from_key(user_data).error(CompletionError::IOError(err.kind(), "pwritev"));
392 return;
393 }
394
395 let written = result;
396
397 if written == 0 && state.remaining() > 0 {
398 state.free_last_iov(&mut self.iov_pool);
399 completion_from_key(user_data).error(CompletionError::ShortWrite);
400 return;
401 }
402 state.advance(written as u64);
403
404 match state.remaining() {
405 0 => {
406 tracing::debug!(
407 "writev operation completed: wrote {} bytes",
408 state.total_written
409 );
410 state.free_last_iov(&mut self.iov_pool);
411 completion_from_key(user_data).complete(state.total_written as i32);
412 }
413 remaining => {
414 tracing::trace!(
415 "resubmitting writev operation for user_data {}: wrote {} bytes, remaining {}",
416 user_data,
417 written,
418 remaining
419 );
420 self.submit_writev(ring, user_data, state);
421 wake_user_data(user_data);
427 }
428 }
429 }
430}
431
432impl IO for UringIO {
433 fn supports_shared_wal_coordination(&self) -> bool {
434 true
435 }
436
437 fn open_file(&self, path: &str, flags: OpenFlags, direct: bool) -> Result<Arc<dyn File>> {
438 trace!("open_file(path = {})", path);
439 let mut file = std::fs::File::options();
440 file.read(true);
441
442 if !flags.contains(OpenFlags::ReadOnly) {
443 file.write(true);
444 file.create(flags.contains(OpenFlags::Create));
445 }
446
447 let file = file.open(path).map_err(|e| io_error(e, "open"))?;
448 let fd = file.as_fd();
451 if direct {
452 match fs::fcntl_setfl(fd, OFlags::DIRECT) {
453 Ok(_) => {}
454 Err(error) => debug!("Error {error:?} returned when setting O_DIRECT flag to read file. The performance of the system may be affected"),
455 }
456 }
457 let uring_file = Arc::new(UringFile {
458 ring: self.ring.clone(),
459 state: self.state.clone(),
460 caps: self.caps.clone(),
461 file,
462 });
463 if std::env::var(common::ENV_DISABLE_FILE_LOCK).is_err()
464 && !flags.intersects(OpenFlags::ReadOnly | OpenFlags::NoLock)
465 {
466 uring_file.lock_file(true)?;
467 }
468 Ok(uring_file)
469 }
470
471 fn remove_file(&self, path: &str) -> Result<()> {
472 std::fs::remove_file(path).map_err(|e| io_error(e, "remove_file"))?;
473 Ok(())
474 }
475
476 fn cancel(&self, completions: &[Completion]) -> Result<()> {
477 let mut state = self.state.lock();
478 for c in completions {
479 c.abort();
480 let e = io_uring::opcode::AsyncCancel::new(Arc::as_ptr(c.get_inner()) as u64)
482 .build()
483 .user_data(CANCEL_TAG);
484 unsafe { state.submit_cancel_urgent(&self.ring, &e)? };
486 }
487 Ok(())
488 }
489
490 fn step(&self) -> Result<()> {
514 let Some(_wait_guard) = self.wait_lock.try_lock() else {
520 return Ok(());
521 };
522
523 loop {
528 let pending = {
529 let mut state = self.state.lock();
530 unsafe { state.flush_overflow(&self.ring) };
532 if state.empty() {
533 return Ok(());
534 }
535 state.pending_ops
536 };
537
538 let wants = std::cmp::min(pending, MAX_WAIT);
539 tracing::trace!("submit_and_wait for {wants} pending operations to complete");
540 self.ring
541 .submit_and_wait(wants)
542 .map_err(|e| io_error(e, "io_uring_submit_and_wait"))?;
543
544 self.drain_cq()?;
546 }
547 }
548
549 fn register_fixed_buffer(&self, ptr: std::ptr::NonNull<u8>, len: usize) -> Result<u32> {
550 turso_assert!(
551 len % 512 == 0,
552 "fixed buffer length must be logical block aligned"
553 );
554 let mut state = self.state.lock();
555 let slot =
556 state.free_arenas.iter().position(|e| e.is_none()).ok_or({
557 crate::error::CompletionError::UringIOError("no free fixed buffer slots")
558 })?;
559 unsafe {
560 self.ring
561 .submitter()
562 .register_buffers_update(
563 slot as u32,
564 &[libc::iovec {
565 iov_base: ptr.as_ptr() as *mut libc::c_void,
566 iov_len: len,
567 }],
568 None,
569 )
570 .map_err(|e| io_error(e, "register_buffers_update"))?
571 };
572 state.free_arenas[slot] = Some((ptr, len));
573 Ok(slot as u32)
574 }
575}
576
577impl UringIO {
578 fn drain_cq(&self) -> Result<bool> {
581 let mut state = self.state.lock();
582 let mut drained_any = false;
583 loop {
584 let mut cq = unsafe { self.ring.completion_shared() };
586 let Some(cqe) = cq.next() else {
587 return Ok(drained_any);
588 };
589 drained_any = true;
590 state.pending_ops -= 1;
591 let user_data = cqe.user_data();
592 if user_data == CANCEL_TAG {
593 continue;
594 }
595 let result = cqe.result();
596 turso_assert!(
597 user_data != 0,
598 "user_data must not be zero, we dont submit linked timeouts that would cause this"
599 );
600 if let Some(wstate) = state.writev_states.remove(&user_data) {
601 drop(cq);
602 unsafe { state.handle_writev_completion(&self.ring, wstate, user_data, result) };
604 continue;
605 }
606 if result < 0 {
607 let errno = -result;
608 let err = std::io::Error::from_raw_os_error(errno);
609 completion_from_key(user_data)
610 .error(CompletionError::IOError(err.kind(), "io_uring_cqe"));
611 } else {
612 completion_from_key(user_data).complete(result);
613 }
614 }
615 }
616}
617
618impl Clock for UringIO {
619 fn current_time_monotonic(&self) -> MonotonicInstant {
620 DefaultClock.current_time_monotonic()
621 }
622
623 fn current_time_wall_clock(&self) -> WallClockInstant {
624 DefaultClock.current_time_wall_clock()
625 }
626}
627
628#[inline(always)]
629fn get_key(c: Completion) -> u64 {
632 Arc::into_raw(c.get_inner().clone()) as u64
633}
634
635#[inline(always)]
636fn completion_from_key(key: u64) -> Completion {
638 let c_inner = unsafe { Arc::from_raw(key as *const CompletionInner) };
639 Completion {
640 inner: Some(c_inner),
641 }
642}
643
644fn wake_user_data(key: u64) {
650 let ptr = key as *const CompletionInner;
651 let kernel_ref = unsafe { Arc::from_raw(ptr) };
655 let cloned = kernel_ref.clone();
656 let _ = Arc::into_raw(kernel_ref);
657 Completion {
658 inner: Some(cloned),
659 }
660 .wake_progress();
661}
662
663pub struct UringFile {
664 ring: Arc<io_uring::IoUring>,
665 state: Arc<Mutex<RingState>>,
666 caps: Arc<UringCapabilities>,
667 file: std::fs::File,
668}
669
670impl Deref for UringFile {
671 type Target = std::fs::File;
672 fn deref(&self) -> &Self::Target {
673 &self.file
674 }
675}
676
677unsafe impl Send for UringFile {}
678unsafe impl Sync for UringFile {}
679crate::assert::assert_send_sync!(UringFile);
680
681impl File for UringFile {
682 fn lock_file(&self, exclusive: bool) -> Result<()> {
683 let fd = self.file.as_fd();
684 fs::fcntl_lock(
687 fd,
688 if exclusive {
689 FlockOperation::NonBlockingLockExclusive
690 } else {
691 FlockOperation::NonBlockingLockShared
692 },
693 )
694 .map_err(|e| {
695 let io_error = std::io::Error::from(e);
696 let message = match io_error.kind() {
697 ErrorKind::WouldBlock => {
698 "Failed locking file. File is locked by another process".to_string()
699 }
700 _ => format!("Failed locking file, {io_error}"),
701 };
702 LimboError::LockingError(message)
703 })?;
704
705 Ok(())
706 }
707
708 fn unlock_file(&self) -> Result<()> {
709 let fd = self.file.as_fd();
710 fs::fcntl_lock(fd, FlockOperation::NonBlockingUnlock).map_err(|e| {
711 LimboError::LockingError(format!(
712 "Failed to release file lock: {}",
713 std::io::Error::from(e)
714 ))
715 })?;
716 Ok(())
717 }
718
719 fn pread(&self, pos: u64, c: Completion) -> Result<Completion> {
720 let r = c.as_read();
721 let read_e = {
722 let buf = r.buf();
723 let ptr = buf.as_mut_ptr();
724 let fd = io_uring::types::Fd(self.file.as_raw_fd());
725 let len = buf.len();
726 if let Some(idx) = buf.fixed_id() {
727 trace!(
728 "pread_fixed(pos = {}, length = {}, idx = {})",
729 pos,
730 len,
731 idx
732 );
733 #[cfg(debug_assertions)]
734 {
735 self.state.lock().debug_check_fixed(idx, ptr, len);
736 }
737 io_uring::opcode::ReadFixed::new(fd, ptr, len as u32, idx as u16)
738 .offset(pos)
739 .build()
740 .user_data(get_key(c.clone()))
741 } else {
742 trace!("pread(pos = {}, length = {})", pos, len);
743 io_uring::opcode::Read::new(fd, buf.as_mut_ptr(), len as u32)
744 .offset(pos)
745 .build()
746 .user_data(get_key(c.clone()))
747 }
748 };
749 let mut state = self.state.lock();
750 unsafe { state.submit_entry(&self.ring, &read_e) };
752 Ok(c)
753 }
754
755 fn pwrite(&self, pos: u64, buffer: Arc<crate::Buffer>, c: Completion) -> Result<Completion> {
756 let write = {
757 let ptr = buffer.as_ptr();
758 let len = buffer.len();
759 let fd = io_uring::types::Fd(self.file.as_raw_fd());
760 if let Some(idx) = buffer.fixed_id() {
761 trace!(
762 "pwrite_fixed(pos = {}, length = {}, idx= {})",
763 pos,
764 len,
765 idx
766 );
767 #[cfg(debug_assertions)]
768 {
769 self.state.lock().debug_check_fixed(idx, ptr, len);
770 }
771 io_uring::opcode::WriteFixed::new(fd, ptr, len as u32, idx as u16)
772 .offset(pos)
773 .build()
774 .user_data(get_key(c.clone()))
775 } else {
776 trace!("pwrite(pos = {}, length = {})", pos, buffer.len());
777 io_uring::opcode::Write::new(fd, ptr, len as u32)
778 .offset(pos)
779 .build()
780 .user_data(get_key(c.clone()))
781 }
782 };
783
784 c.keep_write_buffer_alive(buffer);
788 let mut state = self.state.lock();
789 unsafe { state.submit_entry(&self.ring, &write) };
791 Ok(c)
792 }
793
794 fn sync(&self, c: Completion, _sync_type: crate::io::FileSyncType) -> Result<Completion> {
795 trace!("sync()");
796 let fd = io_uring::types::Fd(self.file.as_raw_fd());
797 let sync = io_uring::opcode::Fsync::new(fd)
798 .build()
799 .user_data(get_key(c.clone()));
800 let mut state = self.state.lock();
801 unsafe { state.submit_entry(&self.ring, &sync) };
803 Ok(c)
804 }
805
806 fn pwritev(
807 &self,
808 pos: u64,
809 bufs: Vec<Arc<crate::Buffer>>,
810 c: Completion,
811 ) -> Result<Completion> {
812 tracing::trace!("pwritev(pos = {}, bufs.len() = {})", pos, bufs.len());
813
814 let wstate = WritevState::new(self, pos, bufs);
815 let mut state = self.state.lock();
816 unsafe { state.submit_writev(&self.ring, get_key(c.clone()), wstate) };
818 Ok(c)
819 }
820
821 fn size(&self) -> Result<u64> {
822 Ok(self
823 .file
824 .metadata()
825 .map_err(|e| io_error(e, "metadata"))?
826 .len())
827 }
828
829 fn truncate(&self, len: u64, c: Completion) -> Result<Completion> {
830 let fd = io_uring::types::Fd(self.file.as_raw_fd());
831 if self.caps.ftruncate {
832 let truncate = io_uring::opcode::Ftruncate::new(fd, len)
833 .build()
834 .user_data(get_key(c.clone()));
835 let mut state = self.state.lock();
836 unsafe { state.submit_entry(&self.ring, &truncate) };
838 Ok(c)
839 } else {
840 let result = self.file.set_len(len);
841 match result {
842 Ok(()) => {
843 trace!("file truncated to len=({})", len);
844 c.complete(0);
845 Ok(c)
846 }
847 Err(e) => Err(io_error(e, "truncate")),
848 }
849 }
850 }
851
852 fn shared_wal_lock_byte(
853 &self,
854 offset: u64,
855 exclusive: bool,
856 kind: SharedWalLockKind,
857 ) -> Result<()> {
858 unix_shared_wal_lock_byte(self.file.as_raw_fd(), offset, exclusive, true, kind).map(|_| ())
859 }
860
861 fn shared_wal_try_lock_byte(
862 &self,
863 offset: u64,
864 exclusive: bool,
865 kind: SharedWalLockKind,
866 ) -> Result<bool> {
867 unix_shared_wal_lock_byte(self.file.as_raw_fd(), offset, exclusive, false, kind)
868 }
869
870 fn shared_wal_unlock_byte(&self, offset: u64, kind: SharedWalLockKind) -> Result<()> {
871 unix_shared_wal_unlock_byte(self.file.as_raw_fd(), offset, kind)
872 }
873
874 fn shared_wal_set_len(&self, len: u64) -> Result<()> {
875 self.file
876 .set_len(len)
877 .map_err(|err| io_error(err, "resize shared WAL coordination file"))
878 }
879
880 fn shared_wal_map(&self, offset: u64, len: usize) -> Result<Box<dyn SharedWalMappedRegion>> {
881 unix_shared_wal_map(offset, len, self.file.as_raw_fd())
882 }
883}
884
885impl Drop for UringFile {
886 fn drop(&mut self) {
887 self.unlock_file().expect("Failed to unlock file");
888 }
889}
890
891#[cfg(clt_turso_tests)]
892mod tests {
893 use super::*;
894 use crate::io::common;
895
896 #[test]
897 fn test_multiple_processes_cannot_open_file() {
898 common::tests::test_multiple_processes_cannot_open_file(UringIO::new);
899 }
900}