mirror of
https://github.com/pgsty/minio.git
synced 2026-10-30 12:30:39 +03:00
fix: flush records queued before an S3 Select error message
The writer goroutine selects between the record channel and the error channel at random. When Evaluate had just queued a record and then reported an error, the error case could win first; it flushed only the staging buffer, so the queued record was dropped in the exit drain and the client saw the error without the records that preceded it. Stage whatever is queued before flushing and writing the error. The CVE-2026-39414 regression test asserted this contract and failed once under the race detector in CI; a new unit test exercises the ordering directly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QwirCh4nsrJCQp2WaXoVLK Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
@@ -295,7 +295,15 @@ func (writer *messageWriter) start() {
|
||||
select {
|
||||
case data := <-writer.errCh:
|
||||
quitFlag = true
|
||||
// Flush collected records before sending error message
|
||||
// A record accepted by SendRecord may still be queued when the
|
||||
// error arrives, because select picks between the two channels
|
||||
// at random. Stage it first so every record produced before the
|
||||
// error precedes the error message instead of being dropped.
|
||||
for len(writer.payloadCh) > 0 {
|
||||
if !writer.stageRecord(<-writer.payloadCh) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !writer.flushRecords() {
|
||||
break
|
||||
}
|
||||
@@ -316,23 +324,8 @@ func (writer *messageWriter) start() {
|
||||
break
|
||||
}
|
||||
writer.write(endMessage)
|
||||
} else {
|
||||
for payload.Len() > 0 {
|
||||
copiedLen := copy(writer.payloadBuffer[writer.payloadBufferIndex:], payload.Bytes())
|
||||
writer.payloadBufferIndex += copiedLen
|
||||
payload.Next(copiedLen)
|
||||
|
||||
// If buffer is filled, flush it now!
|
||||
freeSpace := bufLength - writer.payloadBufferIndex
|
||||
if freeSpace == 0 {
|
||||
if !writer.flushRecords() {
|
||||
quitFlag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bufPool.Put(payload)
|
||||
} else if !writer.stageRecord(payload) {
|
||||
quitFlag = true
|
||||
}
|
||||
|
||||
case <-recordStagingTicker.C:
|
||||
@@ -368,6 +361,26 @@ func (writer *messageWriter) start() {
|
||||
}
|
||||
}
|
||||
|
||||
// stageRecord copies a record into the payload buffer, flushing whenever
|
||||
// the buffer fills, and returns the buffer to the pool. It reports false when
|
||||
// a flush failed.
|
||||
func (writer *messageWriter) stageRecord(payload *bytes.Buffer) bool {
|
||||
defer bufPool.Put(payload)
|
||||
for payload.Len() > 0 {
|
||||
copiedLen := copy(writer.payloadBuffer[writer.payloadBufferIndex:], payload.Bytes())
|
||||
writer.payloadBufferIndex += copiedLen
|
||||
payload.Next(copiedLen)
|
||||
|
||||
// If buffer is filled, flush it now!
|
||||
if bufLength-writer.payloadBufferIndex == 0 {
|
||||
if !writer.flushRecords() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Sends a single whole record.
|
||||
func (writer *messageWriter) SendRecord(payload *bytes.Buffer) error {
|
||||
select {
|
||||
|
||||
Reference in New Issue
Block a user