# RustFuturePoll values _UNIFFI_RUST_FUTURE_POLL_READY = 0 _UNIFFI_RUST_FUTURE_POLL_WAKE = 1 # Stores futures for _uniffi_continuation_callback _UniffiContinuationHandleMap = _UniffiHandleMap() _UNIFFI_GLOBAL_EVENT_LOOP = None """ Set the event loop to use for async functions This is needed if some async functions run outside of the eventloop, for example: - A non-eventloop thread is spawned, maybe from `EventLoop.run_in_executor` or maybe from the Rust code spawning its own thread. - The Rust code calls an async callback method from a sync callback function, using something like `pollster` to block on the async call. In this case, we need an event loop to run the Python async function, but there's no eventloop set for the thread. Use `uniffi_set_event_loop` to force an eventloop to be used in this case. """ def uniffi_set_event_loop(eventloop: asyncio.BaseEventLoop): global _UNIFFI_GLOBAL_EVENT_LOOP _UNIFFI_GLOBAL_EVENT_LOOP = eventloop def _uniffi_get_event_loop(): if _UNIFFI_GLOBAL_EVENT_LOOP is not None: return _UNIFFI_GLOBAL_EVENT_LOOP else: return asyncio.get_running_loop() # Continuation callback for async functions # lift the return value or error and resolve the future, causing the async function to resume. @_UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK def _uniffi_continuation_callback(future_ptr, poll_code): (eventloop, future) = _UniffiContinuationHandleMap.remove(future_ptr) eventloop.call_soon_threadsafe(_uniffi_set_future_result, future, poll_code) def _uniffi_set_future_result(future, poll_code): if not future.cancelled(): future.set_result(poll_code) async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free, lift_func, error_ffi_converter): try: eventloop = _uniffi_get_event_loop() # Loop and poll until we see a _UNIFFI_RUST_FUTURE_POLL_READY value while True: future = eventloop.create_future() ffi_poll( rust_future, _uniffi_continuation_callback, _UniffiContinuationHandleMap.insert((eventloop, future)), ) poll_code = await future if poll_code == _UNIFFI_RUST_FUTURE_POLL_READY: break return lift_func( _uniffi_rust_call_with_error(error_ffi_converter, ffi_complete, rust_future) ) finally: ffi_free(rust_future) {%- if has_async_callback_method %} def _uniffi_trait_interface_call_async(make_call, uniffi_out_dropped_callback, handle_success, handle_error): async def make_call_and_call_callback(): # Note: it's important we call either `handle_success` or `handle_error` exactly once. Each # call consumes an Arc reference, which means there should be no possibility of a double # call. The following code is structured so that will will never call both `handle_success` # and `handle_error`, even in the face of weird exceptions. # # In extreme circumstances we may not call either, for example if we fail to make the ctypes # call to `handle_success`. This means we will leak the Arc reference, which is better than # double-freeing it. try: call_result = await make_call() except Exception as e: print("UniFFI: Unhandled exception in trait interface call", file=sys.stderr) traceback.print_exc(file=sys.stderr) handle_error( _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR, {{ string_type_node.ffi_converter_name }}.lower(repr(e)), ) else: handle_success(call_result) eventloop = _uniffi_get_event_loop() task = asyncio.run_coroutine_threadsafe(make_call_and_call_callback(), eventloop) handle = _UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert((eventloop, task)) uniffi_out_dropped_callback[0] = _UniffiForeignFutureDroppedCallbackStruct(handle, _uniffi_future_dropped_callback) def _uniffi_trait_interface_call_async_with_error(make_call, uniffi_out_dropped_callback, handle_success, handle_error, error_type, lower_error): async def make_call_and_call_callback(): # See the note in _uniffi_trait_interface_call_async for details on `handle_success` and # `handle_error`. try: try: call_result = await make_call() except error_type as e: handle_error( _UniffiRustCallStatus.CALL_ERROR, lower_error(e), ) else: handle_success(call_result) except Exception as e: print("UniFFI: Unhandled exception in trait interface call", file=sys.stderr) traceback.print_exc(file=sys.stderr) handle_error( _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR, {{ string_type_node.ffi_converter_name }}.lower(repr(e)), ) eventloop = _uniffi_get_event_loop() task = asyncio.run_coroutine_threadsafe(make_call_and_call_callback(), eventloop) handle = _UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert((eventloop, task)) uniffi_out_dropped_callback[0] = _UniffiForeignFutureDroppedCallbackStruct(handle, _uniffi_future_dropped_callback) _UNIFFI_FOREIGN_FUTURE_HANDLE_MAP = _UniffiHandleMap() @_UNIFFI_FOREIGN_FUTURE_DROPPED_CALLBACK def _uniffi_future_dropped_callback(handle): (eventloop, task) = _UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.remove(handle) eventloop.call_soon(_uniffi_cancel_task, task) def _uniffi_cancel_task(task): if not task.done(): task.cancel() {%- endif %}