Skip to content

Commit acdc21f

Browse files
authored
Fix various bugs (#99)
* Fix wrong parent for buffer pool * Add toString debug information * Add default length to readUtf8String * Fix readAvailableTo block return value * Prevent redundant buffer borrow * Add tests * Remove obsolete branch in utf8 decoding
1 parent fe899e0 commit acdc21f

File tree

8 files changed

+188
-38
lines changed

8 files changed

+188
-38
lines changed

core/commonMain/src/kotlinx/io/Input.kt

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,10 @@ public abstract class Input : Closeable {
136136

137137
return readBufferRange { buffer, startOffset, endOffset ->
138138
destination.writeBuffer(buffer, startOffset, endOffset)
139-
endOffset
139+
endOffset - startOffset
140140
}
141141
}
142+
142143
// No bytes in cache: fill [destination] buffer direct.
143144
return destination.writeBuffer { buffer, startIndex, endIndex ->
144145
startIndex + fill(buffer, startIndex, endIndex)
@@ -278,8 +279,7 @@ public abstract class Input : Closeable {
278279
var remaining = count
279280
while (remaining > 0) {
280281
val skipCount = readBufferRange { _, startOffset, endOffset ->
281-
val skipCount = min(remaining, endOffset - startOffset)
282-
startOffset + skipCount
282+
min(remaining, endOffset - startOffset)
283283
}
284284

285285
if (skipCount == 0) {
@@ -410,7 +410,7 @@ public abstract class Input : Closeable {
410410
}
411411

412412
/**
413-
* Allows direct read from a buffer, operates on startOffset + endOffset (exclusive), returns new position.
413+
* Allows direct read from a buffer, operates on startOffset + endOffset (exclusive), returns consumed bytes count.
414414
* NOTE: Dangerous to use, if non-local return then position will not be updated.
415415
*
416416
* @return consumed bytes count
@@ -420,10 +420,9 @@ public abstract class Input : Closeable {
420420
return 0
421421
}
422422

423-
val newPosition = reader(buffer, position, limit)
424-
val result = newPosition - position
425-
position = newPosition
426-
return result
423+
val consumed = reader(buffer, position, limit)
424+
position += consumed
425+
return consumed
427426
}
428427

429428
/**
@@ -473,7 +472,11 @@ public abstract class Input : Closeable {
473472
}
474473

475474
val bytes = preview ?: startPreview()
476-
return readThroughPreview(bytes)
475+
return fillAndStoreInPreview(bytes)
476+
}
477+
478+
private fun prepareNewBuffer() {
479+
buffer = bufferPool.borrow()
477480
}
478481

479482
/**
@@ -493,14 +496,15 @@ public abstract class Input : Closeable {
493496
* The [bytes] shouldn't be empty.
494497
*/
495498
private fun fetchFromPreviewAndDiscard(bytes: Bytes): Int {
496-
bufferPool.recycle(buffer)
497499
bytes.discardFirst()
498500

499501
if (bytes.isEmpty()) {
500502
previewBytes = null
501503
return fillFromSource()
502504
}
503505

506+
bufferPool.recycle(buffer)
507+
504508
bytes.pointed(0) { newBuffer, newLimit ->
505509
position = 0
506510
buffer = newBuffer
@@ -510,12 +514,13 @@ public abstract class Input : Closeable {
510514
return limit
511515
}
512516

513-
private fun readThroughPreview(bytes: Bytes): Int {
517+
private fun fillAndStoreInPreview(bytes: Bytes): Int {
514518
val nextIndex = previewIndex + 1
515519

516520
if (bytes.isAfterLast(nextIndex)) {
521+
prepareNewBuffer()
517522
val fetched = fillFromSource() // received data can be empty
518-
bytes.append(buffer, limit) // buffer can be empty
523+
bytes.append(buffer, fetched) // buffer can be empty
519524
previewIndex = nextIndex
520525
return fetched
521526
}
@@ -532,11 +537,9 @@ public abstract class Input : Closeable {
532537
}
533538

534539
private fun fillFromSource(): Int {
535-
val source = bufferPool.borrow()
536-
val fetched = fill(source)
537-
limit = fetched
540+
val fetched = fill(buffer, 0, buffer.size)
538541
position = 0
539-
buffer = source
542+
limit = fetched
540543
return fetched
541544
}
542545

core/commonMain/src/kotlinx/io/buffer/PrimitiveArraysOperations.commom.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ public inline fun Buffer.storeByteArray(
2828
source.useBuffer(sourceOffset, count) { buffer ->
2929
buffer.copyTo(this, 0, count, offset)
3030
}
31-
}
31+
}

core/commonMain/src/kotlinx/io/buffer/UnmanagedBufferPool.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ import kotlin.native.concurrent.*
55

66
internal class UnmanagedBufferPool(
77
private val bufferSize: Int = DEFAULT_BUFFER_SIZE
8-
) : DefaultPool<Buffer>(DEFAULT_POOL_CAPACITY) {
8+
) : ObjectPool<Buffer> {
9+
override val capacity: Int = Int.MAX_VALUE
910

10-
override fun produceInstance(): Buffer = bufferOf(ByteArray(bufferSize))
11+
override fun borrow(): Buffer = bufferOf(ByteArray(bufferSize))
12+
13+
override fun recycle(instance: Buffer) {}
14+
15+
override fun close() {}
1116

1217
@ThreadLocal
1318
companion object {

core/commonMain/src/kotlinx/io/text/TextInputObsolete.kt

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package kotlinx.io.text
22

3-
import kotlinx.io.*
3+
import kotlinx.io.Input
44

5+
private const val DEFAULT_CAPACITY: Int = 32
56

67
/**
78
* Read UTF-8 string until [delimiter] to [output].
@@ -34,7 +35,7 @@ public fun Input.readUtf8StringUntilDelimitersTo(output: Appendable, delimiters:
3435
*
3536
* @throws MalformedInputException if decoder fail to recognize charset.
3637
*/
37-
public fun Input.readUtf8StringTo(output: Appendable, length: Int): Int {
38+
public fun Input.readUtf8StringTo(output: Appendable, length: Int = Int.MAX_VALUE): Int {
3839
var remaining = length
3940
return decodeUtf8Chars {
4041
output.append(it)
@@ -74,8 +75,11 @@ public fun Input.readUtf8LineTo(output: Appendable, limit: Int = Int.MAX_VALUE)
7475
*
7576
* @throws MalformedInputException if decoder fail to recognize charset.
7677
*/
77-
public fun Input.readUtf8String(length: Int): String = buildString(length) {
78-
readUtf8StringTo(this, length)
78+
public fun Input.readUtf8String(length: Int = Int.MAX_VALUE): String {
79+
val capacity = if (length == Int.MAX_VALUE) DEFAULT_CAPACITY else length
80+
return buildString(capacity) {
81+
readUtf8StringTo(this, length)
82+
}
7983
}
8084

8185
/**
@@ -117,20 +121,13 @@ private inline fun Input.decodeUtf8Chars(consumer: (Char) -> Boolean): Int {
117121
malformedInput(value)
118122
if (!consumer(byte.toChar())) {
119123
state = STATE_FINISH
120-
return@readBufferRange offset + 1
124+
return@readBufferRange offset - startOffset + 1
121125
}
122126
count++
123127
}
124128
byteCount == 0 -> {
125129
// first unicode byte
126130
when {
127-
byte < 0x80 -> {
128-
if (!consumer(byte.toChar())) {
129-
state = STATE_FINISH
130-
return@readBufferRange offset + 1
131-
}
132-
count++
133-
}
134131
byte < 0xC0 -> {
135132
byteCount = 0
136133
value = byte and 0x7F
@@ -188,15 +185,15 @@ private inline fun Input.decodeUtf8Chars(consumer: (Char) -> Boolean): Int {
188185
}
189186
if (!more) {
190187
state = STATE_FINISH
191-
return@readBufferRange offset + 1
188+
return@readBufferRange offset - startOffset + 1
192189
}
193190

194191
value = 0
195192
}
196193
}
197194
}
198195
}
199-
endOffset
196+
endOffset - startOffset
200197
}
201198
}
202199
return count
@@ -254,6 +251,7 @@ private val Utf8StateMachine = intArrayOf(
254251
)
255252

256253
private const val STATE_FINISH = -2
254+
257255
//private const val Utf8_STATE_ASCII = -1
258256
internal const val STATE_UTF_8 = 0
259257
internal const val STATE_REJECT = 1

core/commonTest/src/kotlinx/io/InputOutputTest.common.kt

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
@file:Suppress("FORBIDDEN_IDENTITY_EQUALS")
2+
23
package kotlinx.io
34

45
import kotlinx.io.buffer.*
5-
import kotlinx.io.pool.*
6+
import kotlinx.io.pool.DefaultPool
67
import kotlin.test.*
78

89
class InputOutputTest {
@@ -49,7 +50,6 @@ class InputOutputTest {
4950
assertTrue(instance === result)
5051
}
5152

52-
5353
@Test
5454
fun testFillDirect() {
5555
val myBuffer = bufferOf(ByteArray(1024))
@@ -164,6 +164,106 @@ class InputOutputTest {
164164
assertEquals(1023, end)
165165
}
166166

167+
@Test
168+
fun testReadAvailableToReturnValue() {
169+
var readIndex = 0
170+
var writeIndex = 0
171+
172+
val input = object : Input() {
173+
override fun fill(buffer: Buffer, startIndex: Int, endIndex: Int): Int {
174+
readIndex++
175+
176+
buffer.storeByteAt(startIndex, 42)
177+
return 1
178+
}
179+
180+
override fun closeSource() {
181+
return
182+
}
183+
}
184+
185+
val output = object : Output() {
186+
override fun flush(source: Buffer, startIndex: Int, endIndex: Int) {
187+
writeIndex++
188+
189+
assertEquals(startIndex + 1, endIndex)
190+
assertEquals(42, source.loadByteAt(startIndex))
191+
}
192+
193+
override fun closeSource() {
194+
}
195+
}
196+
197+
repeat(DEFAULT_BUFFER_SIZE * 2) {
198+
assertEquals(1, input.readAvailableTo(output))
199+
output.flush()
200+
assertEquals(it + 1, readIndex)
201+
assertEquals(it + 1, writeIndex)
202+
}
203+
204+
repeat(DEFAULT_BUFFER_SIZE * 2) {
205+
input.prefetch(1)
206+
assertEquals(1, input.readAvailableTo(output))
207+
208+
assertEquals(DEFAULT_BUFFER_SIZE * 2 + it + 1, readIndex)
209+
assertEquals(DEFAULT_BUFFER_SIZE * 2 + it + 1, writeIndex)
210+
}
211+
}
212+
213+
@Test
214+
fun testReadAvailableToReturnValueAfterSkipByte() {
215+
var readIndex = 0
216+
var writeIndex = 0
217+
218+
val input = object : Input() {
219+
override fun fill(buffer: Buffer, startIndex: Int, endIndex: Int): Int {
220+
check(startIndex + 10 <= endIndex)
221+
readIndex += 10
222+
return 10
223+
}
224+
225+
override fun closeSource() {
226+
return
227+
}
228+
}
229+
230+
val output = object : Output() {
231+
override fun flush(source: Buffer, startIndex: Int, endIndex: Int) {
232+
val count = endIndex - startIndex
233+
writeIndex += count
234+
assertEquals(9, count)
235+
}
236+
237+
override fun closeSource() {
238+
}
239+
}
240+
241+
242+
repeat(DEFAULT_BUFFER_SIZE * 2) {
243+
input.readByte()
244+
assertEquals(9, input.readAvailableTo(output))
245+
output.flush()
246+
247+
assertEquals((it + 1) * 10, readIndex)
248+
assertEquals((it + 1) * 9, writeIndex)
249+
}
250+
251+
var previousRead = readIndex
252+
var previousWrite = writeIndex
253+
repeat(DEFAULT_BUFFER_SIZE * 2) {
254+
input.prefetch(20)
255+
input.readByte()
256+
assertEquals(9, input.readAvailableTo(output))
257+
input.readByte()
258+
assertEquals(9, input.readAvailableTo(output))
259+
260+
assertEquals(20, readIndex - previousRead)
261+
assertEquals(18, writeIndex - previousWrite)
262+
previousRead = readIndex
263+
previousWrite = writeIndex
264+
}
265+
}
266+
167267
private fun checkException(block: () -> Unit) {
168268
var fail = false
169269
try {

core/commonTest/src/kotlinx/io/text/InputStringTest.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,23 @@ class InputStringTest {
1818
)
1919

2020
@Test
21-
fun decodeUtf8FromInputUntil() = bufferSizes.forEach { size ->
21+
fun testDecodeUtf8FromInputUntil() = bufferSizes.forEach { size ->
2222
val input = buildBytes(size) { writeByteArray(content) }.input()
2323
val result = input.readUtf8StringUntilDelimiter('.')
2424
assertEquals(expected.dropLast(1), result)
2525
}
2626

2727
@Test
28-
fun decodeUtf8FromInput() = bufferSizes.forEach { size ->
28+
fun testDecodeUtf8FromInput() = bufferSizes.forEach { size ->
2929
val input = buildBytes(size) { writeByteArray(content) }.input()
3030
val result = input.readUtf8String(expected.length)
3131
assertEquals(expected, result)
3232
}
33+
34+
@Test
35+
fun testDecodeUtf8FromInputWithoutLength() = bufferSizes.forEach { size ->
36+
val input = buildBytes(size) { writeByteArray(content) }.input()
37+
val result = input.readUtf8String()
38+
assertEquals(expected, result)
39+
}
3340
}

0 commit comments

Comments
 (0)