Skip to content

Commit 168391b

Browse files
fix: clear _continuation_expected on failure during ROWS continuation
decode_continuation() now sets _continuation_expected to False before raising ProtocolError when receiving a FailureResponse or unexpected message type. Previously the flag stayed True, which was semantically wrong and fragile if poison-on-error behavior were ever relaxed. Closes #103 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 52d6c4c commit 168391b

2 files changed

Lines changed: 28 additions & 0 deletions

File tree

src/dqlitewire/codec.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,10 +288,12 @@ def decode_continuation(self) -> RowsResponse | None:
288288

289289
if header.msg_type == ResponseType.FAILURE:
290290
failure = FailureResponse.decode_body(body, schema=header.schema)
291+
self._continuation_expected = False
291292
raise ProtocolError(
292293
f"Server error during ROWS continuation: [{failure.code}] {failure.message}"
293294
)
294295
if header.msg_type != ResponseType.ROWS:
296+
self._continuation_expected = False
295297
raise ProtocolError(
296298
f"Expected ROWS continuation (type {ResponseType.ROWS}), "
297299
f"got type {header.msg_type}"

tests/test_codec.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,6 +1586,32 @@ def test_failure_during_continuation_poisons(self) -> None:
15861586

15871587
assert decoder.is_poisoned
15881588

1589+
def test_failure_during_continuation_clears_flag(self) -> None:
1590+
"""103: _continuation_expected must be False after FailureResponse."""
1591+
from dqlitewire.constants import ValueType
1592+
from dqlitewire.exceptions import ProtocolError
1593+
from dqlitewire.messages.responses import FailureResponse, RowsResponse
1594+
1595+
initial = RowsResponse(
1596+
column_names=["id"],
1597+
column_types=[ValueType.INTEGER],
1598+
rows=[[1]],
1599+
has_more=True,
1600+
)
1601+
failure = FailureResponse(code=5, message="disk I/O error")
1602+
1603+
decoder = MessageDecoder(is_request=False)
1604+
decoder.feed(initial.encode() + failure.encode())
1605+
1606+
msg = decoder.decode()
1607+
assert isinstance(msg, RowsResponse) and msg.has_more
1608+
1609+
with pytest.raises(ProtocolError, match="disk I/O error"):
1610+
decoder.decode_continuation()
1611+
1612+
# The continuation flag must be cleared even though the buffer is poisoned
1613+
assert not decoder._continuation_expected
1614+
15891615
def test_chained_continuations_part_part_done(self) -> None:
15901616
"""084: three frames — initial(PART) + cont(PART) + cont(DONE)."""
15911617
from dqlitewire.constants import ValueType

0 commit comments

Comments
 (0)