Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Java.Interop-Tests] Add CreatePeer_ReplaceableDoesNotReplace test #1323

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

jonpryor
Copy link
Member

Context: 3043d89
Context: dotnet/android#9862
Context: dotnet/android#9862 (comment)

In dotnet/android#9862, there is an observed "race condition" around Android.App.Application subclass creation. Two instances of AndroidApp were created, one from the "normal" app startup:

at crc647fae2f69c19dcd0d.AndroidApp.n_onCreate(Native Method)
at crc647fae2f69c19dcd0d.AndroidApp.onCreate(AndroidApp.java:25)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1316)

and another from an androidx.work.WorkerFactory:

at mono.android.TypeManager.n_activate(Native Method)
at mono.android.TypeManager.Activate(TypeManager.java:7)
at crc647fae2f69c19dcd0d.SyncWorker.<init>(SyncWorker.java:23)
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.java:95)

However, what was odd about this "race condition" was that the second instance created would reliably win!

Further investigation suggested that this was less of a "race condition" and more a bug in AndroidRuntime, wherein when "Replaceable" instances were created, an existing instance would always be replaced.

Aside: JniManagedPeerStates.Replaceable is from 3043d89:

JniManagedPeerStates.Replaceable … means
that the Peer instance was created through the activation constructor.
It additionally means that if two managed instances are created around
the same Java instance, the non-Replaceable instance will be the one
returned by JniRuntime.JniValueManager.PeekObject().

What we're observing in dotnet/android#9862 is that while the Replaceable instance is replaced, it's being replaced by another Replaceable instance! This feels bananas; yes, Replaceable should be replacable, but only by non-Replaceable instances.

Update JniRuntimeJniValueManagerContract to add a new CreatePeer_ReplaceableDoesNotReplace() test to codify the desired semantic that Replaceable instances do not replace Replaceable instances.

Surprisingly, this does not fail on java-interop! Apparently ManagedValueManager.AddPeer() bails early when PeekPeer() finds a value, while AndroidRuntime.AddPeer() does not bail early.

Context: 3043d89
Context: dotnet/android#9862
Context: dotnet/android#9862 (comment)

In dotnet/android#9862, there is an observed "race condition" around
`Android.App.Application` subclass creation.  *Two* instances of
`AndroidApp` were created, one from the "normal" app startup:

	at crc647fae2f69c19dcd0d.AndroidApp.n_onCreate(Native Method)
	at crc647fae2f69c19dcd0d.AndroidApp.onCreate(AndroidApp.java:25)
	at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1316)

and another from an `androidx.work.WorkerFactory`:

	at mono.android.TypeManager.n_activate(Native Method)
	at mono.android.TypeManager.Activate(TypeManager.java:7)
	at crc647fae2f69c19dcd0d.SyncWorker.<init>(SyncWorker.java:23)
	at java.lang.reflect.Constructor.newInstance0(Native Method)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
	at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.java:95)

However, what was odd about this "race condition" was that the
*second* instance created would reliably win!

Further investigation suggested that this was less of a
"race condition" and more a bug in `AndroidRuntime`, wherein when
"Replaceable" instances were created, an existing instance would
*always* be replaced.

Aside: JniManagedPeerStates.Replaceable is from 3043d89:

> `JniManagedPeerStates.Replaceable` … means
> that the Peer instance was created through the activation constructor.
> It additionally means that if two managed instances are created around
> the same Java instance, the non-Replaceable instance will be the one
> returned by JniRuntime.JniValueManager.PeekObject().

What we're observing in dotnet/android#9862 is that while the
Replaceable instance is replaced, it's being replaced by *another*
Replaceable instance!  This feels bananas; yes, Replaceable should
be replacable, but only by *non*-Replaceable instances.

Update `JniRuntimeJniValueManagerContract` to add a new
`CreatePeer_ReplaceableDoesNotReplace()` test to codify the desired
semantic that Replaceable instances do not replace Replaceable
instances.

Surprisingly, this does not fail on java-interop!  Apparently
`ManagedValueManager.AddPeer()` bails early when `PeekPeer()` finds
a value, while `AndroidRuntime.AddPeer()` does not bail early.
jonpryor added a commit to dotnet/android that referenced this pull request Mar 14, 2025
Context: dotnet/java-interop#1323
Context: #9862 (comment)

Does It Build™?

(The expectation is that it *does* build -- only unit tests are changed
in dotnet/java-interop#1323 -- but that the new
`JniRuntimeJniValueManagerContract.cs.CreatePeer_ReplaceableDoesNotReplace()`
test will fail.)`
@Redbone8686
Copy link

Write access

@Redbone8686
Copy link

Write access

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants