Skip to content

Commit fdae6eb

Browse files
author
Sergey Mashkov
committed
Fix end gap processing in takeWhile (#23)
1 parent dd6b5ef commit fdae6eb

File tree

6 files changed

+176
-29
lines changed

6 files changed

+176
-29
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,12 @@ actual class ByteReadPacket
6969
internal actual constructor(head: IoBuffer, remaining: Long, pool: ObjectPool<IoBuffer>) : ByteReadPacketPlatformBase(head, remaining, pool), Input {
7070
actual constructor(head: IoBuffer, pool: ObjectPool<IoBuffer>) : this(head, head.remainingAll(), pool)
7171

72+
init {
73+
markNoMoreChunksAvailable()
74+
}
75+
7276
final override fun fill() = null
77+
7378
override fun closeSource() {
7479
}
7580

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ internal actual constructor(head: IoBuffer, remaining: Long, pool: ObjectPool<Io
3939
pool
4040
)
4141

42+
init {
43+
markNoMoreChunksAvailable()
44+
}
45+
4246
final override fun fill() = null
47+
4348
override fun closeSource() {
4449
}
4550

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,7 @@ internal actual constructor(head: IoBuffer, remaining: Long, pool: ObjectPool<Io
4040
actual constructor(head: IoBuffer, pool: ObjectPool<IoBuffer>) : this(head, head.remainingAll(), pool)
4141

4242
init {
43-
if (remaining == 0L) {
44-
doFill()
45-
}
43+
markNoMoreChunksAvailable()
4644
}
4745

4846
final override fun fill() = null

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,8 @@ abstract class ByteReadPacketBase(@PublishedApi internal var head: IoBuffer,
619619

620620
val remaining = current.readRemaining
621621
val overrunSize = minOf(remaining, ReservedSize - current.endGap)
622+
if (next.startGap < overrunSize) return fixGapAfterReadFallback(current)
623+
622624
next.restoreStartGap(overrunSize)
623625

624626
if (remaining > overrunSize) {
@@ -637,6 +639,12 @@ abstract class ByteReadPacketBase(@PublishedApi internal var head: IoBuffer,
637639
}
638640

639641
private fun fixGapAfterReadFallback(current: IoBuffer) {
642+
if (noMoreChunksAvailable) {
643+
this.headRemaining = current.readRemaining
644+
this.tailRemaining = 0
645+
return
646+
}
647+
640648
val size = current.readRemaining
641649
val overrun = minOf(size, ReservedSize - current.endGap)
642650

@@ -645,6 +653,7 @@ abstract class ByteReadPacketBase(@PublishedApi internal var head: IoBuffer,
645653
} else {
646654
val new = pool.borrow()
647655
new.reserveEndGap(ReservedSize)
656+
new.next = current.next
648657

649658
new.writeBufferAppend(current, size)
650659
this.head = new
@@ -665,6 +674,7 @@ abstract class ByteReadPacketBase(@PublishedApi internal var head: IoBuffer,
665674
chunk1.reserveEndGap(ByteReadPacketBase.ReservedSize)
666675
chunk2.reserveEndGap(ByteReadPacketBase.ReservedSize)
667676
chunk1.next = chunk2
677+
chunk2.next = current.next
668678

669679
chunk1.writeBufferAppend(current, size - overrun)
670680
chunk2.writeBufferAppend(current, overrun)
@@ -708,6 +718,12 @@ abstract class ByteReadPacketBase(@PublishedApi internal var head: IoBuffer,
708718

709719
protected abstract fun closeSource()
710720

721+
internal fun markNoMoreChunksAvailable() {
722+
if (!noMoreChunksAvailable) {
723+
noMoreChunksAvailable = true
724+
}
725+
}
726+
711727
protected fun doFill(): IoBuffer? {
712728
if (noMoreChunksAvailable) return null
713729
val chunk = fill()

src/test/kotlin/kotlinx/io/tests/ReadTextCommonTest.kt

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,66 @@ class ReadTextCommonTest {
321321
assertEquals("\u0BF5", text)
322322
}
323323

324+
@Test
325+
fun testReadTextAfterInt() {
326+
buildPacket {
327+
writeInt(5)
328+
writeText("Hello")
329+
writeInt(1)
330+
}.use { packet ->
331+
assertEquals(5, packet.readInt())
332+
assertEquals("Hello", packet.readText(min = 5, max = 5))
333+
assertEquals(1, packet.readInt())
334+
}
335+
}
336+
337+
@Test
338+
fun testReadTextAfterIntFromInput() {
339+
val content = buildPacket {
340+
writeInt(5)
341+
writeText("Hello")
342+
writeInt(1)
343+
344+
writeFully(ByteArray(8192))
345+
}.readBytes()
346+
347+
val input = object : AbstractInput() {
348+
private var offset = 0
349+
350+
override fun fill(): IoBuffer? {
351+
if (offset >= content.size) return null
352+
353+
val buffer = pool.borrow()
354+
buffer.reserveEndGap(8)
355+
356+
val size = minOf(buffer.writeRemaining, content.size - offset)
357+
buffer.writeFully(content, offset, size)
358+
offset += size
359+
360+
return buffer
361+
}
362+
363+
override fun closeSource() {
364+
offset = Int.MAX_VALUE
365+
}
366+
}
367+
368+
try {
369+
assertEquals(5, input.readInt())
370+
assertEquals("Hello", input.readText(min = 5, max = 5))
371+
assertEquals(1, input.readInt())
372+
} finally {
373+
input.close()
374+
}
375+
}
376+
377+
@Test
378+
fun testReadTextFromPacketFromByteArray() {
379+
val content = byteArrayOf(0x31, 0x32, 0x33, 0x00)
380+
val packet = ByteReadPacket(content)
381+
assertEquals("123", packet.readText(max = 3))
382+
}
383+
324384
private inline fun buildPacket(startGap: Int = 0, block: BytePacketBuilder.() -> Unit): ByteReadPacket {
325385
val builder = BytePacketBuilder(startGap, pool)
326386
try {

src/test/kotlin/kotlinx/io/tests/TakeWhileTest.kt

Lines changed: 89 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@ import kotlin.test.*
55

66
class TakeWhileTest {
77
private val pool = VerifyingObjectPool(IoBuffer.NoPool)
8-
private val ch1 = pool.borrow()
9-
private val ch2 = pool.borrow()
8+
private val chunk1 = pool.borrow()
9+
private val chunk2 = pool.borrow()
1010

1111
private val chunks = ArrayList<IoBuffer>()
1212
private val packets = ArrayList<ByteReadPacket>()
1313

1414
@BeforeTest
1515
fun prepare() {
16-
ch1.resetForWrite()
17-
ch2.resetForWrite()
16+
chunk1.resetForWrite()
17+
chunk2.resetForWrite()
1818

19-
ch1.reserveEndGap(8)
20-
ch2.reserveEndGap(8)
19+
chunk1.reserveEndGap(8)
20+
chunk2.reserveEndGap(8)
2121

22-
ch1.next = ch2
22+
chunk1.next = chunk2
2323

24-
chunks.add(ch1)
25-
chunks.add(ch2)
24+
chunks.add(chunk1)
25+
chunks.add(chunk2)
2626
}
2727

2828
@AfterTest
@@ -39,49 +39,112 @@ class TakeWhileTest {
3939

4040
@Test
4141
fun takeWhileSizeAtEdgeNotConsumed() {
42-
ch1.writeFully(cycle(ch1.writeRemaining))
43-
ch2.writeFully(cycle(3))
42+
chunk1.writeFully(cycle(chunk1.writeRemaining))
43+
chunk2.writeFully(cycle(3))
4444

45-
val pkt = ByteReadPacket(ch1, pool)
45+
val pkt = ByteReadPacket(chunk1, pool)
4646
packets.add(pkt)
47-
chunks.remove(ch1)
48-
chunks.remove(ch2)
47+
chunks.remove(chunk1)
48+
chunks.remove(chunk2)
4949

50-
pkt.discardExact(ch1.readRemaining - 1)
50+
pkt.discardExact(chunk1.readRemaining - 1)
5151

5252
assertEquals(4, pkt.remaining)
53-
assertEquals(1, ch1.readRemaining)
54-
assertEquals(3, ch2.readRemaining)
53+
assertEquals(1, chunk1.readRemaining)
54+
assertEquals(3, chunk2.readRemaining)
5555

5656
pkt.takeWhileSize(4) { 0 }
5757
assertEquals(4, pkt.remaining)
5858
}
5959

6060
@Test
6161
fun takeWhileSizeAtEdgeNotConsumed2() {
62-
ch1.writeFully(cycle(ch1.writeRemaining))
63-
ch2.writeFully(cycle(10))
62+
chunk1.writeFully(cycle(chunk1.writeRemaining))
63+
chunk2.writeFully(cycle(10))
6464

65-
val pkt = ByteReadPacket(ch1, pool)
65+
val pkt = ByteReadPacket(chunk1, pool)
6666
packets.add(pkt)
67-
chunks.remove(ch1)
68-
chunks.remove(ch2)
67+
chunks.remove(chunk1)
68+
chunks.remove(chunk2)
6969

70-
pkt.discardExact(ch1.readRemaining - 1)
70+
pkt.discardExact(chunk1.readRemaining - 1)
7171

7272
assertEquals(11, pkt.remaining)
73-
assertEquals(1, ch1.readRemaining)
74-
assertEquals(10, ch2.readRemaining)
73+
assertEquals(1, chunk1.readRemaining)
74+
assertEquals(10, chunk2.readRemaining)
7575

7676
pkt.takeWhileSize(8) { it.discard(7); 0 }
7777

78-
assertNotNull(ch1.next)
78+
assertNotNull(chunk1.next)
7979
assertEquals(4, pkt.remaining)
8080

8181
pkt.takeWhileSize(4) { 0 }
8282

8383
assertEquals(4, pkt.remaining)
8484
}
8585

86+
@Test
87+
fun testTakeWhileFromByteArrayPacket1() {
88+
val content = byteArrayOf(0x31, 0x32, 0x33, 0x00)
89+
val packet = ByteReadPacket(content)
90+
91+
packet.takeWhileSize { it.discardExact(3); 0 }
92+
assertEquals(1, packet.remaining)
93+
packet.discardExact(1)
94+
}
95+
96+
@Test
97+
fun testTakeWhileFromByteArrayPacket2() {
98+
val content = byteArrayOf(0x31, 0x32, 0x33, 0x00)
99+
val packet = ByteReadPacket(content)
100+
101+
packet.takeWhileSize { it.discardExact(4); 0 }
102+
assertEquals(0, packet.remaining)
103+
}
104+
105+
@Test
106+
fun testTakeWhileFromByteArrayPacket3() {
107+
val content = byteArrayOf(0x31, 0x32, 0x33, 0x00)
108+
val packet = ByteReadPacket(content)
109+
110+
packet.takeWhileSize { 0 }
111+
assertEquals(4, packet.remaining)
112+
packet.discardExact(4)
113+
}
114+
115+
@Test
116+
@Ignore
117+
fun testLong() {
118+
val goldenCopy = cycle(8192)
119+
for (discardBefore in 0..8192) {
120+
buildPacket {
121+
writeFully(goldenCopy)
122+
}.use { pkt ->
123+
pkt.discardExact(discardBefore)
124+
val expected = goldenCopy.copyOfRange(discardBefore, goldenCopy.size)
125+
126+
for (initialSize in 1..8) {
127+
for (consume in 0..initialSize) {
128+
val actual = buildPacket {
129+
pkt.copy().use { copy ->
130+
copy.takeWhileSize(initialSize) { buffer ->
131+
val consumed = buffer.readBytes(consume)
132+
writeFully(consumed)
133+
if (consume == 0) 0 else initialSize
134+
}
135+
136+
val remainingBytes = copy.readBytes()
137+
writeFully(remainingBytes)
138+
}
139+
}.readBytes()
140+
141+
val equals = actual.contentEquals(expected)
142+
assertTrue(equals)
143+
}
144+
}
145+
}
146+
}
147+
}
148+
86149
private fun cycle(size: Int) = ByteArray(size) { (it and 0xff).toByte() }
87150
}

0 commit comments

Comments
 (0)