REVIEW 2 major objections 5 minor 16 references
LitmusKt: Concurrency Stress Testing for Kotlin
T0 review · 2 major / 5 minor · reviewed 2026-08-10 · deepseek-v4-flash
Pith's one-line read LitmusKt, a Kotlin concurrency stress-testing tool, found previously unknown unsafe-publication bugs in Kotlin/Native, got them fixed, and joined Kotlin's CI pipeline.
desk verdict A solid tools paper that found and fixed real Kotlin/Native bugs; the formal-model gap is real but secondary. read the letter →
The pith
A machine-rendered reading of the paper's core claim, the machinery that carries it, and where it could break.
The reading
What carries the argument
The carrying mechanism is the litmus test, i.e., a tiny concurrent program with a declared set of allowed outcomes, here written in a Kotlin DSL and executed many times by LitmusKt. The runner allocates a large array of independent test-state objects, lets each thread step across that array running its part of the test, and inserts periodic barriers so one thread cannot get too far ahead of the other; this amplifies rare weak interleavings into countable events. The DSL's spec classifies each observed outcome as acceptable, interesting, or forbidden, and the appearance of any forbidden outcome turns a run's status into FORBIDDEN. The unsafe-publication test then acts as the concrete probe: it arranges one thread to publish an object while another reads a field, and it is this probe that produced the garbage integers and crashes on Kotlin/Native.
What would settle it
Run the UPUB test as a standalone two-thread Kotlin/Native program without the tool's array-of-states runner; if millions of iterations on the same Arm hardware and Kotlin/Native 1.9.0 never show a garbage integer or crash, while the tool still shows FORBIDDEN outcomes, the runner becomes the suspect. If the standalone program reproduces the garbage values, the compiler is confirmed as the source.
Extended reading notes
Core claim
LitmusKt's discovery claim is that concurrent Kotlin code can violate the language's most basic memory-safety expectations. In the UPUB test, one thread publishes an IntHolder object while a second thread reads its field; on Kotlin/Native 1.9.0 for Arm the reader observed values like 71817408 and -1493360416, which no thread ever wrote, at frequencies around 12 in 40.8 million runs, and the analogous test with a class reference triggered segmentation faults. The paper classifies these as forbidden outcomes, argues they are compiler bugs rather than acceptable weak behaviors, reports a fix that landed by Kotlin 1.9.20, and states that LitmusKt is now part of Kotlin's continuous integration.
Load-bearing premise
The load-bearing premise is that Kotlin's unwritten memory-safety rules really do forbid the observed outgoing values, and that the stress-test tool's machinery for running the test many times does not itself create them.
Editorial extensions
If this is right
- If the central claim holds, Kotlin/Native 1.9.0 on Arm violates basic memory safety under unsafe publication: a reader can observe integer values that were never written by any thread, and a reader of reference fields can crash.
- Cross-platform litmus testing is viable from a single DSL, so the same concurrency expectations can be checked on both Kotlin/Native and Kotlin/JVM instead of maintaining separate harnesses.
- The specific unsafe-publication bug is gone from Kotlin 1.9.20 onward, and LitmusKt's continued inclusion in Kotlin CI means regressions of that class have a chance of being caught automatically.
- The null-from-non-null observation shows that even the type system's nullability guarantees can be violated at runtime under a data race, a consequence compiler engineers and language designers now have to account for.
Reading between the lines
- A natural extension the paper does not pursue is to run the same UPUB pattern on Kotlin/JS and Kotlin/Wasm; the paper's own comparison logic implies divergences there would also signal implementation bugs.
- The observed null from a non-nullable type points beyond the compiler bug: if data races can break type invariants, a future Kotlin memory model will have to say whether such outcomes are memory-model violations or type-system violations.
- The JVM backend's wrapper-based approach suggests a reusable recipe: compile DSL tests to a common bytecode, then generate platform-specific harnesses, which could be applied to other multiplatform languages.
- A concrete testable extension would be to rerun the UPUB and Array<Int> variants on current Kotlin/Native versions while toggling compiler optimization flags; if garbage values reappear, the fix is tied to optimizer behavior rather than to the source-level compiler.
Signed reviews
Editorial analysis
A structured set of objections, weighed in public.
Referee Report
Summary. The manuscript presents LitmusKt, a litmus-testing framework and DSL for Kotlin, with backends for Kotlin/Native and Kotlin/JVM. It describes a custom native runner based on arrays of reusable test states and periodic barriers, and a JVM backend that delegates generated wrappers to jcstress. Using a collection of standard litmus tests, the authors report a central empirical finding: the UPUB test on an Arm machine with Kotlin/Native 1.9.0 produced 'garbage' integer outcomes such as 71817408 and -1493360416 that never appear as writes in the test program. The paper claims these FORBIDDEN outcomes correspond to a compiler bug in unsafe object publication, that a fix was merged into Kotlin, and that LitmusKt was integrated into Kotlin CI. The paper also discusses interesting outcomes of other tests and outlines future work on fuzzing and false-sharing avoidance.
Significance. LitmusKt, if the empirical claims hold, is a meaningful tool contribution: it is the first litmus-testing tool for Kotlin, it allows one DSL to drive two very different runtime backends, and it has already produced an acknowledged compiler fix and CI integration, both supported by commits referenced in the paper. The authors are candid about the absence of a formal Kotlin memory model, and the external fix lowers the circularity risk inherent in declaring outcomes 'forbidden' from the test author's own expectations. The main weakness is verification: the paper does not include the full UPUB test source, raw outcome logs, a standalone reproducer, or a before/after comparison on the fixed compiler, so the attribution of the garbage values to Kotlin/Native code generation, rather than to LitmusKt's state-reuse runner, is not fully checkable from the manuscript.
major comments (2)
- [Section 3, Table 3] The FORBIDDEN outcomes in Table 3 are the load-bearing evidence for the paper's main claim, but the manuscript never states the precise semantic criterion by which values such as 71817408 are forbidden. Because Section 1 acknowledges that Kotlin has no formal memory model, the authors should make their implicit argument explicit: no instruction in the UPUB test ever writes a non-zero value to the field x of IntHolder, so any non-zero value read from x violates the constructor-initialization semantics regardless of weak-memory effects. Please state this or an equivalent invariant, and specify exactly which outcomes the spec section of the UPUB test marks as forbidden.
- [Section 2.2, Section 3] The custom Kotlin/Native runner reuses a fixed array of state objects across iterations, but the paper does not specify whether each state is reinitialized before every iteration or how the per-thread state indices are chosen relative to the barrier. Without this information, a reader of the paper cannot rule out that the UPUB garbage values arose from stale or partially reused runner state rather than from an unsafely published IntHolder. The fix commit referenced as [7] is strong corroboration, but it is not analyzed in the paper. Please include the full LitmusKt source of UPUB, describe the state-reset and indexing protocol precisely, and provide raw logs or a standalone minimal Kotlin/Native program that reproduces a garbage value on 1.9.0 and does not reproduce it on 1.9.20.
minor comments (5)
- [Section 2.2, Figure 2] The schematic shows 't1(state[i])' and 't2(state[j])' followed by 'sync', but the text does not explain whether the two threads access the same state index or different indices; please clarify the indexing scheme.
- [Table 2] The test abbreviations ATOM, MP DRF, CoRR CSE, OOTA, and LB Deps are not defined; a legend or pointer to the corresponding tests in [11] would make the table self-contained.
- [Section 3] The claim that the x86-only weak MP outcome 'can only be reasonably explained by aggressive compiler optimizations' is a hypothesis, not a demonstrated fact; please rephrase as a conjecture and mention that the table reports no confidence intervals or total run counts.
- [Listing 1, Table 1] The DSL example would be easier to follow if the mapping from the state's r1 and r2 fields to the ordered outcome pair in the spec block were stated in the main text, not only in footnotes.
- [References [7], [8]] References [7] and [8] are raw commit URLs; adding the commit titles or short descriptions would allow readers to understand what each commit changed without following the links.
Circularity Check
No significant circularity: the UPUB garbage-value result is an empirical anomaly, not an outcome forced by the tool's own outcome definitions, and the central bug claim is corroborated by the observed values and the subsequent Kotlin fix.
full rationale
The paper's central claim is empirical: running UPUB on Kotlin/Native 1.9.0 on Arm produced FORBIDDEN outcomes with integer values (e.g., 71817408, -1493360416) that are distinct from any value written by the test (the field should be either unread via null, or an initialized value 0). This is not a reduction of a 'prediction' to its inputs by construction: the test's expected outcomes are stated independently (null or 0), and the garbage values violate that expectation. The observed values are not fitted parameters or renamed outputs of the runner. The paper's outcome categories (acceptable/interesting/forbidden) are user-declared, but the bug conclusion rests on the external memory-safety judgment that a memory-safe language must not expose values never written or dangling references, not on a mathematical derivation from the DSL. Self-citations exist (the litmus-test survey [11], the fix commit [7], the CI integration commit [8], and the YouTrack issue [10]), but none is load-bearing: [11] is only a source of standard litmus tests, [7] and [8] post-date and corroborate the empirical findings, and [10] is a future-work suggestion. There is no imported uniqueness theorem or ansatz smuggled in via citation. The paper itself acknowledges the main limitation: Kotlin lacks a formally defined memory model (Section 1), so 'forbidden' is based on informal semantic expectations; additionally, the runner's state-reuse scheme (Section 2.2) and the absence of a standalone reproducer leave a residual alternative explanation for the garbage values. That is an empirical validity/reproducibility concern, not circularity: if the harness caused the values, the claimed compiler bug would be false, not tautological. Therefore, the derivation chain is not circular, and the evidence, including the external fix and CI integration, supports the paper's claims.
Assumptions & free parameters
assumptions (3)
- domain assumption Kotlin/Native's intended semantics disallow reading uninitialized or garbage values from non-nullable object fields after publication.
- domain assumption The LitmusKt harness does not itself introduce the observed corruption.
- domain assumption Standard litmus-test methodology is sound for exposing weak behaviors without falsely attributing races to specific instructions.
Cite this review
Pith. "Pith review of LitmusKt: Concurrency Stress Testing for Kotlin." pith.science (2026). https://pith.science/paper/FIMHVAGN
@misc{pith2026250107472,
author = {Pith},
title = {Pith review of: LitmusKt: Concurrency Stress Testing for Kotlin},
year = {2026},
howpublished = {\url{https://pith.science/paper/FIMHVAGN}},
note = {Machine review of arXiv:2501.07472}
}
read the original abstract
We present LitmusKt - the first tool for litmus testing concurrent programs in Kotlin. The tool's novelty also lies in the fact that Kotlin is a multiplatform language, i.e., it compiles into multiple platforms, which means that the concurrency has to be tested on several of them. Our tool allows writing litmus tests in a single custom DSL, and these tests are then run in Kotlin/Native and Kotlin/JVM, two main platforms for concurrent programming in Kotlin. Using LitmusKt, we discovered novel bugs in the Kotlin compiler, which we then fixed and they are no longer present. Moreover, LitmusKt was integrated into the CI pipeline for Kotlin. LitmusKt is available on GitHub: https://github.com/JetBrains-Research/litmuskt. The demo is available on YouTube: https://youtu.be/oWCZp_Huwss.
Figures
Reference graph
Works this paper leans on
-
[7]
Denis Lochmelis. 2023. UPUB Fix Merged into Kotlin. https://github. com/JetBrains/kotlin/commit/d2e0361b4e6de7e804c6c079e7195354cd77ced9 (Ac- cessed on 2025-04-10)
work page 2023
-
[1]
Jade Alglave, Luc Maranget, and Michael Tautschnig. 2014. Herding Cats: Mod- elling, Simulation, Testing, and Data Mining for Weak Memory. ACM Trans. Program. Lang. Syst. 36, 2 (2014), 1–74. https://doi.org/10.1145/2627752
doi:10.1145/2627752 2014
- [2]
-
[3]
JetBrains. 2024. The Kotlin Programming Language. https://kotlinlang.org/ (Accessed on 2025-04-10)
work page 2024
-
[4]
JetBrains Research. 2024. LitmusKt README. https://github.com/JetBrains- Research/litmuskt/blob/main/README.md (Accessed on 2025-04-10)
work page 2024
-
[5]
Leslie Lamport. 1979. How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs. IEEE Trans. Computers 28, 9 (1979), 690–691. https://doi.org/10.1109/TC.1979.1675439
arXiv 1979
-
[6]
Shuyang Liu, John Bender, and Jens Palsberg. 2022. Compiling Volatile Correctly in Java. In 36th European Conference on Object-Oriented Programming (ECOOP 2022). https://doi.org/10.4230/LIPIcs.ECOOP.2022.6
-
[8]
Denis Lochmelis. 2024. LitmusKT Merged into Kotlin CI. https://github.com/ JetBrains/kotlin/commit/c409279a3833105caf9a4c18c479358c14293ad7 (Accessed on 2025-04-10)
work page 2024
Show all 16 references
-
[9]
Jeremy Manson, William Pugh, and Sarita V. Adve. 2005. The Java Memory Model. In POPL 2005. ACM, 378–391. https://doi.org/10.1145/1040305.1040336
2005
-
[10]
Evgenii Moiseenko. 2024. YouTrack Issue about @Contended Annota- tion. https://youtrack.jetbrains.com/issue/KT-68369/Consider-providing- Contended-annotation (Accessed on 2025-04-10)
2024
-
[11]
Evgenii Moiseenko, Anton Podkopaev, and Dmitry Koznov. 2021. A Survey of Programming Language Memory Models. Programming and Computer Software 47 (2021), 439–456. https://doi.org/10.1134/S0361768821060050
2021 doi
-
[12]
Jonas Oberhauser, Lilith Oberhauser, Antonio Paolillo, Diogo Behrens, Ming Fu, and Viktor Vafeiadis. 2021. Verifying and Optimizing the HMCS Lock for Arm Servers. In Networked Systems: 9th International Conference, NETYS 2021, Virtual Event, May 19–21, 2021, Proceedings . Spri...
2021
-
[13]
OpenJDK. 2024. Java Concurrency Stress Tests. https://openjdk.org/projects/ code-tools/jcstress/ (Accessed on 2025-04-10)
2024
- [14]
-
[15]
Aleksey Shipilev. 2024. JCStress Workshop. https://shipilev.net/talks/hydraconf- June2021-jcstress-workshop.pdf (Accessed on 2025-04-10)
2024
-
[16]
Viktor Vafeiadis, Thibaut Balabonski, Soham Chakraborty, Robin Morisset, and Francesco Zappa Nardelli. 2015. Common Compiler Optimisations Are Invalid in the C11 Memory Model and What We Can Do About It. In Proceedings of the 42nd Annual ACM SIGPLAN-SIGACT Symposium on Princip...
2015
Reviewed August 10, 2026 · model on record in the stance chip above.
Discussion (0). Continue with ORCID to comment.