From 16f9ec690be5e922f39a1ebb3eb631feb233d6cd Mon Sep 17 00:00:00 2001 From: 9seconds Date: Thu, 6 May 2021 22:00:27 +0300 Subject: [PATCH] Fix race condition in closing a relay This commit fixes a situration when relay can be reset before all waiting goroutines are finished. For example, we terminate processing based on some event: socket error etc. So, error happens and context is cancelled. After that a main relay goroutine starts to wait. Meanwhile a second goroutine reaches deferred function and set wg to done. It means that main goroutine can continue. In this case this is really possible that we can start resetting before transmit goroutine really exits. A correct solution is to always do wg.Done() as a first deferred thing on entering to a function. In that case we do not need reordering and so on. --- mtglib/internal/relay/relay.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mtglib/internal/relay/relay.go b/mtglib/internal/relay/relay.go index a9e105a..04cda40 100644 --- a/mtglib/internal/relay/relay.go +++ b/mtglib/internal/relay/relay.go @@ -68,11 +68,12 @@ func (r *Relay) Process(eastConn, westConn io.ReadWriteCloser) error { func (r *Relay) transmit(src io.ReadCloser, dst io.WriteCloser, buffer []byte, direction string, wg *sync.WaitGroup) { + defer wg.Done() + defer func() { + r.ctxCancel() src.Close() dst.Close() - wg.Done() - r.ctxCancel() }() if _, err := io.CopyBuffer(dst, src, buffer); err != nil { @@ -92,6 +93,8 @@ func (r *Relay) transmit(src io.ReadCloser, dst io.WriteCloser, } func (r *Relay) runObserver(one, another io.Closer, wg *sync.WaitGroup) { + defer wg.Done() + ticker := time.NewTicker(time.Second) defer func() { @@ -104,8 +107,6 @@ func (r *Relay) runObserver(one, another io.Closer, wg *sync.WaitGroup) { case <-ticker.C: default: } - - wg.Done() }() lastTickAt := time.Now()