Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
41fc243
CI run is working again, fix formatting
skeet70 Apr 4, 2025
ab40f45
Sort deps
skeet70 Apr 4, 2025
ac7d02b
Only the cancel, most likely problem?
skeet70 Apr 4, 2025
a3dc848
Add one of the errors back in
skeet70 Apr 4, 2025
cc446cb
Got failure with cancel + error, try just error
skeet70 Apr 4, 2025
45c7367
Switch to a different error case.
skeet70 Apr 4, 2025
d589263
Add a long delay after the error
skeet70 Apr 4, 2025
7bd633d
Dropping delay again
skeet70 Apr 4, 2025
8d93bc3
Add more info about timing
skeet70 Apr 4, 2025
65bcca1
Log in the free
skeet70 Apr 4, 2025
9a9a0dc
Remove only exceptional cases for some runs
skeet70 Apr 4, 2025
8e59d53
lots more logging
skeet70 Apr 7, 2025
a54f31c
Sorted
skeet70 Apr 7, 2025
d28f1f9
Add all the trait tests back in
skeet70 Apr 8, 2025
3723cc2
Try backing out uniffi logging to get reproduction back
skeet70 Apr 8, 2025
57caf86
Switch back to uniffi proper, and non-busy loop poll
skeet70 Apr 8, 2025
304d307
All the way back, drop logging
skeet70 Apr 8, 2025
f0bd0e4
Missed printlns
skeet70 Apr 8, 2025
38310fd
Stall for 10s before counting handles
skeet70 Apr 8, 2025
4770c17
What if more time
skeet70 Apr 8, 2025
43b0c6b
Back to busy waiting
skeet70 Apr 8, 2025
cf8e033
Add back in busy-wait in poll
skeet70 Apr 8, 2025
f6adc27
Catch the timeout
skeet70 Apr 8, 2025
eb51402
Don't need the polling log, can see from the time
skeet70 Apr 8, 2025
7b3add3
Make uniffi noisy again
skeet70 Apr 8, 2025
019b57a
Don't stall now that we know it doesn't fix with time
skeet70 Apr 8, 2025
c8da9c8
Try to narrow tests again
skeet70 Apr 8, 2025
cd46622
Couldn't reproduce without all tests
skeet70 Apr 8, 2025
b0d584b
Reproduced but still noisy, try again with less noise
skeet70 Apr 8, 2025
b9409aa
Try all adjacent tests, but only one trait test
skeet70 Apr 9, 2025
6717541
Add superficial java logging
skeet70 Apr 9, 2025
92d409c
Still reproducing, add some more logging
skeet70 Apr 9, 2025
1d98061
callback logging
skeet70 Apr 9, 2025
9f1ca50
Push up more logging around trait calls
skeet70 Apr 10, 2025
bcfc747
Log when FF is dropped on the rust side
skeet70 Apr 10, 2025
7a1ad30
Log if the FreeImpl is GCd to try to catch its weaker reference
skeet70 Apr 10, 2025
d6c8453
Try to log when GCd
skeet70 Apr 10, 2025
f37e22f
Test out the fix, singleton shouldn't ever get GCd
skeet70 Apr 10, 2025
7537504
Move the cancelable future up so it's in scope always
skeet70 Apr 10, 2025
f30e84e
Drop a bunch of logging
skeet70 Apr 10, 2025
6916196
Void test still failing because we're too slow
skeet70 Apr 10, 2025
ec06e25
Remove dead comment
skeet70 Apr 10, 2025
1f57c14
Remove dead import
skeet70 Apr 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
758 changes: 273 additions & 485 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ path = "src/main.rs"

[dependencies]
anyhow = "1"
rinja = { version = "0.3", default-features = false, features = ["config"] }
camino = "1.1.6"
cargo_metadata = "0.15"
clap = { version = "4", default-features = false, features = [
Expand All @@ -44,6 +43,7 @@ heck = "0.5"
once_cell = "1.19.0"
paste = "1"
regex = "1.10.4"
rinja = { version = "0.3", default-features = false, features = ["config"] }
serde = "1"
textwrap = "0.16.1"
toml = "0.5" # can't be on 8, `Value` is part of public interface
Expand Down
2 changes: 1 addition & 1 deletion src/gen_java/callback_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use super::{potentially_add_external_package, CodeType, Config};
use super::{CodeType, Config, potentially_add_external_package};
use crate::ComponentInterface;

#[derive(Debug)]
Expand Down
2 changes: 1 addition & 1 deletion src/gen_java/compounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

use super::{AsCodeType, CodeType, Config};
use uniffi_bindgen::{
backend::{Literal, Type},
ComponentInterface,
backend::{Literal, Type},
};

#[derive(Debug)]
Expand Down
2 changes: 1 addition & 1 deletion src/gen_java/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use super::{potentially_add_external_package, CodeType, Config};
use super::{CodeType, Config, potentially_add_external_package};
use crate::ComponentInterface;

#[derive(Debug)]
Expand Down
2 changes: 1 addition & 1 deletion src/gen_java/enum_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use super::{potentially_add_external_package, CodeType, Config};
use super::{CodeType, Config, potentially_add_external_package};
use crate::ComponentInterface;
use uniffi_bindgen::backend::Literal;

Expand Down
4 changes: 2 additions & 2 deletions src/gen_java/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use super::{potentially_add_external_package, CodeType, Config};
use uniffi_bindgen::{interface::ObjectImpl, ComponentInterface};
use super::{CodeType, Config, potentially_add_external_package};
use uniffi_bindgen::{ComponentInterface, interface::ObjectImpl};

#[derive(Debug)]
pub struct ObjectCodeType {
Expand Down
2 changes: 1 addition & 1 deletion src/gen_java/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use super::{potentially_add_external_package, CodeType, Config};
use super::{CodeType, Config, potentially_add_external_package};
use uniffi_bindgen::ComponentInterface;

#[derive(Debug)]
Expand Down
2 changes: 1 addition & 1 deletion src/gen_java/variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use super::{potentially_add_external_package, AsCodeType, CodeType, Config, JavaCodeOracle};
use super::{AsCodeType, CodeType, Config, JavaCodeOracle, potentially_add_external_package};
use uniffi_bindgen::interface::{ComponentInterface, Variant};

#[derive(Debug)]
Expand Down
56 changes: 32 additions & 24 deletions src/templates/Async.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public final class UniffiAsyncHelpers {
static final byte UNIFFI_RUST_FUTURE_POLL_READY = (byte) 0;
static final byte UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = (byte) 1;
static final UniffiHandleMap<CompletableFuture<Byte>> uniffiContinuationHandleMap = new UniffiHandleMap<>();
static final UniffiHandleMap<CompletableFuture<Void>> uniffiForeignFutureHandleMap = new UniffiHandleMap<>();
static final UniffiHandleMap<CancelableForeignFuture> uniffiForeignFutureHandleMap = new UniffiHandleMap<>();

// FFI type for Rust future continuations
enum UniffiRustFutureContinuationCallbackImpl implements UniffiRustFutureContinuationCallback {
Expand Down Expand Up @@ -49,6 +49,26 @@ public boolean cancel(boolean ignored) {
}
}

// helper so both the Java completable future and the job that handles it finishing and reports to Rust can be
// retrieved (and potentially cancelled) by handle. This allows our FreeImpl to be a parameterless singleton,
// preventing #19, which was caused by our FreeImpls being GCd before Rust called back into them.
static class CancelableForeignFuture {
private CompletableFuture<?> childFuture;
private CompletableFuture<Void> childFutureHandler;

CancelableForeignFuture(CompletableFuture<?> childFuture, CompletableFuture<Void> childFutureHandler) {
this.childFuture = childFuture;
this.childFutureHandler = childFutureHandler;
}

public void cancel() {
var successfullyCancelled = this.childFutureHandler.cancel(true);
if(successfullyCancelled) {
childFuture.cancel(true);
}
}
}

static <T, F, E extends Exception> CompletableFuture<T> uniffiRustCallAsync(
long rustFuture,
PollingFunction pollFunc,
Expand Down Expand Up @@ -131,11 +151,7 @@ private static byte poll(long rustFuture, PollingFunction pollFunc) throws Inter
CompletableFuture<Byte> pollFuture = new CompletableFuture<>();
var handle = uniffiContinuationHandleMap.insert(pollFuture);
pollFunc.apply(rustFuture, UniffiRustFutureContinuationCallbackImpl.INSTANCE, handle);

// busy-wait until the poll completes
// TODO(java): may be more efficient to use a CountdownLatch here instead of a CF we end up busy-waiting
// because of Java bugs.
do {} while (!pollFuture.isDone());
do {} while (!pollFuture.isDone()); // removing this makes futures not cancel (sometimes)
return pollFuture.get();
}

Expand All @@ -148,7 +164,7 @@ static <T> UniffiForeignFuture uniffiTraitInterfaceCallAsync(
// Uniffi does its best to support structured concurrency across the FFI.
// If the Rust future is dropped, `UniffiForeignFutureFreeImpl` is called, which will cancel the Java completable future if it's still running.
var foreignFutureCf = makeCall.get();
CompletableFuture<Void> job = CompletableFuture.supplyAsync(() -> {
CompletableFuture<Void> ffHandler = CompletableFuture.supplyAsync(() -> {
try {
foreignFutureCf.thenAcceptAsync(handleSuccess).get();
} catch(Throwable e) {
Expand All @@ -166,9 +182,8 @@ static <T> UniffiForeignFuture uniffiTraitInterfaceCallAsync(

return null;
});

long handle = uniffiForeignFutureHandleMap.insert(job);
return new UniffiForeignFuture(handle, new UniffiForeignFutureFreeImpl<T>(foreignFutureCf));
long handle = uniffiForeignFutureHandleMap.insert(new CancelableForeignFuture(foreignFutureCf, ffHandler));
return new UniffiForeignFuture(handle, UniffiForeignFutureFreeImpl.INSTANCE);
}

@SuppressWarnings("unchecked")
Expand All @@ -180,7 +195,7 @@ static <T, E extends Throwable> UniffiForeignFuture uniffiTraitInterfaceCallAsyn
Class<E> errorClass
){
var foreignFutureCf = makeCall.get();
CompletableFuture<Void> job = CompletableFuture.supplyAsync(() -> {
CompletableFuture<Void> ffHandler = CompletableFuture.supplyAsync(() -> {
try {
foreignFutureCf.thenAcceptAsync(handleSuccess).get();
} catch (Throwable e) {
Expand Down Expand Up @@ -208,24 +223,17 @@ static <T, E extends Throwable> UniffiForeignFuture uniffiTraitInterfaceCallAsyn
return null;
});

long handle = uniffiForeignFutureHandleMap.insert(job);
return new UniffiForeignFuture(handle, new UniffiForeignFutureFreeImpl<T>(foreignFutureCf));
long handle = uniffiForeignFutureHandleMap.insert(new CancelableForeignFuture(foreignFutureCf, ffHandler));
return new UniffiForeignFuture(handle, UniffiForeignFutureFreeImpl.INSTANCE);
}

static class UniffiForeignFutureFreeImpl<T> implements UniffiForeignFutureFree {
private CompletableFuture<T> childFuture;

UniffiForeignFutureFreeImpl(CompletableFuture<T> childFuture) {
this.childFuture = childFuture;
}
enum UniffiForeignFutureFreeImpl implements UniffiForeignFutureFree {
INSTANCE;

@Override
public void callback(long handle) {
var job = uniffiForeignFutureHandleMap.remove(handle);
var successfullyCancelled = job.cancel(true);
if(successfullyCancelled) {
childFuture.cancel(true);
}
var futureWithHandler = uniffiForeignFutureHandleMap.remove(handle);
futureWithHandler.cancel();
}
}

Expand Down
5 changes: 3 additions & 2 deletions tests/scripts/TestFixtureFutures/TestFixtureFutures.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public static long measureTimeMillis(FutureRunnable r) {

public static void assertReturnsImmediately(long actualTime, String testName) {
// TODO(java): 4ms limit in Kotlin
assert actualTime <= 20 : MessageFormat.format("unexpected {0} time: {1}ms", testName, actualTime);
assert actualTime <= 40 : MessageFormat.format("unexpected {0} time: {1}ms", testName, actualTime);
}

public static void assertApproximateTime(long actualTime, int expectedTime, String testName) {
Expand Down Expand Up @@ -230,6 +230,8 @@ public CompletableFuture<Void> tryDelay(String delayMs) {
}

var traitObj = new JavaAsyncParser();
var startingHandleCount = UniffiAsyncHelpers.uniffiForeignFutureHandleCount();
assert startingHandleCount == 0 : MessageFormat.format("{0} starting handle count != 0", startingHandleCount);
assert Futures.asStringUsingTrait(traitObj, 1, 42).get().equals("42");
assert Futures.tryFromStringUsingTrait(traitObj, 1, "42").get().equals(42);
try {
Expand Down Expand Up @@ -271,7 +273,6 @@ public CompletableFuture<Void> tryDelay(String delayMs) {
assert traitObj.completedDelays == completedDelaysBefore : MessageFormat.format("{0} current delays != {1} delays before", traitObj.completedDelays, completedDelaysBefore);

// Test that all handles were cleaned up
System.gc();
var endingHandleCount = UniffiAsyncHelpers.uniffiForeignFutureHandleCount();
assert endingHandleCount == 0 : MessageFormat.format("{0} current handle count != 0", endingHandleCount);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use camino::{Utf8Path, Utf8PathBuf};
use cargo_metadata::{MetadataCommand, Package, Target};
use std::env::consts::ARCH;
Expand Down