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
Description
Determine whether (and how)
ResolverChain.resolve(...)can internally parallelize recursive type resolution without breaking circular-reference detection and without exposingsuspendin the public API.Background
ResolverChainis 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 aList<ComplexType>of size 100), the sequentialchain.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:
suspendin the public API.fun resolve(type: KType): Any?andfun TypeResolver.resolve(type: KType, chain: ResolverChain): Any?must not becomesuspendfunctions. Users callsome<T>()from plain Kotlin/Java code.data class Node(val next: Node?)and throwSomeCircularReferenceExceptionwhen appropriate.Current Knowledge
ResolverChainmaintains a singlemutableListOf<KType>()(resolutionStack) to detect circular references. This shared mutable state is not thread-safe and will be corrupted if sibling resolutions run in parallel.ClassResolver(constructor parameters) and collection resolvers (ListResolver,SetResolver,ArrayResolver,MapResolver) because their child resolutions are independent.NullableResolver,SealedClassResolver,ValueClassResolver) will not benefit from parallelism.runBlocking { ... }, but the overhead and thread-pool implications are unknown.ExecutorService,ForkJoinPool) have not been evaluated.Scope
In scope:
resolutionStackstate.chain.resolveAll(List<KType>): List<Any?>).runBlocking+Dispatchers.Default) vsExecutorService/ForkJoinPoolfor hidden concurrency.Out of scope:
Outcomes
resolutionStackthat preserves exact circular-reference semantics.resolveAllAPI).runBlockingvsExecutorService/ForkJoinPoolin terms of complexity, overhead, and compatibility with existing callers.