Repository: Kotlin/kotlinx.coroutines
File: kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt (Kotlin)

Code before:
        internal val currentTestScheduler
            get() = currentTestDispatcher?.scheduler
    }

    /**
     * A wrapper around a value that attempts to throw when writing happens concurrently with reading.
     *
     * The read operations never throw. Instead, the failures detected inside them will be remembered and thrown on the
     * next modification.
     */
    private class NonConcurrentlyModifiable<T>(initialValue: T, private val name: String) {
        private val reader: AtomicRef<Throwable?> = atomic(null) // reader currently accessing value
        private val writer: AtomicRef<Throwable?> = atomic(null) // writer currently performing value modification
        private val exceptionWhenReading: AtomicRef<Throwable?> = atomic(null) // exception from reading
        private val _value = atomic(initialValue) // the backing field for the value

        private fun concurrentWW(location: Throwable) = IllegalStateException("$name is modified concurrently", location)
        private fun concurrentRW(location: Throwable) = IllegalStateException("$name is used concurrently with setting it", location)

        var value: T
            get() {
                val readerLocation = Throwable()
                reader.value = readerLocation
                writer.value?.let { exceptionWhenReading.value = concurrentRW(it) }
                val result = _value.value
                reader.compareAndSet(readerLocation, null)
                return result
            }
            set(value) {
                exceptionWhenReading.getAndSet(null)?.let { throw it }
                reader.value?.let { throw concurrentRW(it) }
                val writerLocation = Throwable()
                writer.getAndSet(writerLocation)?.let { throw concurrentWW(it) }
                _value.value = value
                writer.compareAndSet(writerLocation, null)
                reader.value?.let { throw concurrentRW(it) }
            }
    }
}

@Suppress("INVISIBLE_MEMBER")
private val defaultDelay
    inline get() = DefaultDelay

@Suppress("INVISIBLE_MEMBER")
internal expect fun Dispatchers.getTestMainDispatcher(): TestMainDispatcher


Review comment (suggestion):
Imagine two reads and one write happening in parallel. Clearly, something we should report. However, the following may happen:
* The first read sets its location.
* The second read sets its location, overwriting the first.
* The second read completes, setting the `reader` to null.
* The write starts and finishes, despite the first read still not having finished.

We want to catch as many errors as possible. Here's an idea: what if we keep the counter, but also, before incrementing the counter, we write a `Throwable` corresponding to the reader's location to a separate field called, let's say, `lastReadLocation`, which never becomes `null` again? If looking at the counter, the writer detects that there are concurrent reads, it will go to that field and take out the throwable.

Code after:
        internal val currentTestScheduler
            get() = currentTestDispatcher?.scheduler
    }

    /**
     * A wrapper around a value that attempts to throw when writing happens concurrently with reading.
     *
     * The read operations never throw. Instead, the failures detected inside them will be remembered and thrown on the
     * next modification.
     */
    private class NonConcurrentlyModifiable<T>(initialValue: T, private val name: String) {
        private val reader: AtomicRef<Throwable?> = atomic(null) // last reader to attempt access
        private val readers = atomic(0) // number of concurrent readers
        private val writer: AtomicRef<Throwable?> = atomic(null) // writer currently performing value modification
        private val exceptionWhenReading: AtomicRef<Throwable?> = atomic(null) // exception from reading
        private val _value = atomic(initialValue) // the backing field for the value

        private fun concurrentWW(location: Throwable) = IllegalStateException("$name is modified concurrently", location)
        private fun concurrentRW(location: Throwable) = IllegalStateException("$name is used concurrently with setting it", location)

        var value: T
            get() {
                reader.value = Throwable("reader location")
                readers.incrementAndGet()
                writer.value?.let { exceptionWhenReading.value = concurrentRW(it) }
                val result = _value.value
                readers.decrementAndGet()
                return result
            }
            set(value) {
                exceptionWhenReading.getAndSet(null)?.let { throw it }
                if (readers.value != 0) reader.value?.let { throw concurrentRW(it) }
                val writerLocation = Throwable("other writer location")
                writer.getAndSet(writerLocation)?.let { throw concurrentWW(it) }
                _value.value = value
                writer.compareAndSet(writerLocation, null)
                if (readers.value != 0) reader.value?.let { throw concurrentRW(it) }
            }
    }
}

@Suppress("INVISIBLE_MEMBER")
private val defaultDelay
    inline get() = DefaultDelay

@Suppress("INVISIBLE_MEMBER")
internal expect fun Dispatchers.getTestMainDispatcher(): TestMainDispatcher