1. 12
    1. 1

      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 {


      If it’s helpful to anyone trying to do this, steal any code/ideas in there!

      1. 7
        localConn, err := net.Listen("tcp", "localhost:0")
        localPort := localConn.Addr().(*net.TCPAddr).Port

        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.

        	select {
        	case err = <-errC:
        		... return ...
        		return nil

        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?

        1. 2

          Thank you for the notes!