diff --git a/contract/plugin.go b/contract/plugin.go index 9105256..9bc8ea0 100644 --- a/contract/plugin.go +++ b/contract/plugin.go @@ -24,7 +24,8 @@ type Plugin struct { conn net.Conn // the underlying unix sock file connection pending map[uint64]chan isFSMToPlugin_Payload // the outstanding requests from the contract requestContract map[uint64]*Contract // maps request IDs to their contract context for concurrent operations - l sync.Mutex // thread safety + l sync.Mutex // thread safety for pending/requestContract maps + writeMu sync.Mutex // thread safety for concurrent writes to the socket connection config Config // general app config } @@ -266,6 +267,12 @@ func (p *Plugin) sendLengthPrefixed(bz []byte) *PluginError { // create the length prefix (4 bytes, big endian) lengthPrefix := make([]byte, 4) binary.BigEndian.PutUint32(lengthPrefix, uint32(len(bz))) + // serialize writes: ListenForInbound() spawns a goroutine per inbound message, and each + // can independently write to the shared connection (either as a direct FSM response or + // via a nested StateRead/StateWrite call). Without this lock, concurrent writers can + // interleave mid-message and corrupt the length-prefixed framing on the wire. + p.writeMu.Lock() + defer p.writeMu.Unlock() // write the message (length prefixed) if _, er := p.conn.Write(append(lengthPrefix, bz...)); er != nil { return ErrFailedPluginWrite(er)