mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-08-31 21:14:01 +03:00
FILE / ScuroNeko/mtg
mtglib/internal/relay/relay.go
Исходный файл и его история в репозитории.
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.
130 lines
2.1 KiB
Go
130 lines
2.1 KiB
Go
package relay
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Relay struct {
|
|
ctx context.Context
|
|
ctxCancel context.CancelFunc
|
|
logger Logger
|
|
processMutex sync.Mutex
|
|
eastBuffer []byte
|
|
westBuffer []byte
|
|
tickChannel chan struct{}
|
|
errorChannel chan error
|
|
tickTimeout time.Duration
|
|
}
|
|
|
|
func (r *Relay) Reset() {
|
|
r.processMutex.Lock()
|
|
defer r.processMutex.Unlock()
|
|
|
|
if r.ctxCancel != nil {
|
|
r.ctxCancel()
|
|
}
|
|
|
|
r.ctx = nil
|
|
r.ctxCancel = nil
|
|
r.logger = nil
|
|
}
|
|
|
|
func (r *Relay) Process(eastConn, westConn io.ReadWriteCloser) error {
|
|
r.processMutex.Lock()
|
|
defer r.processMutex.Unlock()
|
|
|
|
eastConn = conn{
|
|
ReadWriteCloser: eastConn,
|
|
ctx: r.ctx,
|
|
tickChannel: r.tickChannel,
|
|
}
|
|
westConn = conn{
|
|
ReadWriteCloser: westConn,
|
|
ctx: r.ctx,
|
|
tickChannel: r.tickChannel,
|
|
}
|
|
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(3) // nolint: gomnd
|
|
|
|
go r.runObserver(eastConn, westConn, wg)
|
|
|
|
go r.transmit(eastConn, westConn, r.westBuffer, "west", wg)
|
|
|
|
r.transmit(westConn, eastConn, r.eastBuffer, "east", wg)
|
|
|
|
wg.Wait()
|
|
|
|
select {
|
|
case err := <-r.errorChannel:
|
|
return err
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
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()
|
|
}()
|
|
|
|
if _, err := io.CopyBuffer(dst, src, buffer); err != nil {
|
|
r.logger.Printf("error '%v' happened on direction %s", err, direction)
|
|
|
|
select {
|
|
case <-r.ctx.Done():
|
|
err = r.ctx.Err()
|
|
default:
|
|
}
|
|
|
|
select {
|
|
case r.errorChannel <- err:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *Relay) runObserver(one, another io.Closer, wg *sync.WaitGroup) {
|
|
defer wg.Done()
|
|
|
|
ticker := time.NewTicker(time.Second)
|
|
|
|
defer func() {
|
|
one.Close()
|
|
another.Close()
|
|
|
|
ticker.Stop()
|
|
|
|
select {
|
|
case <-ticker.C:
|
|
default:
|
|
}
|
|
}()
|
|
|
|
lastTickAt := time.Now()
|
|
|
|
for {
|
|
select {
|
|
case <-r.ctx.Done():
|
|
return
|
|
case <-r.tickChannel:
|
|
lastTickAt = time.Now()
|
|
case <-ticker.C:
|
|
if time.Since(lastTickAt) > r.tickTimeout {
|
|
r.logger.Printf("exit due to a timeout")
|
|
r.ctxCancel()
|
|
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|