Skip to main content

clt_database/alloc/
backend.rs

1use std::{fmt, ptr::NonNull, sync::OnceLock};
2
3use super::{api, AllocError, Layout, TursoAllocator};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum SetAllocatorError {
7    AlreadyInitialized,
8}
9
10impl fmt::Display for SetAllocatorError {
11    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12        match self {
13            Self::AlreadyInitialized => f.write_str("Turso allocator is already initialized"),
14        }
15    }
16}
17
18impl std::error::Error for SetAllocatorError {}
19
20/// Backend for Turso heap allocations.
21///
22/// # Safety
23///
24/// Implementations must uphold the `Allocator` contract for every allocation
25/// returned from `allocate`, including zero-sized layouts.
26pub unsafe trait TursoAllocBackend: Sync {
27    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>;
28
29    /// # Safety
30    ///
31    /// `ptr` and `layout` must describe a live block previously returned by
32    /// this backend.
33    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout);
34}
35
36struct DefaultBackend;
37
38unsafe impl TursoAllocBackend for DefaultBackend {
39    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
40        <api::Global as api::ApiAllocator>::allocate(&api::Global, layout)
41    }
42
43    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
44        unsafe {
45            <api::Global as api::ApiAllocator>::deallocate(&api::Global, ptr, layout);
46        }
47    }
48}
49
50static DEFAULT_BACKEND: DefaultBackend = DefaultBackend;
51static BACKEND: OnceLock<&'static dyn TursoAllocBackend> = OnceLock::new();
52
53/// Sets Turso's process-wide allocation backend once.
54///
55/// # Safety
56///
57/// This function must be called before any database operation, or any other
58/// operation that can allocate through [`TursoAllocator`]. The allocator is
59/// process-wide and can only be set once. Allocating with one backend and
60/// deallocating with another can violate allocator invariants. In practice,
61/// some backend pairs may both delegate to the system allocator and happen to
62/// work, but callers must not rely on that.
63pub unsafe fn set_allocator(
64    backend: &'static dyn TursoAllocBackend,
65) -> Result<(), SetAllocatorError> {
66    BACKEND
67        .set(backend)
68        .map_err(|_| SetAllocatorError::AlreadyInitialized)
69}
70
71fn backend() -> &'static dyn TursoAllocBackend {
72    BACKEND.get().copied().unwrap_or(&DEFAULT_BACKEND)
73}
74
75unsafe impl api::ApiAllocator for TursoAllocator {
76    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
77        backend().allocate(layout)
78    }
79
80    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
81        unsafe {
82            backend().deallocate(ptr, layout);
83        }
84    }
85}