Skip to main content

mountpoint_s3_crt/s3/
pool.rs

1//! Bridge custom memory pool implementations to the CRT S3 Client interface.
2
3use std::future::{Future, ready};
4use std::marker::PhantomPinned;
5use std::pin::Pin;
6use std::sync::Arc;
7use std::{fmt::Debug, ptr::NonNull};
8
9use mountpoint_s3_crt_sys::{
10    aws_allocator, aws_byte_buf, aws_byte_buf_from_empty_array, aws_byte_cursor, aws_future_s3_buffer_ticket,
11    aws_future_s3_buffer_ticket_acquire, aws_future_s3_buffer_ticket_new, aws_future_s3_buffer_ticket_release,
12    aws_future_s3_buffer_ticket_set_result_by_move, aws_ref_count_init, aws_s3_buffer_pool, aws_s3_buffer_pool_config,
13    aws_s3_buffer_pool_factory_fn, aws_s3_buffer_pool_reserve_meta, aws_s3_buffer_pool_vtable, aws_s3_buffer_ticket,
14    aws_s3_buffer_ticket_vtable,
15};
16
17use crate::ToAwsByteCursor as _;
18use crate::common::allocator::Allocator;
19use crate::io::event_loop::EventLoopGroup;
20use crate::io::futures::FutureSpawner;
21use crate::s3::client::MetaRequestType;
22
23/// A custom memory pool.
24///
25/// **WARNING:** The API for this trait is still experimental and will likely change
26/// in future releases.
27pub trait MemoryPool: Clone + Send + Sync + 'static {
28    /// Associated buffer type.
29    type Buffer: AsMut<[u8]> + Send;
30
31    /// Get a buffer of at least the requested size.
32    fn get_buffer(&self, size: usize, meta_request_type: MetaRequestType) -> Self::Buffer;
33
34    /// Get a buffer of at least the requested size asynchronously.
35    /// Returns a future that resolves to the buffer.
36    ///
37    /// Implementations must always eventually resolve. If memory is not immediately
38    /// available, the implementation should block/await until it is — not panic or
39    /// deadlock.
40    ///
41    /// The default implementation wraps [`get_buffer`](Self::get_buffer) in a
42    /// ready future. Override this when the pool needs to wait for memory to free up.
43    fn get_buffer_async(
44        &self,
45        size: usize,
46        meta_request_type: MetaRequestType,
47    ) -> impl Future<Output = Self::Buffer> + Send {
48        ready(self.get_buffer(size, meta_request_type))
49    }
50
51    /// Trim the pool.
52    ///
53    /// Return `true` if the pool freed any memory.
54    fn trim(&self) -> bool;
55}
56
57/// Factory for a custom memory pool.
58pub trait MemoryPoolFactory: Send + Sync {
59    /// The [MemoryPool] implementation created by this factory.
60    type Pool: MemoryPool;
61
62    /// Create a memory pool instance.
63    fn create(&self, options: MemoryPoolFactoryOptions) -> Self::Pool;
64}
65
66impl<F, P> MemoryPoolFactory for F
67where
68    F: Fn(MemoryPoolFactoryOptions) -> P + Send + Sync,
69    P: MemoryPool,
70{
71    type Pool = P;
72
73    fn create(&self, options: MemoryPoolFactoryOptions) -> Self::Pool {
74        self(options)
75    }
76}
77
78/// Options to create a [MemoryPool].
79#[derive(Debug)]
80pub struct MemoryPoolFactoryOptions {
81    part_size: usize,
82    max_part_size: usize,
83    memory_limit: usize,
84}
85
86impl MemoryPoolFactoryOptions {
87    /// The default part size set on the client.
88    pub fn part_size(&self) -> usize {
89        self.part_size
90    }
91    /// The max part size for the client.
92    pub fn max_part_size(&self) -> usize {
93        self.max_part_size
94    }
95    /// The memory limit set on the client.
96    pub fn memory_limit(&self) -> usize {
97        self.memory_limit
98    }
99}
100
101/// Type-erased wrapper around a [MemoryPoolFactory] implementation.
102///
103/// This wrapper stores the factory in a type-erased form so it can be held
104/// in non-generic structs like `S3ClientConfig`. The concrete type information
105/// is preserved via function pointers for later use when constructing the
106/// CRT bridge ([`CrtBufferPoolFactory`]).
107#[derive(Debug, Clone)]
108pub struct MemoryPoolFactoryWrapper(Arc<MemoryPoolFactoryWrapperInner>);
109
110#[derive(Debug)]
111struct MemoryPoolFactoryWrapperInner {
112    /// Pointer to the leaked (pinned) [MemoryPoolFactory] implementation.
113    factory_ptr: NonNull<libc::c_void>,
114    /// The generic C callback function pointer for `buffer_pool_factory::<PoolFactory>`.
115    factory_fn: aws_s3_buffer_pool_factory_fn,
116    /// Function pointer to drop the leaked factory.
117    drop_fn: fn(*mut libc::c_void),
118}
119
120// SAFETY: `MemoryPoolFactoryWrapperInner` is safe to transfer across threads because it wraps a [MemoryPoolFactory] implementation that is [Send].
121unsafe impl Send for MemoryPoolFactoryWrapperInner {}
122// SAFETY: `MemoryPoolFactoryWrapperInner` is safe to share across threads because it wraps a [MemoryPoolFactory] implementation that is [Sync].
123unsafe impl Sync for MemoryPoolFactoryWrapperInner {}
124
125impl Drop for MemoryPoolFactoryWrapperInner {
126    fn drop(&mut self) {
127        (self.drop_fn)(self.factory_ptr.as_ptr());
128    }
129}
130
131impl MemoryPoolFactoryWrapper {
132    /// Create a new wrapper from a [MemoryPoolFactory] implementation.
133    ///
134    /// The factory is pinned and leaked into a type-erased pointer, preserving
135    /// the concrete type information via function pointers for later use by
136    /// [`CrtBufferPoolFactory`].
137    pub fn new<PoolFactory: MemoryPoolFactory>(pool_factory: PoolFactory) -> Self {
138        let factory = Box::pin(pool_factory);
139        // SAFETY: The pointer to the factory will only be used in `buffer_pool_factory` and
140        // `drop_pool_factory`, which will treat it as pinned.
141        let leaked = Box::leak(unsafe { Pin::into_inner_unchecked(factory) });
142        // SAFETY: `leaked` is not null.
143        let factory_ptr = unsafe { NonNull::new_unchecked(leaked as *mut PoolFactory as *mut libc::c_void) };
144        Self(Arc::new(MemoryPoolFactoryWrapperInner {
145            factory_ptr,
146            factory_fn: Some(buffer_pool_factory::<PoolFactory>),
147            drop_fn: drop_pool_factory::<PoolFactory>,
148        }))
149    }
150}
151
152/// Factory used by [Client](`super::client::Client`) to create CRT wrappers for [MemoryPool] implementations.
153#[derive(Debug)]
154pub struct CrtBufferPoolFactory(Box<CrtBufferPoolFactoryInner>);
155
156#[derive(Debug)]
157struct CrtBufferPoolFactoryInner {
158    event_loop_group: EventLoopGroup,
159    /// Keeps the wrapper alive so its `Drop` impl frees the factory pointer exactly once.
160    wrapper: MemoryPoolFactoryWrapper,
161}
162
163impl CrtBufferPoolFactory {
164    /// Builds a CRT buffer pool factory from a type-erased wrapper and an [EventLoopGroup].
165    ///
166    /// The wrapper provides the type-erased factory pointer and C callback.
167    pub fn new(wrapper: MemoryPoolFactoryWrapper, event_loop_group: EventLoopGroup) -> Self {
168        Self(Box::new(CrtBufferPoolFactoryInner {
169            event_loop_group,
170            wrapper,
171        }))
172    }
173
174    /// Returns the factory callback and user_data pointer to pass to the CRT.
175    ///
176    /// The user_data pointer points to the `CrtBufferPoolFactoryInner` so that the
177    /// C callback can access both the pool factory and the [EventLoopGroup].
178    ///
179    /// # Safety
180    /// The caller MUST keep this `CrtBufferPoolFactory` alive for as long as the CRT
181    /// may invoke the callback.
182    pub(crate) unsafe fn as_inner(&self) -> (aws_s3_buffer_pool_factory_fn, *mut ::libc::c_void) {
183        (
184            self.0.wrapper.0.factory_fn,
185            &*self.0 as *const CrtBufferPoolFactoryInner as *mut ::libc::c_void,
186        )
187    }
188}
189
190unsafe extern "C" fn buffer_pool_factory<PoolFactory: MemoryPoolFactory>(
191    allocator: *mut aws_allocator,
192    config: aws_s3_buffer_pool_config,
193    user_data: *mut libc::c_void,
194) -> *mut aws_s3_buffer_pool {
195    // SAFETY: `user_data` points to a `CrtBufferPoolFactoryInner` kept alive by the
196    // `Box` in `CrtBufferPoolFactory` (held by `ClientConfig`).
197    let factory_inner = unsafe { &*(user_data as *const CrtBufferPoolFactoryInner) };
198
199    // SAFETY: `factory_ptr` references a pinned box owned by the `MemoryPoolFactoryWrapper`.
200    let pool_factory = unsafe { &*(factory_inner.wrapper.0.factory_ptr.as_ptr() as *mut PoolFactory) };
201
202    let event_loop_group = factory_inner.event_loop_group.clone();
203
204    // SAFETY: `allocator` is a non-null pointer to a `aws_allocator` instance.
205    let allocator = unsafe { NonNull::new_unchecked(allocator).into() };
206
207    let options = MemoryPoolFactoryOptions {
208        part_size: config.part_size,
209        max_part_size: config.max_part_size,
210        memory_limit: config.memory_limit,
211    };
212    let pool = pool_factory.create(options);
213
214    let crt_pool = CrtBufferPool::new(pool.clone(), allocator, event_loop_group);
215
216    // SAFETY: the CRT will only use the pool through its vtable and refcount.
217    unsafe { crt_pool.leak() }
218}
219
220fn drop_pool_factory<PoolFactory: MemoryPoolFactory>(factory_ptr: *mut libc::c_void) {
221    // SAFETY: `factory_ptr` was leaked in `MemoryPoolFactoryWrapper::new`.
222    _ = unsafe { Pin::new_unchecked(Box::from_raw(factory_ptr as *mut PoolFactory)) };
223}
224
225/// Internal wrapper to bridge the [MemoryPool] implementation to
226/// the `aws_s3_buffer_pool` to provide to the CRT.
227///
228/// Instances of this type also hold the vtables to set up both
229/// the `aws_s3_buffer_pool` itself and the `aws_s3_buffer_ticket`s
230/// it returns. Notably, all the functions in the vtables are generic
231/// in the same [MemoryPool] implementation as [CrtBufferPool], so
232/// that the CRT can handle different implementations and there is
233/// no need for dynamic dispatch on the Rust side.
234struct CrtBufferPool<Pool: MemoryPool> {
235    /// Inner struct to pass to CRT functions.
236    inner: aws_s3_buffer_pool,
237    /// [MemoryPool] implementation.
238    pool: Pool,
239    /// Holds the vtable to point to in `inner`.
240    pool_vtable: aws_s3_buffer_pool_vtable,
241    /// Holds the vtable for the `aws_s3_buffer_ticket` instances.
242    ticket_vtable: aws_s3_buffer_ticket_vtable,
243    /// CRT allocator.
244    allocator: Allocator,
245    /// [EventLoopGroup] for spawning async get_buffer calls.
246    event_loop_group: EventLoopGroup,
247    /// Pin this struct because inner.impl_ will be a pointer to this object.
248    _pinned: PhantomPinned,
249}
250
251impl<Pool: MemoryPool> CrtBufferPool<Pool> {
252    fn new(pool: Pool, allocator: Allocator, event_loop_group: EventLoopGroup) -> Pin<Box<Self>> {
253        // `inner` will be initialized after pinning because its fields require pinned addresses.
254        let mut crt_pool = Box::pin(CrtBufferPool {
255            inner: Default::default(),
256            pool,
257            pool_vtable: aws_s3_buffer_pool_vtable {
258                reserve: Some(pool_reserve::<Pool>),
259                trim: Some(pool_trim::<Pool>),
260                acquire: None,
261                release: None,
262                ..Default::default()
263            },
264            ticket_vtable: aws_s3_buffer_ticket_vtable {
265                claim: Some(ticket_claim::<Pool::Buffer>),
266                acquire: None,
267                release: None,
268            },
269            allocator,
270            event_loop_group,
271            _pinned: Default::default(),
272        });
273
274        // Set up the vtable and `impl_` to the pinned addresses (self-referential) and initialize ref-counting.
275        // SAFETY: We're setting up the struct to be self-referential, and we're not moving out
276        // of the struct, so the unchecked deref of the pinned pointer is okay.
277        unsafe {
278            let pool_ref = Pin::get_unchecked_mut(Pin::as_mut(&mut crt_pool));
279            pool_ref.inner.vtable = &raw mut pool_ref.pool_vtable;
280            pool_ref.inner.impl_ = pool_ref as *mut CrtBufferPool<Pool> as *mut libc::c_void;
281            aws_ref_count_init(
282                &mut pool_ref.inner.ref_count,
283                &mut pool_ref.inner as *mut aws_s3_buffer_pool as *mut libc::c_void,
284                Some(pool_destroy::<Pool>),
285            );
286        }
287
288        crt_pool
289    }
290
291    /// Leak a pinned instance and returns a raw pointer.
292    ///
293    /// # Safety
294    /// The returned pointer must eventually be passed to [from_raw] and can
295    /// additionally only used in [ref_from_raw].
296    unsafe fn leak(self: Pin<Box<Self>>) -> *mut aws_s3_buffer_pool {
297        // SAFETY: the resulting pointer will be only used in `pool_reserve`, `pool_trim`, and `pool_destroy`.
298        let pool = Box::leak(unsafe { Pin::into_inner_unchecked(self) });
299        &raw mut pool.inner
300    }
301
302    /// Returns a reference to original instance from a raw pointer.
303    ///
304    /// # Safety
305    /// The raw pointer must have been obtained through [leak()].
306    unsafe fn ref_from_raw(pool: &*mut aws_s3_buffer_pool) -> &Self {
307        // SAFETY: `pool` points to the `inner` field of a pinned instance.
308        unsafe {
309            let impl_ptr = (**pool).impl_;
310            &*(impl_ptr as *mut Self)
311        }
312    }
313
314    /// Re-constructs the original pinned instance from a raw pointer.
315    ///
316    /// # Safety
317    /// The raw pointer must have been obtained through [leak()].
318    unsafe fn from_raw(pool: *mut aws_s3_buffer_pool) -> Pin<Box<Self>> {
319        // SAFETY: `pool` points to the `inner` field of a pinned instance.
320        unsafe { Pin::new_unchecked(Box::from_raw((*pool).impl_ as *mut Self)) }
321    }
322
323    fn trim(&self) {
324        self.pool.trim();
325    }
326
327    fn reserve(&self, size: usize, meta_request_type: MetaRequestType, can_block: bool) -> CrtTicketFuture {
328        let future = CrtTicketFuture::new(&self.allocator);
329        let ticket_vtable = self.ticket_vtable;
330
331        if can_block {
332            // The write path in s3_meta_request.c expects the ticket to be fulfilled
333            // synchronously — it treats an unfulfilled future as error.
334            let buffer = self.pool.get_buffer(size, meta_request_type);
335            let ticket = CrtTicket::new(buffer, ticket_vtable);
336            future.set(ticket);
337        } else {
338            // Read path: spawn on background thread, return unfulfilled future.
339            let pool = self.pool.clone();
340            let future_clone = future.clone();
341            // Dropping the handle is intentional: if the CRT cancels the meta request
342            // while the task is in flight, the buffer will be allocated (eventually) and
343            // set on an already-done future — the CRT handles this safely by destroying
344            // the ticket via `s_future_impl_result_dtor`. This results in a wasted
345            // allocation that is immediately freed, but no leak or error.
346            // TODO: The CRT does not currently provide a cancellation mechanism for
347            // in-flight ticket futures. If one is added, we could propagate it to
348            // `get_buffer_async` to avoid the wasteful buffer allocation.
349            let _handle = self.event_loop_group.spawn_future(async move {
350                let buffer = pool.get_buffer_async(size, meta_request_type).await;
351                let ticket = CrtTicket::new(buffer, ticket_vtable);
352                future_clone.set(ticket);
353            });
354        }
355
356        future
357    }
358}
359
360unsafe extern "C" fn pool_reserve<Pool: MemoryPool>(
361    pool: *mut aws_s3_buffer_pool,
362    meta: aws_s3_buffer_pool_reserve_meta,
363) -> *mut aws_future_s3_buffer_ticket {
364    // SAFETY: `pool` was obtained through `CrtMemoryPool::leak`.
365    let crt_pool = unsafe { CrtBufferPool::<Pool>::ref_from_raw(&pool) };
366
367    // SAFETY: `meta.meta_request` is a pointer to a valid `aws_s3_meta_request`.
368    let request_type = unsafe { (*meta.meta_request).type_ };
369
370    let future = crt_pool.reserve(meta.size, request_type.into(), meta.can_block);
371
372    // SAFETY: the CRT will take ownership of the future.
373    unsafe { future.into_inner_ptr() }
374}
375
376unsafe extern "C" fn pool_trim<Pool: MemoryPool>(pool: *mut aws_s3_buffer_pool) {
377    // SAFETY: `pool` was obtained through `CrtMemoryPool::leak`.
378    let crt_pool = unsafe { CrtBufferPool::<Pool>::ref_from_raw(&pool) };
379    crt_pool.trim();
380}
381
382unsafe extern "C" fn pool_destroy<Pool: MemoryPool>(data: *mut libc::c_void) {
383    let pool = data as *mut aws_s3_buffer_pool;
384
385    // SAFETY: `pool` was obtained through `CrtMemoryPool::leak`.
386    _ = unsafe { CrtBufferPool::<Pool>::from_raw(pool) };
387}
388
389/// Wrapper for [aws_s3_buffer_ticket].
390struct CrtTicket<Buffer: AsMut<[u8]>> {
391    /// Inner struct to pass to CRT functions.
392    inner: aws_s3_buffer_ticket,
393    /// Holds the vtable to point to in `inner`.
394    ticket_vtable: aws_s3_buffer_ticket_vtable,
395    /// Buffer implementing [AsMut<\[u8\]>].
396    buffer: Buffer,
397    /// Pin this struct because inner.impl_ will be a pointer to this object.
398    _pinned: PhantomPinned,
399}
400
401impl<Buffer: AsMut<[u8]>> CrtTicket<Buffer> {
402    fn new(buffer: Buffer, ticket_vtable: aws_s3_buffer_ticket_vtable) -> Pin<Box<Self>> {
403        // `inner` will be initialized after pinning because its fields require pinned addresses.
404        let mut ticket = Box::pin(CrtTicket {
405            inner: Default::default(),
406            ticket_vtable,
407            buffer,
408            _pinned: Default::default(),
409        });
410
411        // Set up the vtable and `impl_` to the pinned addresses (self-referential) and initialize ref-counting.
412        // SAFETY: We're setting up the struct to be self-referential, and we're not moving out
413        // of the struct, so the unchecked deref of the pinned pointer is okay.
414        unsafe {
415            let ticket_ref = Pin::get_unchecked_mut(Pin::as_mut(&mut ticket));
416            ticket_ref.inner.vtable = &raw mut ticket_ref.ticket_vtable;
417            ticket_ref.inner.impl_ = ticket_ref as *mut CrtTicket<Buffer> as *mut libc::c_void;
418            aws_ref_count_init(
419                &mut ticket_ref.inner.ref_count,
420                &mut ticket_ref.inner as *mut aws_s3_buffer_ticket as *mut libc::c_void,
421                Some(ticket_destroy::<Buffer>),
422            );
423        }
424
425        ticket
426    }
427
428    /// Leak a pinned instance and returns a raw pointer.
429    ///
430    /// # Safety
431    /// The returned pointer must eventually be passed to [from_raw] and can
432    /// additionally only be used in [ref_mut_from_raw].
433    unsafe fn leak(self: Pin<Box<Self>>) -> *mut aws_s3_buffer_ticket {
434        // SAFETY: the resulting pointer will be only used in `ticket_claim` and `ticket_destroy`.
435        let boxed = unsafe { Pin::into_inner_unchecked(self) };
436        let pool = Box::leak(boxed);
437        &raw mut pool.inner
438    }
439
440    /// Returns a reference to original instance from a raw pointer.
441    ///
442    /// # Safety
443    /// The raw pointer must have been obtained through [leak()].
444    unsafe fn ref_mut_from_raw(ticket: &mut *mut aws_s3_buffer_ticket) -> &mut Self {
445        // SAFETY: `ticket` points to the `inner` field of a pinned instance.
446        unsafe {
447            let impl_ptr = (**ticket).impl_;
448            &mut *(impl_ptr as *mut Self)
449        }
450    }
451
452    /// Re-constructs the original pinned instance from a raw pointer.
453    ///
454    /// # Safety
455    /// The raw pointer must have been obtained through [leak()].
456    unsafe fn from_raw(ticket: *mut aws_s3_buffer_ticket) -> Pin<Box<Self>> {
457        // SAFETY: `ticket` points to the `inner` field of a pinned instance.
458        unsafe { Pin::new_unchecked(Box::from_raw((*ticket).impl_ as *mut Self)) }
459    }
460}
461
462/// Return the buffer associated with a ticket.
463unsafe extern "C" fn ticket_claim<Buffer: AsMut<[u8]>>(mut ticket: *mut aws_s3_buffer_ticket) -> aws_byte_buf {
464    // SAFETY: `ticket` was obtained through `Ticket::leak`.
465    let ticket = unsafe { CrtTicket::<Buffer>::ref_mut_from_raw(&mut ticket) };
466
467    // SAFETY: the CRT guarantees to only use the returned buffer while holding the ticket.
468    let aws_byte_cursor { len, ptr } = unsafe { ticket.buffer.as_mut().as_aws_byte_cursor() };
469
470    // Use `aws_byte_buf_from_empty_array` to build an `aws_byte_buf` with 0 length and `len` capacity.
471    // SAFETY: `ptr` is a valid buffer with capacity >= `len`.
472    unsafe { aws_byte_buf_from_empty_array(ptr as *mut libc::c_void, len) }
473}
474
475unsafe extern "C" fn ticket_destroy<Buffer: AsMut<[u8]>>(data: *mut libc::c_void) {
476    let ticket = data as *mut aws_s3_buffer_ticket;
477    // SAFETY: `ticket` was obtained through `Ticket::leak`.
478    _ = unsafe { CrtTicket::<Buffer>::from_raw(ticket) };
479}
480
481/// Wrapper for [aws_future_s3_buffer_ticket].
482#[derive(Debug)]
483struct CrtTicketFuture {
484    inner: *mut aws_future_s3_buffer_ticket,
485}
486
487// SAFETY: `aws_future_s3_buffer_ticket` is reference counted and its methods are thread-safe.
488unsafe impl Send for CrtTicketFuture {}
489
490// SAFETY: `aws_future_s3_buffer_ticket` is reference counted and its methods are thread-safe.
491unsafe impl Sync for CrtTicketFuture {}
492
493impl CrtTicketFuture {
494    fn new(allocator: &Allocator) -> Self {
495        // SAFETY: aws_future_s3_buffer_ticket_new return a non-null pointer to a new aws_future_s3_buffer_ticket with a reference count of 1.
496        let inner = unsafe { aws_future_s3_buffer_ticket_new(allocator.inner.as_ptr()) };
497        Self { inner }
498    }
499
500    fn set<Buffer: AsMut<[u8]>>(&self, ticket: Pin<Box<CrtTicket<Buffer>>>) {
501        // SAFETY: `ticket` will be passed to the CRT which will only use it through its vtable and refcount.
502        let mut ticket = unsafe { ticket.leak() };
503        // SAFETY: `self.inner` is a valid future and we are setting it to `ticket`.
504        unsafe {
505            aws_future_s3_buffer_ticket_set_result_by_move(self.inner, &mut ticket);
506        }
507    }
508
509    /// Return the pointer to the inner `aws_future_s3_buffer_ticket` instance.
510    ///
511    /// # Safety
512    /// The returned pointer follows ref-counting rules and must be eventually released.
513    unsafe fn into_inner_ptr(mut self) -> *mut aws_future_s3_buffer_ticket {
514        // Swap the pointer to return with null, so drop will be a no-op.
515        std::mem::replace(&mut self.inner, std::ptr::null_mut())
516    }
517}
518
519impl Clone for CrtTicketFuture {
520    fn clone(&self) -> Self {
521        // SAFETY: `self.inner` is a valid `aws_future_s3_buffer_ticket`, and we increment its
522        // reference count on Clone and decrement it on Drop.
523        let inner = unsafe { aws_future_s3_buffer_ticket_acquire(self.inner) };
524        Self { inner }
525    }
526}
527
528impl Drop for CrtTicketFuture {
529    fn drop(&mut self) {
530        // SAFETY: `self.inner` is a valid `aws_future_s3_buffer_ticket` (or null), and on Drop
531        // it's safe to decrement the reference count since this is balancing the `acquire` in `new`.
532        unsafe { aws_future_s3_buffer_ticket_release(self.inner) };
533    }
534}