Skip to content

Commit 0b0fa44

Browse files
committed
Consolidate/Simplify the foreign bindings handlemap code
I'm trying to get mozilla#1823 merged now that 0.26.0 is out, but I'll break it up a bit to make it easier. The first step is consolidating the foreign handle map code. Foreign languages define 1 handle map and use it for all object types. I tried to simplify the handle map implementations, the had a lot of features that we weren't using at all and seemed to be only half-implemneted to me, like the right map and counter map. Handles are always `u64` values. This allows us to remove some code like the `USize` handling on Kotlin and the `FfiType::RustFutureContinuationData` variant.
1 parent 26a2fef commit 0b0fa44

35 files changed

+170
-355
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
- The callback interface code was reworked to use vtables rather than a single callback method.
1818
See https://github.com/mozilla/uniffi-rs/pull/1818 for details and how the other bindings were updated.
19+
- Removed `FfiType::RustFutureContinuationData`. `RustFutureContinuation` callbacks now a `u64` handle.
1920

2021
## v0.26.0 (backend crates: v0.26.0) - (_2024-01-23_)
2122

uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,6 @@ impl KotlinCodeOracle {
379379
FfiType::Struct(name) => self.ffi_struct_name(name),
380380
FfiType::Reference(inner) => self.ffi_type_label_by_reference(inner),
381381
FfiType::VoidPointer | FfiType::RustFutureHandle => "Pointer".to_string(),
382-
FfiType::RustFutureContinuationData => "USize".to_string(),
383382
}
384383
}
385384

uniffi_bindgen/src/bindings/kotlin/templates/Async.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toByte()
44
internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toByte()
55

6-
internal val uniffiContinuationHandleMap = UniFfiHandleMap<CancellableContinuation<Byte>>()
6+
internal val uniffiContinuationHandleMap = UniffiHandleMap<CancellableContinuation<Byte>>()
77

88
// FFI type for Rust future continuations
99
internal object uniffiRustFutureContinuationCallback: UniffiRustFutureContinuationCallback {
10-
override fun callback(data: USize, pollResult: Byte) {
11-
uniffiContinuationHandleMap.remove(data)?.resume(pollResult)
10+
override fun callback(data: Long, pollResult: Byte) {
11+
uniffiContinuationHandleMap.remove(data).resume(pollResult)
1212
}
1313
}
1414

1515
internal suspend fun<T, F, E: Exception> uniffiRustCallAsync(
1616
rustFuture: Pointer,
17-
pollFunc: (Pointer, UniffiRustFutureContinuationCallback, USize) -> Unit,
17+
pollFunc: (Pointer, UniffiRustFutureContinuationCallback, Long) -> Unit,
1818
completeFunc: (Pointer, UniffiRustCallStatus) -> F,
1919
freeFunc: (Pointer) -> Unit,
2020
liftFunc: (F) -> T,

uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,3 @@
1-
{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }}
2-
{{- self.add_import("java.util.concurrent.locks.ReentrantLock") }}
3-
{{- self.add_import("kotlin.concurrent.withLock") }}
4-
5-
internal typealias UniffiHandle = Long
6-
internal class ConcurrentHandleMap<T>(
7-
private val leftMap: MutableMap<UniffiHandle, T> = mutableMapOf(),
8-
) {
9-
private val lock = java.util.concurrent.locks.ReentrantLock()
10-
private val currentHandle = AtomicLong(0L)
11-
private val stride = 1L
12-
13-
fun insert(obj: T): UniffiHandle =
14-
lock.withLock {
15-
currentHandle.getAndAdd(stride)
16-
.also { handle ->
17-
leftMap[handle] = obj
18-
}
19-
}
20-
21-
fun get(handle: UniffiHandle) = lock.withLock {
22-
leftMap[handle] ?: throw InternalException("No callback in handlemap; this is a Uniffi bug")
23-
}
24-
25-
fun delete(handle: UniffiHandle) {
26-
this.remove(handle)
27-
}
28-
29-
fun remove(handle: UniffiHandle): T? =
30-
lock.withLock {
31-
leftMap.remove(handle)
32-
}
33-
}
34-
351
// Magic number for the Rust proxy to call using the same mechanism as every other method,
362
// to free the callback once it's dropped by Rust.
373
internal const val IDX_CALLBACK_FREE = 0
@@ -40,8 +6,8 @@ internal const val UNIFFI_CALLBACK_SUCCESS = 0
406
internal const val UNIFFI_CALLBACK_ERROR = 1
417
internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2
428

43-
public abstract class FfiConverterCallbackInterface<CallbackInterface>: FfiConverter<CallbackInterface, UniffiHandle> {
44-
internal val handleMap = ConcurrentHandleMap<CallbackInterface>()
9+
public abstract class FfiConverterCallbackInterface<CallbackInterface: Any>: FfiConverter<CallbackInterface, UniffiHandle> {
10+
internal val handleMap = UniffiHandleMap<CallbackInterface>()
4511

4612
internal fun drop(handle: UniffiHandle) {
4713
handleMap.remove(handle)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Handle from a UniffiHandleMap
2+
internal typealias UniffiHandle = Long
3+
4+
// Map handles to objects
5+
//
6+
// This is used pass an opaque 64-bit handle representing a foreign object to the Rust code.
7+
internal class UniffiHandleMap<T: Any> {
8+
private val map = ConcurrentHashMap<UniffiHandle, T>()
9+
private val counter = java.util.concurrent.atomic.AtomicLong(0)
10+
11+
val size: Int
12+
get() = map.size
13+
14+
// Insert a new object into the handle map and get a handle for it
15+
fun insert(obj: T): UniffiHandle {
16+
val handle = counter.getAndAdd(1)
17+
map.put(handle, obj)
18+
return handle
19+
}
20+
21+
// Get an object from the handle map
22+
fun get(handle: UniffiHandle): T {
23+
return map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle")
24+
}
25+
26+
// Remove an entry from the handlemap and get the Kotlin object back
27+
fun remove(handle: UniffiHandle): T {
28+
return map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle")
29+
}
30+
}

uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt

Lines changed: 0 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -77,51 +77,6 @@ private inline fun <U> uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U
7777
return uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, callback);
7878
}
7979

80-
// IntegerType that matches Rust's `usize` / C's `size_t`
81-
public class USize(value: Long = 0) : IntegerType(Native.SIZE_T_SIZE, value, true) {
82-
// This is needed to fill in the gaps of IntegerType's implementation of Number for Kotlin.
83-
override fun toByte() = toInt().toByte()
84-
// Needed until https://youtrack.jetbrains.com/issue/KT-47902 is fixed.
85-
@Deprecated("`toInt().toChar()` is deprecated")
86-
override fun toChar() = toInt().toChar()
87-
override fun toShort() = toInt().toShort()
88-
89-
fun writeToBuffer(buf: ByteBuffer) {
90-
// Make sure we always write usize integers using native byte-order, since they may be
91-
// casted to pointer values
92-
buf.order(ByteOrder.nativeOrder())
93-
try {
94-
when (Native.SIZE_T_SIZE) {
95-
4 -> buf.putInt(toInt())
96-
8 -> buf.putLong(toLong())
97-
else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}")
98-
}
99-
} finally {
100-
buf.order(ByteOrder.BIG_ENDIAN)
101-
}
102-
}
103-
104-
companion object {
105-
val size: Int
106-
get() = Native.SIZE_T_SIZE
107-
108-
fun readFromBuffer(buf: ByteBuffer) : USize {
109-
// Make sure we always read usize integers using native byte-order, since they may be
110-
// casted from pointer values
111-
buf.order(ByteOrder.nativeOrder())
112-
try {
113-
return when (Native.SIZE_T_SIZE) {
114-
4 -> USize(buf.getInt().toLong())
115-
8 -> USize(buf.getLong())
116-
else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}")
117-
}
118-
} finally {
119-
buf.order(ByteOrder.BIG_ENDIAN)
120-
}
121-
}
122-
}
123-
}
124-
12580
internal inline fun<T> uniffiTraitInterfaceCall(
12681
callStatus: UniffiRustCallStatus,
12782
makeCall: () -> T,
@@ -153,39 +108,3 @@ internal inline fun<T, reified E: Throwable> uniffiTraitInterfaceCallWithError(
153108
}
154109
}
155110
}
156-
157-
// Map handles to objects
158-
//
159-
// This is used when the Rust code expects an opaque pointer to represent some foreign object.
160-
// Normally we would pass a pointer to the object, but JNA doesn't support getting a pointer from an
161-
// object reference , nor does it support leaking a reference to Rust.
162-
//
163-
// Instead, this class maps USize values to objects so that we can pass a pointer-sized type to
164-
// Rust when it needs an opaque pointer.
165-
//
166-
// TODO: refactor callbacks to use this class
167-
internal class UniFfiHandleMap<T: Any> {
168-
private val map = ConcurrentHashMap<USize, T>()
169-
// Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible
170-
// values seems like enough. If somehow we generate 4 billion handles, then this will wrap
171-
// around back to zero and we can assume the first handle generated will have been dropped by
172-
// then.
173-
private val counter = java.util.concurrent.atomic.AtomicInteger(0)
174-
175-
val size: Int
176-
get() = map.size
177-
178-
fun insert(obj: T): USize {
179-
val handle = USize(counter.getAndAdd(1).toLong())
180-
map.put(handle, obj)
181-
return handle
182-
}
183-
184-
fun get(handle: USize): T? {
185-
return map.get(handle)
186-
}
187-
188-
fun remove(handle: USize): T? {
189-
return map.remove(handle)
190-
}
191-
}

uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@
9595
// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
9696
//
9797

98-
{{ self.add_import("java.util.concurrent.atomic.AtomicLong") }}
9998
{{ self.add_import("java.util.concurrent.atomic.AtomicBoolean") }}
10099
{%- if self.include_once_check("interface-support") %}
101100
{%- include "ObjectCleanerHelper.kt" %}
@@ -312,7 +311,7 @@ open class {{ impl_class_name }}: Disposable, AutoCloseable, {{ interface_name }
312311

313312
public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> {
314313
{%- if obj.has_callback_interface() %}
315-
internal val handleMap = ConcurrentHandleMap<{{ interface_name }}>()
314+
internal val handleMap = UniffiHandleMap<{{ type_name }}>()
316315
{%- endif %}
317316

318317
override fun lower(value: {{ type_name }}): Pointer {

uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import java.nio.ByteBuffer
3030
import java.nio.ByteOrder
3131
import java.nio.CharBuffer
3232
import java.nio.charset.CodingErrorAction
33+
import java.util.concurrent.atomic.AtomicLong
3334
import java.util.concurrent.ConcurrentHashMap
3435

3536
{%- for req in self.imports() %}
@@ -39,6 +40,7 @@ import java.util.concurrent.ConcurrentHashMap
3940
{% include "RustBufferTemplate.kt" %}
4041
{% include "FfiConverterTemplate.kt" %}
4142
{% include "Helpers.kt" %}
43+
{% include "HandleMap.kt" %}
4244

4345
// Contains loading, initialization code,
4446
// and the FFI Function declarations in a com.sun.jna.Library.

uniffi_bindgen/src/bindings/python/gen_python/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,6 @@ impl PythonCodeOracle {
372372
// Pointer to an `asyncio.EventLoop` instance
373373
FfiType::Reference(inner) => format!("ctypes.POINTER({})", self.ffi_type_label(inner)),
374374
FfiType::VoidPointer | FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(),
375-
FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(),
376375
}
377376
}
378377

uniffi_bindgen/src/bindings/python/templates/Async.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
_UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1
44

55
# Stores futures for _uniffi_continuation_callback
6-
_UniffiContinuationPointerManager = _UniffiPointerManager()
6+
_UniffiContinuationHandleMap = _UniffiHandleMap()
77

88
# Continuation callback for async functions
99
# lift the return value or error and resolve the future, causing the async function to resume.
1010
@UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK
1111
def _uniffi_continuation_callback(future_ptr, poll_code):
12-
(eventloop, future) = _UniffiContinuationPointerManager.release_pointer(future_ptr)
12+
(eventloop, future) = _UniffiContinuationHandleMap.remove(future_ptr)
1313
eventloop.call_soon_threadsafe(_uniffi_set_future_result, future, poll_code)
1414

1515
def _uniffi_set_future_result(future, poll_code):
@@ -26,7 +26,7 @@ async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free,
2626
ffi_poll(
2727
rust_future,
2828
_uniffi_continuation_callback,
29-
_UniffiContinuationPointerManager.new_pointer((eventloop, future)),
29+
_UniffiContinuationHandleMap.insert((eventloop, future)),
3030
)
3131
poll_code = await future
3232
if poll_code == _UNIFFI_RUST_FUTURE_POLL_READY:

uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,3 @@
1-
import threading
2-
3-
class ConcurrentHandleMap:
4-
"""
5-
A map where inserting, getting and removing data is synchronized with a lock.
6-
"""
7-
8-
def __init__(self):
9-
# type Handle = int
10-
self._left_map = {} # type: Dict[Handle, Any]
11-
12-
self._lock = threading.Lock()
13-
self._current_handle = 0
14-
self._stride = 1
15-
16-
def insert(self, obj):
17-
with self._lock:
18-
handle = self._current_handle
19-
self._current_handle += self._stride
20-
self._left_map[handle] = obj
21-
return handle
22-
23-
def get(self, handle):
24-
with self._lock:
25-
obj = self._left_map.get(handle)
26-
if not obj:
27-
raise InternalError("No callback in handlemap; this is a uniffi bug")
28-
return obj
29-
30-
def remove(self, handle):
31-
with self._lock:
32-
if handle in self._left_map:
33-
obj = self._left_map.pop(handle)
34-
return obj
35-
361
# Magic number for the Rust proxy to call using the same mechanism as every other method,
372
# to free the callback once it's dropped by Rust.
383
IDX_CALLBACK_FREE = 0
@@ -42,7 +7,7 @@ def remove(self, handle):
427
_UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2
438

449
class UniffiCallbackInterfaceFfiConverter:
45-
_handle_map = ConcurrentHandleMap()
10+
_handle_map = _UniffiHandleMap()
4611

4712
@classmethod
4813
def lift(cls, handle):
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
class _UniffiHandleMap:
2+
"""
3+
A map where inserting, getting and removing data is synchronized with a lock.
4+
"""
5+
6+
def __init__(self):
7+
# type Handle = int
8+
self._map = {} # type: Dict[Handle, Any]
9+
self._lock = threading.Lock()
10+
self._counter = itertools.count()
11+
12+
def insert(self, obj):
13+
with self._lock:
14+
handle = next(self._counter)
15+
self._map[handle] = obj
16+
return handle
17+
18+
def get(self, handle):
19+
try:
20+
with self._lock:
21+
return self._map[handle]
22+
except KeyError:
23+
raise InternalError("UniffiHandleMap.get: Invalid handle")
24+
25+
def remove(self, handle):
26+
try:
27+
with self._lock:
28+
return self._map.pop(handle)
29+
except KeyError:
30+
raise InternalError("UniffiHandleMap.remove: Invalid handle")
31+

uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def _uniffi_future_callback_t(return_type):
99
"""
1010
Factory function to create callback function types for async functions
1111
"""
12-
return ctypes.CFUNCTYPE(None, ctypes.c_size_t, return_type, _UniffiRustCallStatus)
12+
return ctypes.CFUNCTYPE(None, ctypes.c_uint64, return_type, _UniffiRustCallStatus)
1313

1414
def _uniffi_load_indirect():
1515
"""

uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def __ne__(self, other: object) -> {{ ne.return_type().unwrap()|type_name }}:
8484

8585
class {{ ffi_converter_name }}:
8686
{%- if obj.has_callback_interface() %}
87-
_handle_map = ConcurrentHandleMap()
87+
_handle_map = _UniffiHandleMap()
8888
{%- endif %}
8989

9090
@staticmethod

0 commit comments

Comments
 (0)