Finds a free port to listen on and creates a new RPC invoker that connects to that port
(ctx context.Context, fwd portforwarder.PortForwarder)
| 75 | |
| 76 | // Finds a free port to listen on and creates a new RPC invoker that connects to that port |
| 77 | func connect(ctx context.Context, fwd portforwarder.PortForwarder) (Invoker, error) { |
| 78 | listener, err := listenTCP() |
| 79 | if err != nil { |
| 80 | return nil, err |
| 81 | } |
| 82 | localAddress := listener.Addr().String() |
| 83 | |
| 84 | invoker := &invoker{ |
| 85 | fwd: fwd, |
| 86 | listener: listener, |
| 87 | } |
| 88 | |
| 89 | // Create a cancelable context to be able to cancel background tasks |
| 90 | // if we encounter an error while connecting to the gRPC server |
| 91 | connectctx, cancel := context.WithCancel(context.Background()) |
| 92 | defer func() { |
| 93 | if err != nil { |
| 94 | cancel() |
| 95 | } |
| 96 | }() |
| 97 | |
| 98 | ch := make(chan error, 2) // Buffered channel to ensure we don't block on the goroutine |
| 99 | |
| 100 | // Ensure we close the port forwarder if we encounter an error |
| 101 | // or once the gRPC connection is closed. pfcancel is retained |
| 102 | // to close the PF whenever we close the gRPC connection. |
| 103 | pfctx, pfcancel := context.WithCancel(connectctx) |
| 104 | invoker.cancelPF = pfcancel |
| 105 | |
| 106 | // Tunnel the remote gRPC server port to the local port |
| 107 | go func() { |
| 108 | // Start forwarding the port locally |
| 109 | opts := portforwarder.ForwardPortOpts{ |
| 110 | Port: codespacesInternalPort, |
| 111 | Internal: true, |
| 112 | } |
| 113 | ch <- fwd.ForwardPortToListener(pfctx, opts, listener) |
| 114 | }() |
| 115 | |
| 116 | var conn *grpc.ClientConn |
| 117 | go func() { |
| 118 | // Attempt to connect to the port |
| 119 | opts := []grpc.DialOption{ |
| 120 | grpc.WithTransportCredentials(insecure.NewCredentials()), |
| 121 | } |
| 122 | conn, err = grpc.NewClient(localAddress, opts...) |
| 123 | ch <- err // nil if we successfully connected |
| 124 | }() |
| 125 | |
| 126 | // Wait for the connection to be established or for the context to be cancelled |
| 127 | select { |
| 128 | case <-ctx.Done(): |
| 129 | return nil, ctx.Err() |
| 130 | case err := <-ch: |
| 131 | if err != nil { |
| 132 | return nil, err |
| 133 | } |
| 134 | } |
no test coverage detected