Improved userspace tunnel concurrency to avoid race conditions.
The problem is described in the issue #502. A potential solution to this problem is to close the reader and writer after the io,Copy() and sending result (EOF, error) to the error channel. By closing the writer and reader the counter part io.Copy() will drain and unblocked. By this change the code will be resistant to one side network connection closes.
The close function needs to be synced with sync.Once to avoid multiple entrance so double freeing.
Apart from this it is essential to use buffered channel like
errCh := make(chan error, 2)
Because, buffered channel sender and receiver should be synced at the same time. This means if the code doesn't reach
return errors.Join(<-err1, <-err2)
at the same time with the line in ioCopyWithErr
errCh <- err
Then it will be dead/circular locked.
I've tested it with different scenarios as mentioned in #502 and it worked very performant and with no locking issue at all.
I'm not a golang expert but after some extensive research this solution seems robust.