mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-05-18 07:41:31 +00:00
fix: handle pipelined SCSI commands during Data-Out collection
The Linux kernel iSCSI initiator pipelines multiple SCSI commands on the same TCP connection (command queuing). When a write needs R2T for data beyond the immediate portion, collectDataOut may read a pipelined SCSI command instead of the expected Data-Out PDU. Fix: queue non-Data-Out PDUs received during collectDataOut into a pending buffer. The main dispatch loop drains pending PDUs before reading from the connection. This correctly handles interleaved commands during multi-PDU write transfers. Bug found during WSL2 smoke test: mkfs.ext4 hangs at "Writing superblocks" because inode table zeroing sends large writes that exceed FirstBurstLength, triggering R2T while the kernel has already queued the next command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,11 @@ type Session struct {
|
||||
// Data sequencing
|
||||
dataInWriter *DataInWriter
|
||||
|
||||
// PDU queue for commands received during Data-Out collection.
|
||||
// The initiator pipelines commands; we may read a SCSI Command
|
||||
// while waiting for Data-Out PDUs.
|
||||
pending []*PDU
|
||||
|
||||
// Shutdown
|
||||
closed atomic.Bool
|
||||
closeErr error
|
||||
@@ -80,7 +85,7 @@ func (s *Session) HandleConnection() error {
|
||||
defer s.close()
|
||||
|
||||
for !s.closed.Load() {
|
||||
pdu, err := ReadPDU(s.conn)
|
||||
pdu, err := s.nextPDU()
|
||||
if err != nil {
|
||||
if s.closed.Load() {
|
||||
return nil
|
||||
@@ -101,6 +106,17 @@ func (s *Session) HandleConnection() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextPDU returns the next PDU to process, draining the pending queue
|
||||
// (populated during Data-Out collection) before reading from the connection.
|
||||
func (s *Session) nextPDU() (*PDU, error) {
|
||||
if len(s.pending) > 0 {
|
||||
pdu := s.pending[0]
|
||||
s.pending = s.pending[1:]
|
||||
return pdu, nil
|
||||
}
|
||||
return ReadPDU(s.conn)
|
||||
}
|
||||
|
||||
// Close terminates the session.
|
||||
func (s *Session) Close() error {
|
||||
s.closed.Store(true)
|
||||
@@ -274,14 +290,17 @@ func (s *Session) collectDataOut(collector *DataOutCollector, itt uint32) error
|
||||
}
|
||||
r2tSN++
|
||||
|
||||
// Read Data-Out PDUs until F-bit
|
||||
// Read Data-Out PDUs until F-bit.
|
||||
// The initiator may pipeline other commands; queue them for later.
|
||||
for {
|
||||
doPDU, err := ReadPDU(s.conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if doPDU.Opcode() != OpSCSIDataOut {
|
||||
return fmt.Errorf("expected Data-Out, got %s", OpcodeName(doPDU.Opcode()))
|
||||
// Not our Data-Out — queue for later dispatch
|
||||
s.pending = append(s.pending, doPDU)
|
||||
continue
|
||||
}
|
||||
if err := collector.AddDataOut(doPDU); err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user