run_cqlsh.py: fix parsing of a multi-byte UTF-8 sequence, os.read()/pipe reads can split the sequence across two reads

patch by Dmitry Konstantinov; reviewed by Stefan Miklosovic for CASSANDRA-21466
This commit is contained in:
Dmitry Konstantinov 2026-06-22 12:16:56 +01:00
parent fb7efd6219
commit 5858baf199
1 changed files with 25 additions and 8 deletions

View File

@ -19,6 +19,7 @@
import os
import sys
import re
import codecs
import contextlib
import subprocess
import signal
@ -120,6 +121,12 @@ class ProcRunner:
env = {}
self.env = env
self.readbuf = ''
# os.read()/pipe reads can split a multi-byte UTF-8 sequence across two
# reads (the boundary depends on terminal/readline echo flushing, which
# changed with the Ubuntu 22.04 / readline 8.1 build image). Decode
# incrementally so a partial trailing sequence is buffered until the
# rest of its bytes arrive, instead of failing with UnicodeDecodeError.
self._decoder = codecs.getincrementaldecoder("utf-8")()
self.start_proc()
@ -166,16 +173,26 @@ class ProcRunner:
self.proc.stdin.write(data)
def read_tty(self, blksize, timeout=None):
buf = os.read(self.childpty, blksize)
if isinstance(buf, bytes):
buf = buf.decode("utf-8")
return buf
while True:
buf = os.read(self.childpty, blksize)
if not isinstance(buf, bytes):
return buf
decoded = self._decoder.decode(buf)
# A non-empty read that decodes to '' means we only got the start of
# a multi-byte UTF-8 sequence; keep reading until it completes rather
# than returning '', which callers (read_until) treat as EOF.
# An empty read (b'') is genuine EOF and must propagate as ''.
if decoded != '' or buf == b'':
return decoded
def read_pipe(self, blksize, timeout=None):
buf = self.proc.stdout.read(blksize)
if isinstance(buf, bytes):
buf = buf.decode("utf-8")
return buf
while True:
buf = self.proc.stdout.read(blksize)
if not isinstance(buf, bytes):
return buf
decoded = self._decoder.decode(buf)
if decoded != '' or buf == b'':
return decoded
def read_until(self, until, blksize=4096, timeout=None,
flags=0, ptty_timeout=None, replace=[]):