Skip to content

Spike: Parallelize ResolverChain resolution while preserving circular-reference detection #60

Description

@MessiasLima

Description

Determine whether (and how) ResolverChain.resolve(...) can internally parallelize recursive type resolution without breaking circular-reference detection and without exposing suspend in the public API.

Background

ResolverChain is the central orchestrator of type resolution. Today it is strictly sequential. For deep or wide object graphs (e.g. a class with 20 constructor parameters, or a List<ComplexType> of size 100), the sequential chain.resolve(...) calls are the bottleneck. Because each resolution is CPU-bound and independent of its siblings, they are ideal candidates for parallel execution.

Hard constraints:

  • No suspend in the public API. fun resolve(type: KType): Any? and fun TypeResolver.resolve(type: KType, chain: ResolverChain): Any? must not become suspend functions. Users call some<T>() from plain Kotlin/Java code.
  • Circular-reference detection must stay exact. The current stack semantics (classifier equality + nullable/non-nullable rules) must continue to detect cycles such as data class Node(val next: Node?) and throw SomeCircularReferenceException when appropriate.
  • No behavioural changes for sequential callers. If the library is used on a single thread, the outcome must be identical to today.

Current Knowledge

  • ResolverChain maintains a single mutableListOf<KType>() (resolutionStack) to detect circular references. This shared mutable state is not thread-safe and will be corrupted if sibling resolutions run in parallel.
  • The main parallelism candidates are ClassResolver (constructor parameters) and collection resolvers (ListResolver, SetResolver, ArrayResolver, MapResolver) because their child resolutions are independent.
  • Resolvers with single recursive calls (NullableResolver, SealedClassResolver, ValueClassResolver) will not benefit from parallelism.
  • Kotlin coroutines can be hidden behind a synchronous API via runBlocking { ... }, but the overhead and thread-pool implications are unknown.
  • Alternative concurrency mechanisms (e.g. ExecutorService, ForkJoinPool) have not been evaluated.
  • There is no existing benchmark harness for comparing sequential vs parallel resolution.

Scope

In scope:

  • Investigating how to remove or make safe the shared resolutionStack state.
  • Evaluating whether to introduce parallelism transparently or via an opt-in API (e.g. chain.resolveAll(List<KType>): List<Any?>).
  • Comparing coroutines (runBlocking + Dispatchers.Default) vs ExecutorService / ForkJoinPool for hidden concurrency.
  • Benchmarking sequential vs parallel for small, wide, and deep graphs.
  • Documenting the recommended approach and, if viable, a task breakdown for implementation.

Out of scope:

  • Actually implementing the parallelization in production code.
  • Adding new public API surface (the spike may recommend one, but designing it is part of the follow-up).
  • Changes to resolver generation strategies or randomization behaviour.

Outcomes

  • Identify a thread-safe replacement for the shared resolutionStack that preserves exact circular-reference semantics.
  • Confirm whether parallelism should be transparent or opt-in (e.g. resolveAll API).
  • Compare coroutines-under-runBlocking vs ExecutorService / ForkJoinPool in terms of complexity, overhead, and compatibility with existing callers.
  • Benchmark sequential vs parallel for shallow, wide, and deep object graphs; measure latency and memory impact.
  • Produce a design document (markdown) with a clear recommendation: go / no-go / go with opt-in flag.
  • If the recommendation is "go", produce a rough task breakdown for the implementation ticket(s).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Status
    Ready

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions