Skip to content

[API Proposal]: NoGC callback #66039

Closed
@cshung

Description

@cshung

Background and motivation

When people use the TryStartNoGCRegion method, in general, they are trying to prevent a GC from happening. However, this can be done as much as the allocation stays within the totalSize specified upfront.

Ensuring the application does not allocate more than totalSize is difficult, if not impossible. Right now, the GC will perform a GC when the totalSize is exceeded and the GC will have to garbage collect. This is okay from some applications, but for some others, it might not.

API Proposal

Customers are asking for a callback when that happens and let the customer decide if we should

  1. Simply terminate the process as OutOfMemory.
  2. Allocate more memory and let the process continue, or
  3. Perform a GC and let the process continue.

We had some discussions (see below) to explore the actual use case and implementation constraints, and we discovered a key conflict between them.

From the requirements perspective, we would like to have a callback when the memory is exhausted so that we can gracefully terminate the game without a GC, but

From the implementation perspective, we cannot serve a callback that could potentially allocate more memory without a GC while we have already exhausted our pre-allocated memory.

To resolve this conflict, we notice that exactly exhausting the memory is not necessary from the requirement side. As long as we have a callback issued so that the game can gracefully terminate, this is good enough.

We also notice that nobody need the process to be terminated right away.

Therefore, I revised the proposal as follow:

API Usage

void OnBeforeEndNoGCRegion()
{
  /*
   * This callback will be invoked on the finalizer thread.
   * At this point, you have used 256M memory since you started the 
   * NoGCRegion, you have 4M to go before the NoGCRegion really ends
   * 
   * Since this is invoked on the finalizer thread, we expect minimal work here
   * to notify the game to terminate. The game loop thread will probably read
   * a flag and start showing goodbye.
   */
}

GC.TryStartNoGCRegion(260 * 1024 * 1024);
GC.RegisterAllocationCallback(256 * 1024 * 1024, OnBeforeEndNoGCRegion);
/*
 * Ideally, the work here should not use more than 256M, if that's the case, the code will 
 * perform a GC when EndNoGCRegion is invoked and that's the best case.
 * 
 * However, if the usage exceeds 256M, the callback will be invoked.
 * 
 * If the usage exceeds 260M, a GC will be automatically invoked and the NoGCRegion
 * will be terminated automatically.
 */
GC.EndNoGCRegion(); // And the callback will be detached automatically.

Alternative Designs

One alternative design is to expose a enum what to do when the NoGCRegion is exhausted. The options are to terminate the NoGCRegion, to commit more memory, or to fail fast. This is decided to be not good enough because what customer really wanted to customizable logic to show some screen and end the game gracefully, none of those options allow them to do that safely.

Another alternative design is to add parameters to the TryStartNoGCRegion. That will work, but there are two reasons why we wanted to have separate APIs for starting to NoGCRegion and registering the callback.

  1. There are already a few overloads on GC.TryStartNoGCRegion, and adding more parameters to it makes it looks really complicated, and
  2. We are envisioning extending this API to support scenarios other than just NoGCRegions, as of the time of writing, there is no plan to implement that yet, but having this API shape allows us to do that when we wanted.

Risks

By asking the caller to provide two limits, the caller must estimate how much memory is necessary for serving the callback, which circles back to the initial question that it is not easy to estimate memory usage. The key idea here is that the callback is supposed to be some simple thing, as @WenceyWang suggested below, it will simply be switching a flag and using some preallocated resources. That hopefully ease the problem.

Implementation-wise, we will reserve up to the larger limit to begin the NoGCRegion. Therefore, there is no risk to issue the callback without GC. When we reach the larger limit, we will terminate the NoGCRegion regardless. That way we can maintain our implementation reliability.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-approvedAPI was approved in API review, it can be implementedapi-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-GC-coreclr

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions