1. 12
  1.  

  2. 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 {
    

    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!

    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 ...
      	default:
      		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!