Repository navigation
Spike: Parallelize ResolverChain resolution while preserving circular-reference detection #60
Copy link
Copy link
Closed
Milestone
Description
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
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. - 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 throwSomeCircularReferenceExceptionwhen 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
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.- 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
resolutionStackstate. - 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) vsExecutorService/ForkJoinPoolfor 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
resolutionStackthat preserves exact circular-reference semantics. - Confirm whether parallelism should be transparent or opt-in (e.g.
resolveAllAPI). - Compare coroutines-under-
runBlockingvsExecutorService/ForkJoinPoolin 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).
Reactions are currently unavailable
Activity
MessiasLima commentedon Sep 18, 2026
Investigation result: parallelization is a no-go; caching is the better path
We investigated parallelizing
ResolverChain.resolve(...)for this spike. Conclusion: parallelization is not a good solution for improving performance here. The main costs and blockers:resolutionStackis central to circular-reference detection. Parallel branches would require a thread-safe redesign plus per-branch context propagation, adding complexity and risk to the exact cycle semantics.Instead, caching is the best solution. The expensive parts are repeated reflection/metadata lookups that are stable for a given class or type, so caching removes repeated work without touching resolution semantics or introducing concurrency.
Caching tasks created
Supporting changes
Recommendation: no-go on parallelization; pursue the caching tasks above instead.