Skip to content

Mark BufferedChannelIterator.continuation @BeningDataRace to address … #3873

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,7 @@ internal open class BufferedChannel<E>(
* continuation. The [tryResumeHasNext] and [tryResumeHasNextOnClosedChannel]
* function resume this continuation when the [hasNext] invocation should complete.
*/
@BenignDataRace // See its uses for the explanation of the race
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should explicitly list the races right next to the annotation. This way, if a new race is discovered, we won't look at the annotation and go, "oh yeah, we know about this race, it's benign, no worries."

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, fixed

private var continuation: CancellableContinuationImpl<Boolean>? = null

// `hasNext()` is just a special receive operation.
Expand Down
12 changes: 12 additions & 0 deletions kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,15 @@ internal expect class ReentrantLock() {
internal expect inline fun <T> ReentrantLock.withLock(action: () -> T): T

internal expect fun <E> identitySet(expectedSize: Int): MutableSet<E>

/**
* Annotation indicating that the marked property is the subject of benign data race.
* LLVM does not support this notion, so on K/N platforms we alias it into `@Volatile` to prevent potential OoTA.
*
* The purpose of this annotation is not to save an extra-volatile on JVM platform, but rather to explicitly emphasize
* that data-race is benign.
*/
@OptionalExpectation
@Target(AnnotationTarget.FIELD)
@OptIn(ExperimentalMultiplatform::class)
internal expect annotation class BenignDataRace()
14 changes: 11 additions & 3 deletions kotlinx-coroutines-core/common/src/selects/Select.kt
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,12 @@ internal open class SelectImplementation<R>(

/**
* List of clauses waiting on this `select` instance.
*
* This property is the subject to bening data-race: concurrent cancellation might null-out this property
* while [trySelect] operation reads it and iterates over its content.
* A logical race is resolved by the consensus on [state] property.
*/
@BenignDataRace
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the explanation here, for example.

private var clauses: MutableList<ClauseData>? = ArrayList(2)

/**
Expand Down Expand Up @@ -408,6 +413,7 @@ internal open class SelectImplementation<R>(
* or [DisposableHandle] instance when the clause is completed during registration ([inRegistrationPhase] is `false`).
* Yet, this optimization is omitted for code simplicity.
*/
@BenignDataRace // See its cleanup phase for the explanation
private var internalResult: Any? = NO_RESULT

/**
Expand Down Expand Up @@ -621,9 +627,11 @@ internal open class SelectImplementation<R>(
// try to resume the continuation.
this.internalResult = internalResult
if (cont.tryResume(onCancellation)) return TRY_SELECT_SUCCESSFUL
// If the resumption failed, we need to clean
// the [result] field to avoid memory leaks.
this.internalResult = null
/*
* If the resumption failed, we need to clean the [result] field to avoid memory leaks.
* This write is benignly races with the very same write in cancellation invoke() handler
*/
this.internalResult = NO_RESULT
return TRY_SELECT_CANCELLED
}
}
Expand Down
2 changes: 2 additions & 0 deletions kotlinx-coroutines-core/native/src/internal/Concurrent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ internal open class SuppressSupportingThrowableImpl : Throwable() {
}
}

@Suppress("ACTUAL_WITHOUT_EXPECT") // This suppress can be removed in 2.0: KT-59355
internal actual typealias BenignDataRace = kotlin.concurrent.Volatile