Skip to content

Add EventPipe interop instrumentation for wrapper lifecycle and GC-bridge reachability - #12258

Open
jkoritzinsky with Copilot wants to merge 2 commits into
mainfrom
copilot/add-eventpipe-instrumentation-dotnet-java-interop
Open

Add EventPipe interop instrumentation for wrapper lifecycle and GC-bridge reachability#12258
jkoritzinsky with Copilot wants to merge 2 commits into
mainfrom
copilot/add-eventpipe-instrumentation-dotnet-java-interop

Conversation

Copilot AI commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

This adds first-class EventPipe visibility into the .NET↔Java interop layer so wrapper lifecycle and cross-runtime reachability transitions can be observed in production traces. The instrumentation covers wrapper creation/release on both sides and GC-bridge reachability state transitions with correlation data.

  • New interop EventSource provider

    • Added a single provider: Java.Interop
    • Added stable event IDs/keywords for:
      • DotNetWrapperCreated
      • JavaWrapperCreated
      • DotNetWrapperReleasedJavaReference
      • JavaWrapperReleasedDotNetReference
      • DotNetObjectOnlyReachableFromJava
      • JavaObjectOnlyReachableFromDotNet
    • Standardized payload fields: managed type, Java type, JNI identity hash, managed object hash, runtime mode; reachability events also include component/context correlation fields.
  • Wrapper creation instrumentation

    • Emitted .NET wrapper for Java object events from:
      • ReflectionJniValueManager.CreatePeer
      • TrimmableTypeMapValueManager.CreatePeer
      • AndroidRuntime.AndroidValueManager.CreatePeer
    • Emitted Java wrapper for .NET object events from:
      • ManagedPeer.Construct
      • ConstructPeerCore paths where Java peer references are established
  • Wrapper release instrumentation

    • Emitted .NET wrapper releases Java reference from disposal/finalization paths:
      • JniValueManager.DisposePeer
      • JavaMarshalRegisteredPeers.FinalizePeer
    • Emitted Java wrapper releases .NET reference from registered-peer removal:
      • JavaMarshalRegisteredPeers.RemovePeer
  • GC-bridge reachability instrumentation

    • Emitted reachability-transition events from JavaMarshalRegisteredPeers bridge processing:
      • .NET object only reachable from Java
      • Java object only reachable from .NET
    • Included bridge round correlation data (componentIndex, contextIndex, contextPointer) for trace-side reconstruction.
  • Low-overhead event gating

    • Guarded all instrumentation paths with InteropEventSource.IsEnabled() before payload-heavy work (e.g., Java type resolution) to avoid unnecessary overhead when no listener is attached.
  • API, tests, and docs

    • Added public API entries for InteropEventSource.
    • Added Java.Interop unit tests validating event emission and payload shape.
    • Added runtime-facing tests in Mono.Android for lifecycle event behavior.
    • Added event catalog and dotnet-trace usage documentation.
if (InteropEventSource.IsEnabled ()) {
	InteropEventSource.DotNetWrapperCreated (
		peer.GetType ().FullName,
		javaType,
		peer.JniIdentityHashCode,
		RuntimeHelpers.GetHashCode (peer),
		GetRuntimeMode ());
}

Copilot AI and others added 2 commits July 28, 2026 00:17
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces EventPipe instrumentation across the .NET↔Java interop layer to make wrapper lifecycle and GC-bridge reachability transitions observable in production traces via a new Java.Interop EventSource provider.

Changes:

  • Added a new Java.Interop EventSource (InteropEventSource) with stable event IDs/keywords and a documented payload schema.
  • Instrumented wrapper creation/release in Java.Interop value managers + Mono.Android runtime, and added GC-bridge reachability transition events with correlation fields.
  • Added unit tests (Java.Interop) and runtime-facing tests (Mono.Android) plus documentation for capturing with dotnet-trace.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/InteropEventSourceRuntimeTests.cs Runtime tests validating lifecycle events are emitted on-device/in-runtime.
src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs Emits wrapper creation events for trimmable typemap value manager paths.
src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalRegisteredPeers.cs Emits wrapper release + GC-bridge reachability events during bridge processing.
src/Mono.Android/Android.Runtime/AndroidRuntime.cs Emits wrapper creation events for AndroidValueManager.CreatePeer.
external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/InteropEventSourceTests.cs Unit tests validating EventSource emission and payload shape.
external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt Declares new public API surface for InteropEventSource.
external/Java.Interop/src/Java.Interop/Java.Interop/ManagedPeer.cs Emits JavaWrapperCreated during managed-peer construction from Java.
external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs Emits wrapper creation events in reflection-based value manager.
external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs Emits wrapper release events during peer disposal.
external/Java.Interop/src/Java.Interop/Java.Interop/InteropEventSource.cs New provider implementation + event definitions/keywords.
external/Java.Interop/Documentation/EventPipeInteropEvents.md Event catalog, payload schema, and dotnet-trace collection instructions.


peer.SetPeerReference (newRef);
peer.SetJniIdentityHashCode (JniEnvironment.References.GetIdentityHashCode (newRef));
if (!peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Activatable) && InteropEventSource.IsEnabled ()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can InteropEventSource.IsEnabled () be backed by a trimmer feature switch that is off by default? We have a RuntimeFeature.cs where we enable these.

Comment on lines +81 to +83
if (!peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Activatable) && InteropEventSource.IsEnabled ()) {
EmitJavaWrapperCreatedEvent (peer, newRef);
}
Comment on lines +195 to 200
if (InteropEventSource.IsEnabled ()) {
EmitJavaWrapperReleasedDotNetReference (value);
}
peers.RemoveAt (i);
peer.Dispose ();
}
Comment on lines +14 to +17
public static bool IsEnabled ()
{
return source.IsEnabled ();
}
Comment on lines +123 to +126
string GetRuntimeMode ()
{
return Runtime.GetType ().FullName ?? "Unknown";
}
Comment on lines +119 to +127
if (InteropEventSource.IsEnabled ()) {
var javaType = JniEnvironment.Types.GetJniTypeNameFromInstance (h);
InteropEventSource.DotNetWrapperReleasedJavaReference (
value.GetType ().FullName,
javaType,
value.JniIdentityHashCode,
RuntimeHelpers.GetHashCode (value),
Runtime.GetType ().FullName);
}
Comment on lines +109 to +117
if (InteropEventSource.IsEnabled ()) {
var managedObjectHashCode = self != null ? RuntimeHelpers.GetHashCode (self) : 0;
InteropEventSource.JavaWrapperCreated (
type.FullName,
typeSig.SimpleReference,
runtime.ValueManager.GetJniIdentityHashCode (r_self),
managedObjectHashCode,
runtime.GetType ().FullName);
}

namespace Java.Interop
{
public static class InteropEventSource

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be public? I'd rather users opt into this logging with settings (either MSBuild property/trimmer switch or Android system property)

And then we'd need no public APIs at all?

Comment on lines +19 to +25
public static void DotNetWrapperCreated (
string? managedType,
string? javaType,
int jniIdentityHashCode,
int managedObjectHashCode,
string? runtimeMode)
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming choices here seem odd, e.g. DotNetWrapper. Can we use pre-existing terminology? You might look in external/java.interop/docs/ for examples.


peer.SetPeerReference (newRef);
peer.SetJniIdentityHashCode (JniEnvironment.References.GetIdentityHashCode (newRef));
if (!peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Activatable) && InteropEventSource.IsEnabled ()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can InteropEventSource.IsEnabled () be backed by a trimmer feature switch that is off by default? We have a RuntimeFeature.cs where we enable these.

Comment on lines +3 to +16
The .NET ↔ Java interop layer emits EventPipe events through a single provider:

- **Provider name:** `Java.Interop`

## Event catalog

| Event ID | Event name | Meaning |
|---|---|---|
| 1 | `DotNetWrapperCreated` | A managed wrapper for a Java object was created. |
| 2 | `JavaWrapperCreated` | A Java wrapper for a managed object was created. |
| 3 | `DotNetWrapperReleasedJavaReference` | A managed wrapper released its Java reference. |
| 4 | `JavaWrapperReleasedDotNetReference` | A Java wrapper released its managed reference. |
| 5 | `DotNetObjectOnlyReachableFromJava` | A managed object is only reachable from Java during bridge processing. |
| 6 | `JavaObjectOnlyReachableFromDotNet` | A Java object is only reachable from .NET during bridge processing. |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is some existing code in here for writing gref/lref logs. It is old and text-based. Does this replace or supplement this?

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.

4 participants