diff --git a/client.go b/client.go index 068501c0..9c64ce6e 100644 --- a/client.go +++ b/client.go @@ -299,6 +299,14 @@ func (c *Client) monitor(ctx context.Context) { // a reconnection to the server // close previous secure channel + // + // todo(fs): the two calls to Close() trigger a double-close on both the + // todo(fs): secure channel and the UACP connection. I have guarded for this + // todo(fs): with a sync.Once but that feels like a band-aid. We need to investigate + // todo(fs): why we are trying to create a new secure channel when we shut the client + // todo(fs): down. + // + // https://github.com/gopcua/opcua/pull/470 _ = c.conn.Close() c.sechan.Close() c.sechan = nil diff --git a/uacp/conn.go b/uacp/conn.go index e6a17fa7..379c1c26 100644 --- a/uacp/conn.go +++ b/uacp/conn.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "net" + "sync" "sync/atomic" "github.com/gopcua/opcua/debug" @@ -144,7 +145,7 @@ func (l *Listener) Accept(ctx context.Context) (*Conn, error) { if err != nil { return nil, err } - conn := &Conn{c, nextid(), l.ack} + conn := &Conn{TCPConn: c, id: nextid(), ack: l.ack} if err := conn.srvhandshake(l.endpoint); err != nil { c.Close() return nil, err @@ -171,6 +172,8 @@ type Conn struct { *net.TCPConn id uint32 ack *Acknowledge + + closeOnce sync.Once } func NewConn(c *net.TCPConn, ack *Acknowledge) (*Conn, error) { @@ -203,7 +206,13 @@ func (c *Conn) MaxChunkCount() uint32 { return c.ack.MaxChunkCount } -func (c *Conn) Close() error { +func (c *Conn) Close() (err error) { + err = io.EOF + c.closeOnce.Do(func() { err = c.close() }) + return err +} + +func (c *Conn) close() error { debug.Printf("conn %d: close", c.id) return c.TCPConn.Close() } diff --git a/uasc/secure_channel.go b/uasc/secure_channel.go index 0d860542..9e51d015 100644 --- a/uasc/secure_channel.go +++ b/uasc/secure_channel.go @@ -116,6 +116,8 @@ type SecureChannel struct { // errorCh receive dispatcher errors errCh chan<- error + + closeOnce sync.Once } func NewSecureChannel(endpoint string, c *uacp.Conn, cfg *Config, errCh chan<- error) (*SecureChannel, error) { @@ -819,7 +821,15 @@ func (s *SecureChannel) nextRequestID() uint32 { } // Close closes an existing secure channel -func (s *SecureChannel) Close() error { +func (s *SecureChannel) Close() (err error) { + // https://github.com/gopcua/opcua/pull/470 + // guard against double close until we found the root cause + err = io.EOF + s.closeOnce.Do(func() { err = s.close() }) + return +} + +func (s *SecureChannel) close() error { debug.Printf("uasc Close()") defer func() {