Skip to content

Commit 7bf4fa9

Browse files
author
Sergey Mashkov
committed
IO: fix race in ByteBufferChannel.write(lambda)
1 parent 2874d16 commit 7bf4fa9

File tree

2 files changed

+29
-5
lines changed

2 files changed

+29
-5
lines changed

core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/ByteBufferChannel.kt

+21-5
Original file line numberDiff line numberDiff line change
@@ -1363,16 +1363,32 @@ internal class ByteBufferChannel(
13631363
var written = false
13641364

13651365
writing { dst, state ->
1366-
if (state.availableForWrite >= min) {
1366+
val locked = state.tryWriteAtLeast(min)
1367+
1368+
if (locked > 0) {
1369+
// here we have locked all remaining for write bytes
1370+
// however we don't know how many bytes will be actually written
1371+
// so later we have to return (locked - actuallyWritten) bytes back
1372+
1373+
// it is important to lock bytes to fail concurrent tryLockForRelease
1374+
// once we have locked some bytes, tryLockForRelease will fail so it is safe to use buffer
1375+
13671376
val position = dst.position()
13681377
val l = dst.limit()
13691378
block(dst)
13701379
if (l != dst.limit()) throw IllegalStateException("buffer limit modified")
1371-
val delta = dst.position() - position
1372-
if (delta < 0) throw IllegalStateException("position has been moved backward: pushback is not supported")
13731380

1374-
if (!state.tryWriteExact(delta)) throw IllegalStateException()
1375-
dst.bytesWritten(state, delta)
1381+
val actuallyWritten = dst.position() - position
1382+
if (actuallyWritten < 0) throw IllegalStateException("position has been moved backward: pushback is not supported")
1383+
1384+
dst.bytesWritten(state, actuallyWritten)
1385+
1386+
if (actuallyWritten < locked) {
1387+
state.completeRead(locked - actuallyWritten) // return back extra bytes (see note above)
1388+
// we use completeRead in spite of that it is write block
1389+
// we don't need to resume write as we are already in writing block
1390+
}
1391+
13761392
written = true
13771393
}
13781394
}

core/kotlinx-coroutines-io/src/main/kotlin/kotlinx/coroutines/experimental/io/internal/RingBufferCapacity.kt

+8
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ internal class RingBufferCapacity(private val totalCapacity: Int) {
4040
}
4141
}
4242

43+
fun tryWriteAtLeast(n: Int): Int {
44+
while (true) {
45+
val remaining = availableForWrite
46+
if (remaining < n) return 0
47+
if (AvailableForWrite.compareAndSet(this, remaining, 0)) return remaining
48+
}
49+
}
50+
4351
fun tryWriteExact(n: Int): Boolean {
4452
while (true) {
4553
val remaining = availableForWrite

0 commit comments

Comments
 (0)