Package org.apache.groovy.runtime.indy

# Class IndyInvalidation

java.lang.Object
org.apache.groovy.runtime.indy.IndyInvalidation

public final class IndyInvalidation extends Object
Scoped invokedynamic `SwitchPoint` invalidation for the Groovy MOP (GROOVY-12191).

Domain: one SwitchPoint domain per `Class`, owned by its `ClassInfo` and covering every MetaClass generation of that class (pre-MetaClass link included). The domain outlives the MetaClass object, so exact-class invalidation still reaches installed guards after a soft/weak MetaClass has been collected. Monomorphic indy sites install a single `guardWithTest` on the class-level domain observed at link time.

## Width policy (two axes only)

- Exact class — `invalidateClass(Class)`: stock `MetaClassImpl` / EMC changes (including `ClassInfo.incVersion()`, registry replace, per-instance MetaClass).
- All loaded — process-wide retire used for category enter/leave (`invalidateCategory()`), unattributed registry events (`invalidateUnscoped()`), and non-stock custom MetaClass kinds (`invalidateBulk()`).

There is no parent→child SwitchPoint fan-out. Indy selection consults only the receiver’s own MetaClass tables; ancestor-dependent resolution on the missing-method / missing-property path re-enters `MetaClass.invokeMethod` / property miss and walks the hierarchy live from the registry each call. Construction-time snapshots of ancestor expando methods are pre-existing MOP behaviour and are not refreshed by SwitchPoint retirement. See also `MetaClassImpl.findMethodInClassHierarchy(java.lang.Class, java.lang.String, java.lang.Class[], groovy.lang.MetaClass)`.

Optional stats: `-Dgroovy.indy.invalidation.stats=true`. Production guards: `IndyInterface.applyMopSwitchPoints`; tests may use `guardWithMopSwitchPoints(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle, java.lang.Object)`.

## Layering

This class is the policy half of a two-level subsystem: `SwitchPointInvalidator` is the mechanism (one domain's SwitchPoint lifecycle plus the process-wide live registry) and makes no policy decisions; this class decides invalidation width, labels the reasons, owns reclaim anchoring and — when the global ClassValue store reclaims values (GROOVY-12281 soft mode) — per-Class domain continuity. The only other supported consumer of the mechanism is `ClassInfo`, which owns domain instances and performs the local operations (allocate, invalidate, detach) directly. No other code should construct or invalidate `SwitchPointInvalidator` instances; both classes are internal and may change incompatibly.

Since:
  6.0.0

- ## Method Summary

  Modifier and Type
  Method
  Description
  `static void`
  `anchorClassDomain(ClassInfo owner, SwitchPointInvalidator domain)`
  Anchors a reclaim reference for a ClassInfo-owned class domain: when the ClassInfo (and with it the class) becomes unreachable while the domain still has a live registered SwitchPoint, the reference is delivered by the shared `weak-bundle` manager and the domain is retired — installed guards keep only the SwitchPoint's internal invoker reachable, so retiring the orphan forces straggler sites to re-link, which is always safe.
  `static void`
  `anchorClassDomainToClass(Class<?> owner, SwitchPointInvalidator domain)`
  Anchors a reclaim reference keyed to the `Class` rather than a ClassInfo instance: soft mode retires a domain only when the class dies (no receiver can reach an installed guard afterwards), because ClassInfo instances are replaceable and their successors adopt the same domain.
  `static long`
  `bulkInvalidationCount()`
  Non-category process-wide bulk event count (`invalidateBulk()` / `invalidateUnscoped()`).
  `static long`
  `categoryInvalidationCount()`
  Category-style bulk invalidation count (`invalidateCategory()`).
  `static SwitchPointInvalidator`
  `classDomainFor(Class<?> type, SwitchPointInvalidator candidate)`
  Returns the canonical domain for `type`, seeding the per-Class map with `candidate` on first touch.
  `static long`
  `classInvalidationCount()`
  Exact-class invalidation event count (`invalidateClass(Class)`).
  `static SwitchPoint`
  `classSwitchPointFor(Class<?> type)`
  Returns the SwitchPoint for the given class at link time: the current generation of the ClassInfo-owned class domain (defineClass-safe — does not create or consult a MetaClass).
  `static SwitchPoint`
  `classSwitchPointFor(Object receiver)`
  Returns the class-level MetaClass SwitchPoint for the given receiver.
  `static void`
  `collectLiveForClass(Class<?> type, List<SwitchPoint> out)`
  Collects the live SwitchPoint (if any) of `type`'s class domain.
  `static MethodHandle`
  `guardWithMopSwitchPoints(MethodHandle handle, MethodHandle fallback, Class<?> receiverClass)`
  Installs a MetaClass SwitchPoint guard on `handle`.
  `static MethodHandle`
  `guardWithMopSwitchPoints(MethodHandle handle, MethodHandle fallback, Object receiver)`
  Installs a MetaClass SwitchPoint guard on `handle`.
  `static void`
  `invalidateBulk()`
  Process-wide bulk retirement for non-stock (custom) MetaClass registry events — correctness-first when selection may consult state outside the stock miss walk.
  `static void`
  `invalidateCategory()`
  Category enter/leave and `VMPlugin.invalidateCallSites()`.
  `static void`
  `invalidateClass(Class<?> type)`
  Retires only `type`'s MetaClass SwitchPoint domain (exact class; no subtype fan-out).
  `static void`
  `invalidateForMetaClassChange(MetaClassRegistryChangeEvent event)`
  Registry-driven invalidation: exact class for stock MetaClass kinds and per-instance changes; process-wide bulk for custom MetaClass kinds or unattributed events.
  `static void`
  `invalidateMetaClass(MetaClass mc)`
  Retires the class domain of `mc`'s class (if a generation was allocated).
  `static void`
  `invalidateUnscoped()`
  Process-wide bulk retirement when a MetaClass registry event carries no `Class` attribution.
  `static boolean`
  `isStockMetaClass(MetaClass mc)`
  Whether `mc` is a stock MOP MetaClass for which exact-class invalidation is sufficient: `null`, or unwrapped `MetaClassImpl` (includes EMC and subclasses).
  `static boolean`
  `needsBulkInvalidation(MetaClass oldMc, MetaClass newMc)`
  Whether a class-level MetaClass change involving `oldMc`/`newMc` requires process-wide bulk invalidation.
  `static void`
  `resetCountersForTesting()`
  Resets process-wide counters (tests only).
  `static Class<?>`
  `switchPointClassFor(Object receiver)`
  Resolves the class used for the MetaClass SwitchPoint domain of a receiver.
  `static SwitchPoint`
  `switchPointForMetaClass(MetaClass mc)`
  Returns the class-domain SwitchPoint for a MetaClass.

  ### Methods inherited from class java.lang.Object

  `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`

- ## Method Details

  - ### anchorClassDomain

    public static void anchorClassDomain(ClassInfo owner, SwitchPointInvalidator domain)
    Anchors a reclaim reference for a ClassInfo-owned class domain: when the ClassInfo (and with it the class) becomes unreachable while the domain still has a live registered SwitchPoint, the reference is delivered by the shared `weak-bundle` manager and the domain is retired — installed guards keep only the SwitchPoint's internal invoker reachable, so retiring the orphan forces straggler sites to re-link, which is always safe.

    Reachability: the live-SwitchPoint registry holds the invalidator strongly, and the invalidator holds the reclaim reference, so cleanup stays reachable exactly as long as there is something to clean; once the domain is retired the whole chain becomes collectable.

    Parameters:
      `owner` - the ClassInfo owning the domain (must not be `null`)
      `domain` - the class-level domain (must not be `null`)

  - ### classDomainFor

    public static SwitchPointInvalidator classDomainFor(Class<?> type, SwitchPointInvalidator candidate)
    Returns the canonical domain for `type`, seeding the per-Class map with `candidate` on first touch. Soft-mode only (callers gate).
    Parameters:
      `type` - the class whose domain is requested (must not be `null`)
      `candidate` - the caller-owned domain to install if none is mapped yet
    Returns:
      the canonical per-Class domain

  - ### anchorClassDomainToClass

    public static void anchorClassDomainToClass(Class<?> owner, SwitchPointInvalidator domain)
    Anchors a reclaim reference keyed to the `Class` rather than a ClassInfo instance: soft mode retires a domain only when the class dies (no receiver can reach an installed guard afterwards), because ClassInfo instances are replaceable and their successors adopt the same domain.
    Parameters:
      `owner` - the class owning the domain (must not be `null`)
      `domain` - the class-level domain (must not be `null`)

  - ### invalidateClass

    public static void invalidateClass(Class<?> type)
    Retires only `type`'s MetaClass SwitchPoint domain (exact class; no subtype fan-out). No-op when `type` is `null`. Does not bump `ClassInfo.getVersion()`.

    Used by `ClassInfo.incVersion()`, stock registry MetaClass changes, and per-instance MetaClass changes. Non-stock MetaClass kinds use `invalidateBulk()` instead.

    Parameters:
      `type` - the class whose MetaClass changed

  - ### invalidateCategory

    public static void invalidateCategory()
    Category enter/leave and `VMPlugin.invalidateCallSites()`. Retires every loaded class-level domain so sites re-link under the new category state. No separate category SwitchPoint on the hot path.

  - ### invalidateBulk

    public static void invalidateBulk()
    Process-wide bulk retirement for non-stock (custom) MetaClass registry events — correctness-first when selection may consult state outside the stock miss walk. Rare.

  - ### invalidateUnscoped

    public static void invalidateUnscoped()
    Process-wide bulk retirement when a MetaClass registry event carries no `Class` attribution.

  - ### invalidateForMetaClassChange

    public static void invalidateForMetaClassChange(MetaClassRegistryChangeEvent event)
    Registry-driven invalidation: exact class for stock MetaClass kinds and per-instance changes; process-wide bulk for custom MetaClass kinds or unattributed events. See class javadoc.
    Parameters:
      `event` - the registry change event (must not be `null`)

  - ### needsBulkInvalidation

    public static boolean needsBulkInvalidation(MetaClass oldMc, MetaClass newMc)
    Whether a class-level MetaClass change involving `oldMc`/`newMc` requires process-wide bulk invalidation.

    Bulk is reserved for non-stock MetaClass kinds: not `MetaClassImpl` after adapter unwrap. Stock pairs stay exact-class.

    Parameters:
      `oldMc` - previous MetaClass (may be `null`)
      `newMc` - new MetaClass (may be `null` on remove)
    Returns:
      `true` if bulk invalidation is required

  - ### isStockMetaClass

    public static boolean isStockMetaClass(MetaClass mc)
    Whether `mc` is a stock MOP MetaClass for which exact-class invalidation is sufficient: `null`, or unwrapped `MetaClassImpl` (includes EMC and subclasses). Any other kind is non-stock and triggers bulk invalidation on registry replace.
    Parameters:
      `mc` - MetaClass to classify (may be `null`)
    Returns:
      `true` if exact-class invalidation is sufficient

  - ### collectLiveForClass

    public static void collectLiveForClass(Class<?> type, List<SwitchPoint> out)
    Collects the live SwitchPoint (if any) of `type`'s class domain. Does not create a MetaClass and does not allocate a domain generation.
    Parameters:
      `type` - class to inspect (must not be `null`)
      `out` - destination list

  - ### switchPointClassFor

    public static Class<?> switchPointClassFor(Object receiver)
    Resolves the class used for the MetaClass SwitchPoint domain of a receiver. `null` maps to `NullObject`; a `Class` receiver uses itself.
    Parameters:
      `receiver` - the call receiver (may be `null`)
    Returns:
      the class whose class-level MetaClass SwitchPoint guards this site

  - ### switchPointForMetaClass

    public static SwitchPoint switchPointForMetaClass(MetaClass mc)
    Returns the class-domain SwitchPoint for a MetaClass. The domain belongs to `the class`, not the MetaClass instance, so every generation (and every adapter wrapping) of a class's MetaClass shares one domain.
    Parameters:
      `mc` - MetaClass (must not be `null`)
    Returns:
      the class-domain SwitchPoint

  - ### invalidateMetaClass

    public static void invalidateMetaClass(MetaClass mc)
    Retires the class domain of `mc`'s class (if a generation was allocated). Does not bump counters (local domain retire; width policy is separate).
    Parameters:
      `mc` - MetaClass (may be `null`)

  - ### classSwitchPointFor

    public static SwitchPoint classSwitchPointFor(Object receiver)
    Returns the class-level MetaClass SwitchPoint for the given receiver.
    Parameters:
      `receiver` - the call receiver (may be `null`)
    Returns:
      the MetaClass-domain switch point

  - ### classSwitchPointFor

    public static SwitchPoint classSwitchPointFor(Class<?> type)
    Returns the SwitchPoint for the given class at link time: the current generation of the ClassInfo-owned class domain (defineClass-safe — does not create or consult a MetaClass).
    Parameters:
      `type` - the class (must not be `null`)
    Returns:
      the SwitchPoint for monomorphic MOP guards

  - ### guardWithMopSwitchPoints

    public static MethodHandle guardWithMopSwitchPoints(MethodHandle handle, MethodHandle fallback, Object receiver)
    Installs a MetaClass SwitchPoint guard on `handle`. Public entry for tests; production uses `IndyInterface.applyMopSwitchPoints`.
    Parameters:
      `handle` - the fast-path handle
      `fallback` - the re-link / fallback handle
      `receiver` - the receiver used to select the MetaClass-domain switch point
    Returns:
      the guarded handle

  - ### guardWithMopSwitchPoints

    public static MethodHandle guardWithMopSwitchPoints(MethodHandle handle, MethodHandle fallback, Class<?> receiverClass)
    Installs a MetaClass SwitchPoint guard on `handle`.
    Parameters:
      `handle` - the fast-path handle
      `fallback` - the re-link / fallback handle
      `receiverClass` - the class whose class-level MetaClass domain guards this site
    Returns:
      the guarded handle

  - ### classInvalidationCount

    public static long classInvalidationCount()
    Exact-class invalidation event count (`invalidateClass(Class)`).
    Returns:
      class invalidation count

  - ### bulkInvalidationCount

    public static long bulkInvalidationCount()
    Non-category process-wide bulk event count (`invalidateBulk()` / `invalidateUnscoped()`).
    Returns:
      bulk invalidation count

  - ### categoryInvalidationCount

    public static long categoryInvalidationCount()
    Category-style bulk invalidation count (`invalidateCategory()`).
    Returns:
      category invalidation count

  - ### resetCountersForTesting

    public static void resetCountersForTesting()
    Resets process-wide counters (tests only). Does not reset live switch points.