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}