If anyone wants to see another example of this in practice I had to build an SSH port forwarder for DataStation so that you could connect to databases/web servers running on remote servers.
func (ec EvalContext) withRemoteConnection(si *ServerInfo, host, port string, cb func(host, port string) error) error {
Using net.ListenTCP instead of net.Listen avoids the need for the type assertion, and the concomitant runtime risks.
—
errC := make(chan error)
// Local server
go func() { ... errC <- <error> ... }
...
localPort := ...
cbErr := cb("localhost", fmt.Sprintf("%d", localPort))
if cbErr != nil {
return cbErr
}
If you hit the return cbErr code path, the function will return without ensuring the local server goroutine has terminated, which is a potential leak. Easy fix: move the cb block before the go func block.
The default case here means that withRemoteConnection always return immediately. It will return a nil error by default, and a non-nil error only if (a) the local server goroutine you spawned previously is scheduled by the Go scheduler somehow between the go call and this select block, and (b) that code produces an outcome and sends it to the errC chan before the scheduler executes this block. In any other case, that local server goroutine will block forever on its send to the (unbuffered) errC, which leaks the goroutine. I suppose this isn’t your intent?
If anyone wants to see another example of this in practice I had to build an SSH port forwarder for DataStation so that you could connect to databases/web servers running on remote servers.
https://github.com/multiprocessio/datastation/blob/main/runner/ssh.go
If it’s helpful to anyone trying to do this, steal any code/ideas in there!
Using net.ListenTCP instead of net.Listen avoids the need for the type assertion, and the concomitant runtime risks.
—
If you hit the
return cbErr
code path, the function will return without ensuring the local server goroutine has terminated, which is a potential leak. Easy fix: move thecb
block before thego func
block.—
The
default
case here means thatwithRemoteConnection
always return immediately. It will return a nil error by default, and a non-nil error only if (a) the local server goroutine you spawned previously is scheduled by the Go scheduler somehow between thego
call and thisselect
block, and (b) that code produces an outcome and sends it to the errC chan before the scheduler executes this block. In any other case, that local server goroutine will block forever on its send to the (unbuffered) errC, which leaks the goroutine. I suppose this isn’t your intent?Thank you for the notes!
Here’s a static link that highlights
withRemoteConnection
: https://github.com/multiprocessio/datastation/blob/4e72a6e0af28fcda767b0b29bb5dc9b697d05cb3/runner/ssh.go#L260-L316