Found while building ControlledLoopOverheadBenchmark (PR/commit cd651b1).
Problem
DefaultSimulationContext creates its Koin scope with an id taken from an identity hash code:
// core/src/commonMain/kotlin/cz/vutbr/fit/interlockSim/context/DefaultSimulationContext.kt:152
override val scope =
KoinPlatformTools.defaultContext().get().createScope(
scopeId = platformIdentityCode(this), // <-- System.identityHashCode
...
)
platformIdentityCode delegates to System.identityHashCode on JVM (and kotlin.native.identityHashCode on native). Identity hash codes are not unique — distinct live objects can collide. Its own KDoc even says so ("collisions are possible") and describes the function as being for debug logging, but here it is load-bearing: it is the scopes primary key.
DefaultEditingContext:98 has the same shape (scopeId = this.hashCode().toString()).
Impact
Context.close() does close the scope (Context.kt:126), so at normal context volumes ids get recycled harmlessly and this never shows. Under context churn it throws:
org.koin.core.error.ScopeAlreadyCreatedException: Scope with id 1052863908 is already created
Reproduced reliably by running the controlled-loop benchmark in JMH AverageTime mode, which builds ~38k contexts per iteration. Birthday-bound on a 31-bit space makes a collision near-certain at that volume.
This is currently latent rather than user-visible — no production path creates contexts at that rate. But the scope id is not a safe identifier, and any future work that creates many contexts (scale validation, long agent-driven runs, fuzzing, stress tests) can hit it. It also means two simultaneously-live contexts can, in principle, collide.
Suggested fix
Replace the identity-hash scope id with something actually unique — a monotonic counter (AtomicLong/atomicfu for KMP) or a UUID — in both DefaultSimulationContext and DefaultEditingContext. While there, correct the platformIdentityCode KDoc, or restrict it to logging as documented.
Workaround in place
ControlledLoopOverheadBenchmark uses Mode.SingleShotTime to keep context count in the hundreds, and documents why. That constraint can be lifted once this is fixed.
Found while building
ControlledLoopOverheadBenchmark(PR/commit cd651b1).Problem
DefaultSimulationContextcreates its Koin scope with an id taken from an identity hash code:platformIdentityCodedelegates toSystem.identityHashCodeon JVM (andkotlin.native.identityHashCodeon native). Identity hash codes are not unique — distinct live objects can collide. Its own KDoc even says so ("collisions are possible") and describes the function as being for debug logging, but here it is load-bearing: it is the scopes primary key.DefaultEditingContext:98has the same shape (scopeId = this.hashCode().toString()).Impact
Context.close()does close the scope (Context.kt:126), so at normal context volumes ids get recycled harmlessly and this never shows. Under context churn it throws:Reproduced reliably by running the controlled-loop benchmark in JMH
AverageTimemode, which builds ~38k contexts per iteration. Birthday-bound on a 31-bit space makes a collision near-certain at that volume.This is currently latent rather than user-visible — no production path creates contexts at that rate. But the scope id is not a safe identifier, and any future work that creates many contexts (scale validation, long agent-driven runs, fuzzing, stress tests) can hit it. It also means two simultaneously-live contexts can, in principle, collide.
Suggested fix
Replace the identity-hash scope id with something actually unique — a monotonic counter (
AtomicLong/atomicfufor KMP) or a UUID — in bothDefaultSimulationContextandDefaultEditingContext. While there, correct theplatformIdentityCodeKDoc, or restrict it to logging as documented.Workaround in place
ControlledLoopOverheadBenchmarkusesMode.SingleShotTimeto keep context count in the hundreds, and documents why. That constraint can be lifted once this is fixed.