Skip to main content

agave_scheduling_utils/
transaction_ptr.rs

1use {
2    agave_scheduler_bindings::{
3        MAX_TRANSACTIONS_PER_MESSAGE, SharableTransactionBatchRegion, SharableTransactionRegion,
4    },
5    agave_transaction_view::transaction_data::TransactionData,
6    core::ptr::NonNull,
7    rts_alloc::Allocator,
8    std::marker::PhantomData,
9};
10
11#[derive(Debug)]
12pub struct TransactionPtr {
13    ptr: NonNull<u8>,
14    count: usize,
15}
16
17impl TransactionData for TransactionPtr {
18    fn data(&self) -> &[u8] {
19        unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), self.count) }
20    }
21}
22
23impl TransactionData for &TransactionPtr {
24    fn data(&self) -> &[u8] {
25        unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), self.count) }
26    }
27}
28
29impl TransactionPtr {
30    /// Constructions a [`TransactionPtr`] from raw parts.
31    ///
32    /// # Safety
33    ///
34    /// - `ptr` must be valid for reads.
35    /// - `count` must be accurate and not overrun the end of `ptr`.
36    ///
37    /// # Note
38    ///
39    /// If you are trying to construct a pointer for use by Agave, you almost certainly want to use
40    /// [`Self::from_sharable_transaction_region`].
41    pub unsafe fn from_raw_parts(ptr: NonNull<u8>, count: usize) -> Self {
42        Self { ptr, count }
43    }
44
45    /// # Safety
46    /// - `sharable_transaction_region` must reference a valid offset and length
47    ///   within the `allocator`.
48    pub unsafe fn from_sharable_transaction_region(
49        sharable_transaction_region: &SharableTransactionRegion,
50        allocator: &Allocator,
51    ) -> Self {
52        // SAFETY: `sharable_transaction_region.offset` was allocated by `allocator`.
53        let ptr = unsafe { allocator.ptr_from_offset(sharable_transaction_region.offset) };
54        Self {
55            ptr,
56            count: sharable_transaction_region.length as usize,
57        }
58    }
59
60    /// Translate the ptr type into a sharable region.
61    ///
62    /// # Safety
63    /// - `allocator` must be the allocator owning the memory region pointed
64    ///   to by `self`.
65    pub unsafe fn to_sharable_transaction_region(
66        &self,
67        allocator: &Allocator,
68    ) -> SharableTransactionRegion {
69        // SAFETY: The `TransactionPtr` creation `Self::from_sharable_transaction_region`
70        // is already conditioned on the offset being valid, if that safety constraint
71        // was satisfied translation back to offset is safe.
72        let offset = unsafe { allocator.offset(self.ptr) };
73        SharableTransactionRegion {
74            offset,
75            length: self.count as u32,
76        }
77    }
78
79    /// Frees the memory region pointed to in the `allocator`.
80    /// This should only be called by the owner of the memory
81    /// i.e. the external scheduler.
82    ///
83    /// # Safety
84    /// - Data region pointed to by `TransactionPtr` belongs to the `allocator`.
85    /// - Inner `ptr` must not have been previously freed.
86    pub unsafe fn free(self, allocator: &Allocator) {
87        unsafe { allocator.free(self.ptr) }
88    }
89}
90
91/// A batch of transaction pointers that can be iterated over.
92pub struct TransactionPtrBatch<'a, M = ()> {
93    tx_ptr: NonNull<SharableTransactionRegion>,
94    meta_ptr: NonNull<M>,
95    num_transactions: usize,
96    allocator: &'a Allocator,
97
98    _meta: PhantomData<M>,
99}
100
101impl<'a, M> TransactionPtrBatch<'a, M> {
102    pub const TRANSACTION_CORE_SIZE: usize = size_of::<SharableTransactionRegion>();
103    pub const TRANSACTION_CORE_END: usize =
104        Self::TRANSACTION_CORE_SIZE * MAX_TRANSACTIONS_PER_MESSAGE;
105
106    pub const TRANSACTION_META_START: usize =
107        Self::TRANSACTION_CORE_END.next_multiple_of(align_of::<M>());
108    pub const TRANSACTION_META_SIZE: usize = size_of::<M>() * MAX_TRANSACTIONS_PER_MESSAGE;
109    pub const TRANSACTION_META_END: usize =
110        Self::TRANSACTION_META_START + Self::TRANSACTION_META_SIZE;
111
112    #[allow(dead_code, reason = "Invariant assertion")]
113    const TRANSACTION_BATCH_SIZE_ASSERT: () = assert!(Self::TRANSACTION_META_END <= 4096);
114
115    /// # Safety
116    /// - [`SharableTransactionBatchRegion`] must reference a valid offset and length
117    ///   within the `allocator`.
118    /// - ALL [`SharableTransactionRegion`]  within the batch must be valid.
119    ///   See [`TransactionPtr::from_sharable_transaction_region`] for details.
120    /// - `M` must match the actual `M` used within this allocation.
121    pub unsafe fn from_sharable_transaction_batch_region(
122        sharable_transaction_batch_region: &SharableTransactionBatchRegion,
123        allocator: &'a Allocator,
124    ) -> Self {
125        // SAFETY: `sharable_transaction_batch_region.transactions_offset` was allocated by `allocator`.
126        let base = unsafe {
127            allocator.ptr_from_offset(sharable_transaction_batch_region.transactions_offset)
128        };
129        let tx_ptr = base.cast();
130        // SAFETY:
131        // - Assuming the batch was originally allocated to support `M`, this call will also be
132        //   safe.
133        let meta_ptr = unsafe { base.byte_add(Self::TRANSACTION_META_START).cast() };
134
135        Self {
136            tx_ptr,
137            meta_ptr,
138            num_transactions: usize::from(sharable_transaction_batch_region.num_transactions),
139            allocator,
140
141            _meta: PhantomData,
142        }
143    }
144
145    /// The number of transactions in this batch.
146    pub const fn len(&self) -> usize {
147        self.num_transactions
148    }
149
150    /// Whether the batch is empty.
151    pub const fn is_empty(&self) -> bool {
152        self.len() == 0
153    }
154
155    /// Iterator returning [`TransactionPtr`] for each transaction in the batch.
156    pub fn iter(&'a self) -> impl Iterator<Item = (TransactionPtr, M)> + 'a {
157        (0..self.num_transactions).map(|idx| unsafe {
158            let tx = self.tx_ptr.add(idx);
159            let tx = TransactionPtr::from_sharable_transaction_region(tx.as_ref(), self.allocator);
160            let meta = self.meta_ptr.add(idx).read();
161
162            (tx, meta)
163        })
164    }
165
166    /// Free the transaction batch container.
167    ///
168    /// # Safety
169    ///
170    /// - [`SharableTransactionBatchRegion`] must be exclusively owned by this pointer.
171    ///
172    /// # Note
173    ///
174    /// This will not free the underlying transactions as their lifetimes may be differ from that of
175    /// the batch.
176    pub unsafe fn free(self) {
177        unsafe { self.allocator.free(self.tx_ptr.cast()) }
178    }
179}