Skip to content

Proposal: call partial methods within method bindings #1085

Closed
@jonpryor

Description

@jonpryor

Context: dotnet/runtime#82121

Background: When should the GC run? If a "large" object is allocated, should that change the GC heuristics for when it runs?

If someone allocates a large Bitmap, should we GC more often on devices with low memory?

We've had questions around this for quite some time, but the only "reasonable" way to alter GC heuristics was by using GC.AddMemoryPressure() and GC.RemoveMemoryPressure(), but Mono didn't support these methods, so it was all moot: there was no way to inform the GC about "implicit" non-managed memory allocations, so we didn't.

That is now changing with dotnet/runtime#82121

Meaning there is now a way to tell our GC about implicit non-managed memory usage!

Background: Binding: Consider the current (abbreviated) binding for Android.Grpahics.Bitmap:

namespace Android.Graphics {
	public sealed partial class Bitmap {
		internal Bitmap (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer)
		{
		}

		public unsafe int AllocationByteCount {
			get {
				const string __id = "getAllocationByteCount.()I";
				try {
					var __rm = _members.InstanceMethods.InvokeAbstractInt32Method (__id, this, null);
					return __rm;
				} finally {
				}
			}
		}

		public static unsafe Android.Graphics.Bitmap? CreateBitmap (Android.Graphics.Bitmap src)
		{
			const string __id = "createBitmap.(Landroid/graphics/Bitmap;)Landroid/graphics/Bitmap;";
			try {
				JniArgumentValue* __args = stackalloc JniArgumentValue [1];
				__args [0] = new JniArgumentValue ((src == null) ? IntPtr.Zero : ((global::Java.Lang.Object) src).Handle);
				var __rm = _members.StaticMethods.InvokeObjectMethod (__id, __args);
				return global::Java.Lang.Object.GetObject<Android.Graphics.Bitmap> (__rm.Handle, JniHandleOwnership.TransferLocalRef);
			} finally {
				global::System.GC.KeepAlive (src);
			}
		}
	}
}

What would we like to do? We'd like to "alter" certain methods so that we can "reasonably" tell the GC "this instance has x bytes of associated memory".

How do we do that?

Proposal: Update generator to emit C# partial methods for each bound member and constructor, allowing partial classes to be written which allow "extra work" to be done when the bound method is executed.

Open question: How many partial methods should we have, and where should they be called from?

  • One, before/after the JNI call? (Which one?)
  • Two, on member entry & exit?
  • What should the name convention be? "Obvious" is _On prefix + member name + some kind of suffix. What about overloads? Should we care about overloads, or have all overloads invoke the same set of partial methods?

How many such partial methods do we need, anyway? While trying to write up the following example, it occurs to me that, while having Bitmap.CreateBitmap() call GC.AddMemoryPresure() "makes sense", it doesn't really make sense becaus it calls Object.GetObject<Bitmap>(), which means the Bitmap(IntPtr, JniHandleOwnership) constructor will be invoked. This suggests we may only need one such partial method here, for constructors, and all other members aren't needed for this use case.

Example: Bitmap with partial methods!

namespace Android.Graphics {
	public sealed partial class Bitmap {
		partial void _OnConstructor ();

		internal Bitmap (IntPtr javaReference, JniHandleOwnership transfer) : base (javaReference, transfer)
		{
			_OnConstructor ();
		}

		partial void _OnAllocationByteCount_Entry ();
		partial void _OnAllocationByteCount_Exit (int ret);

		public unsafe int AllocationByteCount {
			get {
				const string __id = "getAllocationByteCount.()I";
				try {
					_OnAllocationByteCount_Entry ();
					var __rm = _members.InstanceMethods.InvokeAbstractInt32Method (__id, this, null);
					var __ret = __rm;
					_OnAllocationByteCount_Exit (__ret);
					return __ret;
				} finally {
				}
			}
		}

		partial void _OnCreateBitmap_Entry (Android.Graphics.Bitmap src);
		partial void _OnCreateBitmap_Exit (Android.Graphics.Bitmap ret);

		public static unsafe Android.Graphics.Bitmap? CreateBitmap (Android.Graphics.Bitmap src)
		{
			_OnCreateBitmap_Entry (src);
			const string __id = "createBitmap.(Landroid/graphics/Bitmap;)Landroid/graphics/Bitmap;";
			try {
				JniArgumentValue* __args = stackalloc JniArgumentValue [1];
				__args [0] = new JniArgumentValue ((src == null) ? IntPtr.Zero : ((global::Java.Lang.Object) src).Handle);
				var __rm = _members.StaticMethods.InvokeObjectMethod (__id, __args);
				var __ret = global::Java.Lang.Object.GetObject<Android.Graphics.Bitmap> (__rm.Handle, JniHandleOwnership.TransferLocalRef);
				_OnCreateBitmap_Exit (__ret);
				return __ret;
			} finally {
				global::System.GC.KeepAlive (src);
			}
		}
	}
}

Once such partial methods exist, we can "reasonably" update Mono.Android.dll to implement required partial methods to Do Something Useful:

partial class Bitmap {

	partial void _OnConstructor ()
	{
		GC.AddMemoryPressure (ByteCount);
	}

	protected override void Dispose (bool disposing)
	{
		if (Handle != IntPtr.Zero) {
			GC.RemoveMemoryPressure (ByteCount);
		}
	}
}

Meta-question: is overriding Dispose() "sufficient enough" in practice for calling GC.RemoveMemoryPressure()? Or could this result in constantly increasing memory pressure?

Metadata

Metadata

Assignees

No one assigned

    Labels

    generatorIssues binding a Java library (generator, class-parse, etc.)proposalIssue raised for discussion, we do not even know if the change would be desirable yet

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions