goburrow / modbus

Fault-tolerant implementation of modbus protocol in Go (golang)
BSD 3-Clause "New" or "Revised" License
936 stars 366 forks source link

panic due to concurrent closeIdle and Send() #88

Open dataselfservice opened 1 year ago

dataselfservice commented 1 year ago

Hi,

running version 606c02f and RTU 60 seconds sampling rate (modbus polls) == serialIdleTimeout, after changing hardware (to faster one) the following panic occured.

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0xb5ac4]
goroutine 21 [running]:
io.ReadAtLeast(0x0, 0x0, 0x1cc4a00, 0x100, 0x100, 0x4, 0x0, 0x399844, 0x1c1ad60)
        io/io.go:328 +0x40
github.com/goburrow/modbus.(*rtuSerialTransporter).Send(0x1c86804, 0x1c1ad60, 0x8, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0)
        github.com/goburrow/modbus@v0.1.0/rtuclient.go:137 +0x1dc
github.com/goburrow/modbus.(*client).send(0x1c0e9e0, 0x1c0edc0, 0x2, 0x1c1ad5c, 0x4)
        github.com/goburrow/modbus@v0.1.0/client.go:443 +0x80
github.com/goburrow/modbus.(*client).ReadHoldingRegisters(0x1c0e9e0, 0x50000, 0x39c8b8, 0x1c18c30, 0x0, 0x1, 0x456001)
        github.com/goburrow/modbus@v0.1.0/client.go:112 +0xb4
...

It appears closeIdle, from modbus/serial.go:90 runs in parallel with Send, from modbus/rtuclient.go:113. In some (unfrequent cases) mb.close(), modbus/serial.go:100, from previous Send, occurs in currentSend after 124 and before 137, resulting in panic on line 137.

113 func (mb *rtuSerialTransporter) Send(aduRequest []byte) (aduResponse []byte, err error) {
114         // Make sure port is connected
115         if err = mb.serialPort.connect(); err != nil {
116                 return
117         }
118         // Start the timer to close when idle
119         mb.serialPort.lastActivity = time.Now()
120         mb.serialPort.startCloseTimer()
121         
122         // Send the request
123         mb.serialPort.logf("modbus: sending % x\n", aduRequest)
124         if _, err = mb.port.Write(aduRequest); err != nil {
125                 return
126         }
127         function := aduRequest[1]
128         functionFail := aduRequest[1] & 0x80
129         bytesToRead := calculateResponseLength(aduRequest)
130         time.Sleep(mb.calculateDelay(len(aduRequest) + bytesToRead))
131         
132         var n int
133         var n1 int
134         var data [rtuMaxSize]byte
135         //We first read the minimum length and then read either the full package
136         //or the error package, depending on the error status (byte 2 of the response)
137         n, err = io.ReadAtLeast(mb.port, data[:], rtuMinSize)
138         if err != nil {
139                 return
140         }
...