๐Ÿ‘ฉโ€๐Ÿ”ง RTrace and Memory Leaks

Check your leaky code!

If an expectation fails and hits an unreachable() instruction, any unreleased references in the function call stack will be held indefinitely as a memory leak. Test Suites don't stop running if they fail the test callback. However, tests will stop if they fail inside the beforeEach(), beforeAll(), afterEach(), and afterAll() callbacks.

Typically, a throws() test will leave at least a single Expectation on the heap. This is to be expected, because the unreachable() instruction unwinds the stack, and prevents the ability for each function to __release a reference pointer properly. Your test suite output may look like this:

[Describe]: toHaveLength TypedArray type: Uint32Array

 [Success]: โœ” should assert expected length
  [Throws]: โœ” when expected length should not equal the same value RTrace: +3
 [Success]: โœ” should verify the length is not another value
  [Throws]: โœ” when the length is another expected value RTrace: +3

The RTrace: +3 corresponds to an Expectation, a Uint32Array, and a single backing ArrayBuffer that was left on the heap because of the fact that the expectation failed. This was expected because these two tests were annotated with the throws(desc, callback) function. If you see a function that is expected to pass and RTrace returns a very large value, it might be an indicator of a very serious memory leak, and the DefaultTestReporter can be your best friend when it comes to finding these sorts of problems.

Among other solutions, the following methods are exposed to you as a way to inspect how many allocations and frees occurred during the course of function execution.

declare class RTrace {

  /** Return the current number of active references on the heap. */
  public static count(): i32;

  /**
   * Start counting heap allocations.
   *
   * @param {i32} label - The numeric label for this refcounting group.
   */
  public static start(label: i32): void;

  /**
   * End counting heap allocations.
   *
   * @param {i32} label - The numeric label for this refcounting group.
   * @returns The delta number of heap allocations relative to the start.
   */
  public static end(label: i32): i32;

  /** Overall module count for ARC increments. */
  public static increments(): i32;

  /** Overall module count for ARC decrements. */
  public static decrements(): i32;

  /** Overall TestGroup count for ARC increments. */
  public static groupIncrements(): i32;

  /** Overall TestGroup count for ARC decrements. */
  public static groupDecrements(): i32;

  /** Overall Test count for ARC increments. */
  public static testIncrements(): i32;

  /** Overall Test count for ARC decrements. */
  public static testDecrements(): i32;

  /** Overall count for ARC allocations. */
  public static allocations(): i32;
 
  /** Overall count for ARC frees. */
  public static frees(): i32;

  /** TestGroup count for ARC allocations. */
  public static groupAllocations(): i32;
 
  /** TestGroup count for ARC frees. */
  public static groupFrees(): i32;

  /** Test count for ARC allocations. */
  public static testAllocations(): i32;

  /** Test count for ARC frees. */
  public static testFrees(): i32;

  /** Overall count for ARC reallocations. */
  public static reallocations(): i32;

  /** TestGroup count for ARC reallocations. */
  public static groupReallocations(): i32;

  /** TestGroup count for ARC reallocations. */
  public static testReallocations(): i32;

  /**
   * This method triggers a garbage collection.
   */
  public static collect(): void;

  /**
   * Get the type id (class id) of the pointer.
   *
   * @param {usize} pointer - The pointer.
   * @returns {u32} - The type id of the allocated block.
   */
  public static typeIdOf(pointer: usize): u32;

  /**
   * Get the type id (class id) of a reference.
   *
   * @param {T} reference - The reference.
   * @returns {u32} - The type id of the allocated block.
   */
  public static typeIdOfReference<T>(reference: T): u32;

  /**
   * Get the size of a pointer.
   *
   * @param {usize} pointer - The pointer.
   * @returns {u32} - The size of the allocated block.
   */
  public static sizeOf(pointer: usize): u32;

  /**
   * Get the size of a reference.
   *
   * @param {T} reference - The reference.
   * @returns {u32} - The size of the allocated block.
   */
  public static sizeOfReference<T>(reference: T): u32;

  /**
   * Get the currently allocated blocks.
   */
  public static activeBlocks(): usize[];

  /**
   * Get the current groups allocated blocks.
   */
  public static activeGroupBlocks(): usize[];

  /** Get the current tests allocated blocks. */
  public static activeTestBlocks(): usize[];

  /** Get the current ARC count of a given pointer. */
  public static refCountOf(ptr: usize): u32;

  /**
   * Gets the current count of the specified reference.
   * @param {T} reference - the reference.
   */
  public static refCountOfReference<T>(reference: T): u32;
}

Every one of these functions exist in the RTrace namespace and will call into JavaScript to query the state of the heap relative to the overall test file, the test group, and each individual test depending on the function.

RTrace can be disabled with the --nortrace cli option, to avoid collecting reference counting statistics.

Last updated