Skip to content

Commit 07f0007

Browse files
author
Sergey Mashkov
committed
Fix error "Unable to stop reading in state Writing"
1 parent 6bf2c4d commit 07f0007

File tree

15 files changed

+290
-11
lines changed

15 files changed

+290
-11
lines changed

binary-compatibility-validator/reference-public-api/kotlinx-io-jvm.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ public abstract class kotlinx/io/core/ByteReadPacketBase : kotlinx/io/core/Input
181181
public final fun ensureNext (Lkotlinx/io/core/IoBuffer;)Lkotlinx/io/core/IoBuffer;
182182
public final fun ensureNextHead (Lkotlinx/io/core/IoBuffer;)Lkotlinx/io/core/IoBuffer;
183183
protected abstract fun fill ()Lkotlinx/io/core/IoBuffer;
184+
public final fun fixGapAfterRead (Lkotlinx/io/core/IoBuffer;)V
184185
public final fun getByteOrder ()Lkotlinx/io/core/ByteOrder;
185186
public fun getEndOfInput ()Z
186187
public final fun getPool ()Lkotlinx/io/pool/ObjectPool;
@@ -403,6 +404,7 @@ public final class kotlinx/io/core/IoBuffer : kotlinx/io/core/Input, kotlinx/io/
403404
public final fun setAttachment (Ljava/lang/Object;)V
404405
public final fun setByteOrder (Lkotlinx/io/core/ByteOrder;)V
405406
public final fun setNext (Lkotlinx/io/core/IoBuffer;)V
407+
public fun toString ()Ljava/lang/String;
406408
public final fun tryPeek ()I
407409
public final fun write (Ljava/nio/ByteBuffer;)V
408410
public final fun write ([BII)V

kotlinx-io-js/src/main/kotlin/kotlinx/io/core/IoBufferJS.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,26 @@ actual class IoBuffer internal constructor(
10491049
readPosition += rc
10501050
}
10511051

1052+
internal actual fun restoreEndGap(n: Int) {
1053+
val newLimit = limit - n
1054+
limit = newLimit
1055+
if (writePosition > newLimit) {
1056+
writePosition = newLimit
1057+
}
1058+
if (readPosition > newLimit) {
1059+
readPosition = newLimit
1060+
}
1061+
}
1062+
1063+
internal actual fun restoreStartGap(n: Int) {
1064+
val rp = readPosition
1065+
if (rp < n) {
1066+
throw IllegalArgumentException("Can't restore start gap: $n bytes were not reserved before")
1067+
}
1068+
1069+
readPosition = rp - n
1070+
}
1071+
10521072
internal actual fun writeBufferPrepend(other: IoBuffer) {
10531073
val size = other.readRemaining
10541074
require(size <= startGap) { "size should be greater than startGap (size = $size, startGap = $startGap)" }
@@ -1105,6 +1125,9 @@ actual class IoBuffer internal constructor(
11051125
throw UnsupportedOperationException("close for buffer view is not supported")
11061126
}
11071127

1128+
override fun toString(): String =
1129+
"Buffer[readable = $readRemaining, writable = $writeRemaining, startGap = $startGap, endGap = $endGap]"
1130+
11081131
actual companion object {
11091132
private val EmptyBuffer = ArrayBuffer(0)
11101133
private val EmptyDataView = DataView(EmptyBuffer)

kotlinx-io-jvm/src/main/kotlin/kotlinx/io/core/IoBufferJVM.kt

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,18 +464,41 @@ actual class IoBuffer private constructor(
464464
return length
465465
}
466466

467+
internal actual fun restoreStartGap(n: Int) {
468+
val rp = readPosition
469+
if (rp < n) {
470+
throw IllegalArgumentException("Can't restore start gap: $n bytes were not reserved before")
471+
}
472+
473+
readPosition = rp - n
474+
}
475+
476+
internal actual fun restoreEndGap(n: Int) {
477+
val limit = writeBuffer.limit()
478+
val newLimit = limit - n
479+
writeBuffer.limit(newLimit) // position will be bumped as well
480+
481+
if (readBuffer.limit() > newLimit) {
482+
readBuffer.limit(newLimit) // position will be bumped as well
483+
}
484+
}
485+
467486
internal actual fun writeBufferPrepend(other: IoBuffer) {
468487
val size = other.readRemaining
469488
val rp = readPosition
470489

471-
if (size > rp) throw IllegalArgumentException("Can't prepend buffer: not enough free space in the beginning")
490+
if (size > rp) {
491+
throw IllegalArgumentException("Can't prepend buffer: not enough free space at the beginning")
492+
}
472493

473494
val to = writeBuffer
474495
val pos = writePosition
475496

497+
val limitBefore = to.limit()
476498
to.limit(rp)
477499
to.position(rp - size)
478500
to.put(other.readBuffer)
501+
to.limit(limitBefore)
479502

480503
readPosition = rp - size
481504
writePosition = pos
@@ -489,7 +512,9 @@ actual class IoBuffer private constructor(
489512
val requiredGap = size - rem
490513
val gap = endGap
491514

492-
if (requiredGap > gap) throw IllegalArgumentException("Can't append buffer: not enough free space in the end")
515+
if (requiredGap > gap) {
516+
throw IllegalArgumentException("Can't append buffer: not enough free space at the end")
517+
}
493518
writeBuffer.limit(writeBuffer.limit() + requiredGap)
494519
}
495520

@@ -874,6 +899,9 @@ actual class IoBuffer private constructor(
874899
readBuffer.limit(writePosition)
875900
}
876901

902+
override fun toString(): String =
903+
"Buffer[readable = $readRemaining, writable = $writeRemaining, startGap = $startGap, endGap = $endGap]"
904+
877905
actual companion object {
878906
private val EmptyBuffer: ByteBuffer = ByteBuffer.allocateDirect(0)
879907
private val RefCount = AtomicLongFieldUpdater.newUpdater(IoBuffer::class.java, IoBuffer::refCount.name)!!

kotlinx-io-jvm/src/main/kotlin/kotlinx/io/nio/Input.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import java.nio.channels.*
88
private class ChannelAsInput(private val channel: ReadableByteChannel, pool: ObjectPool<IoBuffer>) :
99
AbstractInput(pool = pool), Input {
1010
init {
11-
require(channel !is SelectableChannel || !channel.isBlocking) { "Non-blocking channels as not supported" }
11+
require(channel !is SelectableChannel || !channel.isBlocking) { "Non-blocking channels are not supported" }
1212
}
1313

1414
override fun fill(): IoBuffer? {
1515
val buffer: IoBuffer = pool.borrow()
16+
buffer.reserveEndGap(ByteReadPacketBase.ReservedSize)
17+
1618
try {
1719
var rc = -1
1820

kotlinx-io-jvm/src/main/kotlin/kotlinx/io/streams/ByteArrays.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package kotlinx.io.streams
22

33
import kotlinx.io.pool.*
44

5+
internal const val ByteArrayPoolBufferSize = 4096
6+
57
internal val ByteArrayPool = object : DefaultPool<ByteArray>(128) {
68
final override fun produceInstance(): ByteArray {
7-
return ByteArray(4096)
9+
return ByteArray(ByteArrayPoolBufferSize)
810
}
911

1012
final override fun validateInstance(instance: ByteArray) {
11-
require(instance.size == 4096) { "Unable to recycle buffer of wrong size: ${instance.size} != 4096" }
13+
require(instance.size == ByteArrayPoolBufferSize) { "Unable to recycle buffer of wrong size: ${instance.size} != 4096" }
1214
super.validateInstance(instance)
1315
}
1416
}

kotlinx-io-jvm/src/main/kotlin/kotlinx/io/streams/Input.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,15 @@ internal class InputStreamAsInput(private val stream: InputStream, pool: ObjectP
99
override fun fill(): IoBuffer? {
1010
val buffer = ByteArrayPool.borrow()
1111
try {
12-
val rc = stream.read(buffer)
12+
val rc = stream.read(buffer, 0, ByteArrayPoolBufferSize - ByteReadPacketBase.ReservedSize)
1313
val result = when {
14-
rc >= 0 -> pool.borrow().also { it.writeFully(buffer, 0, rc) }
14+
rc >= 0 -> pool.borrow().also {
15+
it.reserveEndGap(ByteReadPacketBase.ReservedSize); it.writeFully(
16+
buffer,
17+
0,
18+
rc
19+
)
20+
}
1521
else -> null
1622
}
1723

@@ -28,4 +34,4 @@ internal class InputStreamAsInput(private val stream: InputStream, pool: ObjectP
2834
}
2935
}
3036

31-
fun InputStream.asInput(pool: ObjectPool<IoBuffer> = IoBuffer.Pool): Input = InputStreamAsInput(this, pool)
37+
fun InputStream.asInput(pool: ObjectPool<IoBuffer> = IoBuffer.Pool): Input = InputStreamAsInput(this, pool)

kotlinx-io-jvm/src/test/kotlin/kotlinx/io/tests/ChannelsTest.kt

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ package kotlinx.io.tests
22

33
import kotlinx.io.core.*
44
import kotlinx.io.nio.*
5+
import org.junit.*
56
import java.io.*
67
import java.nio.channels.*
8+
import java.util.*
79
import kotlin.test.*
10+
import kotlin.test.Test
811

912
class ChannelsTest {
1013
@Test
@@ -16,6 +19,24 @@ class ChannelsTest {
1619
assertEquals(0x11223344, input.readInt())
1720
}
1821

22+
@Test
23+
fun testInputBig() {
24+
val array = ByteArray(16384)
25+
array.fill('a'.toByte())
26+
27+
val content = ByteArrayInputStream(array)
28+
val input = Channels.newChannel(content).asInput()
29+
30+
var iterations = 0
31+
while (!input.endOfInput) {
32+
input.peekEquals("erfr")
33+
input.discard(1)
34+
iterations++
35+
}
36+
37+
assertEquals(array.size, iterations)
38+
}
39+
1940
@Test
2041
fun testOutput() {
2142
val baos = ByteArrayOutputStream()
@@ -28,4 +49,16 @@ class ChannelsTest {
2849

2950
assertTrue { byteArrayOf(0x11, 0x22, 0x33, 0x44).contentEquals(baos.toByteArray()) }
3051
}
31-
}
52+
53+
private fun Input.peekEquals(text: String): Boolean {
54+
var equals = false
55+
takeWhileSize(text.length) { buffer ->
56+
val remaining = buffer.readRemaining
57+
val sourceText = buffer.readText(max = text.length)
58+
equals = sourceText == text
59+
buffer.pushBack(remaining - buffer.readRemaining)
60+
0
61+
}
62+
return equals
63+
}
64+
}

kotlinx-io-native/src/main/kotlin/kotlinx/io/core/IoBufferNative.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,26 @@ actual class IoBuffer internal constructor(
858858
return length
859859
}
860860

861+
internal actual fun restoreStartGap(n: Int) {
862+
val rp = readPosition
863+
if (rp < n) {
864+
throw IllegalArgumentException("Can't restore start gap: $n bytes were not reserved before")
865+
}
866+
867+
readPosition = rp - n
868+
}
869+
870+
internal actual fun restoreEndGap(n: Int) {
871+
val newLimit = limit - n
872+
limit = newLimit
873+
if (writePosition > newLimit) {
874+
writePosition = newLimit
875+
}
876+
if (readPosition > newLimit) {
877+
readPosition = newLimit
878+
}
879+
}
880+
861881
internal actual fun writeBufferPrepend(other: IoBuffer) {
862882
val size = other.readRemaining
863883
require(size <= startGap) { "size should be greater than startGap (size = $size, startGap = $startGap)" }
@@ -993,6 +1013,9 @@ actual class IoBuffer internal constructor(
9931013
throw UnsupportedOperationException("close for buffer view is not supported")
9941014
}
9951015

1016+
override fun toString(): String =
1017+
"Buffer[readable = $readRemaining, writable = $writeRemaining, startGap = $startGap, endGap = $endGap]"
1018+
9961019
@Suppress("NOTHING_TO_INLINE")
9971020
private inline fun swap(s: Short): Short = (((s.toInt() and 0xff) shl 8) or ((s.toInt() and 0xffff) ushr 8)).toShort()
9981021
@Suppress("NOTHING_TO_INLINE")

src/main/kotlin/kotlinx/io/core/Buffers.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ expect class IoBuffer : Input, Output {
164164
@Deprecated("Use writeFully instead", ReplaceWith("writeFully(src, length)"))
165165
fun writeBuffer(src: IoBuffer, length: Int): Int
166166

167+
internal fun restoreStartGap(n: Int)
168+
internal fun restoreEndGap(n: Int)
169+
167170
internal fun writeBufferPrepend(other: IoBuffer)
168171
internal fun writeBufferAppend(other: IoBuffer, maxSize: Int)
169172

0 commit comments

Comments
 (0)