Skip to content

Commit

Permalink
get on latest bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
nplasterer committed Dec 21, 2024
1 parent 3378882 commit 93b808b
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 2 deletions.
4 changes: 2 additions & 2 deletions library/src/main/java/libxmtp-version.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Version: 33c06af6
Version: 7d863bde
Branch: main
Date: 2024-12-20 17:34:43 +0000
Date: 2024-12-20 22:46:38 +0000
299 changes: 299 additions & 0 deletions library/src/main/java/xmtpv3.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,14 @@ internal interface UniffiLib : Library {
`ptr`: Pointer, `error`: RustBuffer.ByValue, uniffi_out_err: UniffiRustCallStatus,
): Unit

fun uniffi_xmtpv3_fn_clone_fficonversationlistitem(
`ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus,
): Pointer

fun uniffi_xmtpv3_fn_free_fficonversationlistitem(
`ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus,
): Unit

fun uniffi_xmtpv3_fn_clone_fficonversationmetadata(
`ptr`: Pointer, uniffi_out_err: UniffiRustCallStatus,
): Pointer
Expand Down Expand Up @@ -1231,6 +1239,10 @@ internal interface UniffiLib : Library {
`ptr`: Pointer, `opts`: RustBuffer.ByValue,
): Long

fun uniffi_xmtpv3_fn_method_fficonversations_list_conversations(
`ptr`: Pointer,
): Long

fun uniffi_xmtpv3_fn_method_fficonversations_list_dms(
`ptr`: Pointer, `opts`: RustBuffer.ByValue,
): Long
Expand Down Expand Up @@ -2112,6 +2124,9 @@ internal interface UniffiLib : Library {
fun uniffi_xmtpv3_checksum_method_fficonversations_list(
): Short

fun uniffi_xmtpv3_checksum_method_fficonversations_list_conversations(
): Short

fun uniffi_xmtpv3_checksum_method_fficonversations_list_dms(
): Short

Expand Down Expand Up @@ -2528,6 +2543,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) {
if (lib.uniffi_xmtpv3_checksum_method_fficonversations_list() != 42790.toShort()) {
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
}
if (lib.uniffi_xmtpv3_checksum_method_fficonversations_list_conversations() != 29098.toShort()) {
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
}
if (lib.uniffi_xmtpv3_checksum_method_fficonversations_list_dms() != 41576.toShort()) {
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
}
Expand Down Expand Up @@ -5073,6 +5091,225 @@ public object FfiConverterTypeFfiConversationCallback :
//


public interface FfiConversationListItemInterface {

companion object
}

open class FfiConversationListItem : Disposable, AutoCloseable, FfiConversationListItemInterface {

constructor(pointer: Pointer) {
this.pointer = pointer
this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer))
}

/**
* This constructor can be used to instantiate a fake object. Only used for tests. Any
* attempt to actually use an object constructed this way will fail as there is no
* connected Rust object.
*/
@Suppress("UNUSED_PARAMETER")
constructor(noPointer: NoPointer) {
this.pointer = null
this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer))
}

protected val pointer: Pointer?
protected val cleanable: UniffiCleaner.Cleanable

private val wasDestroyed = AtomicBoolean(false)
private val callCounter = AtomicLong(1)

override fun destroy() {
// Only allow a single call to this method.
// TODO: maybe we should log a warning if called more than once?
if (this.wasDestroyed.compareAndSet(false, true)) {
// This decrement always matches the initial count of 1 given at creation time.
if (this.callCounter.decrementAndGet() == 0L) {
cleanable.clean()
}
}
}

@Synchronized
override fun close() {
this.destroy()
}

internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R {
// Check and increment the call counter, to keep the object alive.
// This needs a compare-and-set retry loop in case of concurrent updates.
do {
val c = this.callCounter.get()
if (c == 0L) {
throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
}
if (c == Long.MAX_VALUE) {
throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
}
} while (!this.callCounter.compareAndSet(c, c + 1L))
// Now we can safely do the method call without the pointer being freed concurrently.
try {
return block(this.uniffiClonePointer())
} finally {
// This decrement always matches the increment we performed above.
if (this.callCounter.decrementAndGet() == 0L) {
cleanable.clean()
}
}
}

// Use a static inner class instead of a closure so as not to accidentally
// capture `this` as part of the cleanable's action.
private class UniffiCleanAction(private val pointer: Pointer?) : Runnable {
override fun run() {
pointer?.let { ptr ->
uniffiRustCall { status ->
UniffiLib.INSTANCE.uniffi_xmtpv3_fn_free_fficonversationlistitem(ptr, status)
}
}
}
}

fun uniffiClonePointer(): Pointer {
return uniffiRustCall() { status ->
UniffiLib.INSTANCE.uniffi_xmtpv3_fn_clone_fficonversationlistitem(pointer!!, status)
}
}


companion object

}

/**
* @suppress
*/
public object FfiConverterTypeFfiConversationListItem :
FfiConverter<FfiConversationListItem, Pointer> {

override fun lower(value: FfiConversationListItem): Pointer {
return value.uniffiClonePointer()
}

override fun lift(value: Pointer): FfiConversationListItem {
return FfiConversationListItem(value)
}

override fun read(buf: ByteBuffer): FfiConversationListItem {
// The Rust code always writes pointers as 8 bytes, and will
// fail to compile if they don't fit.
return lift(Pointer(buf.getLong()))
}

override fun allocationSize(value: FfiConversationListItem) = 8UL

override fun write(value: FfiConversationListItem, buf: ByteBuffer) {
// The Rust code always expects pointers written as 8 bytes,
// and will fail to compile if they don't fit.
buf.putLong(Pointer.nativeValue(lower(value)))
}
}


// This template implements a class for working with a Rust struct via a Pointer/Arc<T>
// to the live Rust struct on the other side of the FFI.
//
// Each instance implements core operations for working with the Rust `Arc<T>` and the
// Kotlin Pointer to work with the live Rust struct on the other side of the FFI.
//
// There's some subtlety here, because we have to be careful not to operate on a Rust
// struct after it has been dropped, and because we must expose a public API for freeing
// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
//
// * Each instance holds an opaque pointer to the underlying Rust struct.
// Method calls need to read this pointer from the object's state and pass it in to
// the Rust FFI.
//
// * When an instance is no longer needed, its pointer should be passed to a
// special destructor function provided by the Rust FFI, which will drop the
// underlying Rust struct.
//
// * Given an instance, calling code is expected to call the special
// `destroy` method in order to free it after use, either by calling it explicitly
// or by using a higher-level helper like the `use` method. Failing to do so risks
// leaking the underlying Rust struct.
//
// * We can't assume that calling code will do the right thing, and must be prepared
// to handle Kotlin method calls executing concurrently with or even after a call to
// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
//
// * We must never allow Rust code to operate on the underlying Rust struct after
// the destructor has been called, and must never call the destructor more than once.
// Doing so may trigger memory unsafety.
//
// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner`
// is implemented to call the destructor when the Kotlin object becomes unreachable.
// This is done in a background thread. This is not a panacea, and client code should be aware that
// 1. the thread may starve if some there are objects that have poorly performing
// `drop` methods or do significant work in their `drop` methods.
// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`,
// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html).
//
// If we try to implement this with mutual exclusion on access to the pointer, there is the
// possibility of a race between a method call and a concurrent call to `destroy`:
//
// * Thread A starts a method call, reads the value of the pointer, but is interrupted
// before it can pass the pointer over the FFI to Rust.
// * Thread B calls `destroy` and frees the underlying Rust struct.
// * Thread A resumes, passing the already-read pointer value to Rust and triggering
// a use-after-free.
//
// One possible solution would be to use a `ReadWriteLock`, with each method call taking
// a read lock (and thus allowed to run concurrently) and the special `destroy` method
// taking a write lock (and thus blocking on live method calls). However, we aim not to
// generate methods with any hidden blocking semantics, and a `destroy` method that might
// block if called incorrectly seems to meet that bar.
//
// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track
// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
// has been called. These are updated according to the following rules:
//
// * The initial value of the counter is 1, indicating a live object with no in-flight calls.
// The initial value for the flag is false.
//
// * At the start of each method call, we atomically check the counter.
// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
// If it is nonzero them we atomically increment it by 1 and proceed with the method call.
//
// * At the end of each method call, we atomically decrement and check the counter.
// If it has reached zero then we destroy the underlying Rust struct.
//
// * When `destroy` is called, we atomically flip the flag from false to true.
// If the flag was already true we silently fail.
// Otherwise we atomically decrement and check the counter.
// If it has reached zero then we destroy the underlying Rust struct.
//
// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
//
// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
// of the underlying Rust code.
//
// This makes a cleaner a better alternative to _not_ calling `destroy()` as
// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop`
// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner
// thread may be starved, and the app will leak memory.
//
// In this case, `destroy`ing manually may be a better solution.
//
// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects
// with Rust peers are reclaimed:
//
// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen:
// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then:
// 3. The memory is reclaimed when the process terminates.
//
// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
//


public interface FfiConversationMetadataInterface {

fun `conversationType`(): FfiConversationType
Expand Down Expand Up @@ -5336,6 +5573,8 @@ public interface FfiConversationsInterface {

suspend fun `list`(`opts`: FfiListConversationsOptions): List<FfiConversation>

suspend fun `listConversations`(): List<FfiConversationListItem>

suspend fun `listDms`(`opts`: FfiListConversationsOptions): List<FfiConversation>

suspend fun `listGroups`(`opts`: FfiListConversationsOptions): List<FfiConversation>
Expand Down Expand Up @@ -5588,6 +5827,38 @@ open class FfiConversations : Disposable, AutoCloseable, FfiConversationsInterfa
}


@Throws(GenericException::class)
@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
override suspend fun `listConversations`(): List<FfiConversationListItem> {
return uniffiRustCallAsync(
callWithPointer { thisPtr ->
UniffiLib.INSTANCE.uniffi_xmtpv3_fn_method_fficonversations_list_conversations(
thisPtr,

)
},
{ future, callback, continuation ->
UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_poll_rust_buffer(
future,
callback,
continuation
)
},
{ future, continuation ->
UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_complete_rust_buffer(
future,
continuation
)
},
{ future -> UniffiLib.INSTANCE.ffi_xmtpv3_rust_future_free_rust_buffer(future) },
// lift function
{ FfiConverterSequenceTypeFfiConversationListItem.lift(it) },
// Error FFI converter
GenericException.ErrorHandler,
)
}


@Throws(GenericException::class)
@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
override suspend fun `listDms`(`opts`: FfiListConversationsOptions): List<FfiConversation> {
Expand Down Expand Up @@ -11773,6 +12044,34 @@ public object FfiConverterSequenceTypeFfiConversation :
}


/**
* @suppress
*/
public object FfiConverterSequenceTypeFfiConversationListItem :
FfiConverterRustBuffer<List<FfiConversationListItem>> {
override fun read(buf: ByteBuffer): List<FfiConversationListItem> {
val len = buf.getInt()
return List<FfiConversationListItem>(len) {
FfiConverterTypeFfiConversationListItem.read(buf)
}
}

override fun allocationSize(value: List<FfiConversationListItem>): ULong {
val sizeForLength = 4UL
val sizeForItems =
value.map { FfiConverterTypeFfiConversationListItem.allocationSize(it) }.sum()
return sizeForLength + sizeForItems
}

override fun write(value: List<FfiConversationListItem>, buf: ByteBuffer) {
buf.putInt(value.size)
value.iterator().forEach {
FfiConverterTypeFfiConversationListItem.write(it, buf)
}
}
}


/**
* @suppress
*/
Expand Down
Binary file modified library/src/main/jniLibs/arm64-v8a/libuniffi_xmtpv3.so
Binary file not shown.
Binary file modified library/src/main/jniLibs/armeabi-v7a/libuniffi_xmtpv3.so
Binary file not shown.
Binary file modified library/src/main/jniLibs/x86/libuniffi_xmtpv3.so
Binary file not shown.
Binary file modified library/src/main/jniLibs/x86_64/libuniffi_xmtpv3.so
Binary file not shown.

0 comments on commit 93b808b

Please sign in to comment.