diff --git a/pool.go b/pool.go index 28480bf..58e0271 100644 --- a/pool.go +++ b/pool.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "net" "sync" "github.com/chromedp/chromedp/runner" @@ -70,6 +71,18 @@ func (p *Pool) Allocate(ctxt context.Context, opts ...runner.CommandLineOption) r := p.next(ctxt) + // Check if the port is available first. If it's not, Chrome will print + // an "address already in use" error, but it will otherwise keep + // running. This can lead to Allocate succeeding, while the chrome + // process isn't actually listening on the port we need. + l, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", r.port)) + if err != nil { + // we can't use this port, e.g. address already in use + p.errf("pool could not allocate runner on port %d: %v", r.port, err) + return nil, err + } + l.Close() + p.debugf("pool allocating %d", r.port) // create runner diff --git a/pool_test.go b/pool_test.go new file mode 100644 index 0000000..7f96e64 --- /dev/null +++ b/pool_test.go @@ -0,0 +1,43 @@ +package chromedp + +import ( + "context" + "net" + "strconv" + "strings" + "testing" +) + +func TestAllocatePortInUse(t *testing.T) { + t.Parallel() + + // take a random available port + l, err := net.Listen("tcp4", "localhost:0") + if err != nil { + t.Fatal(err) + } + defer l.Close() + + ctxt, cancel := context.WithCancel(context.Background()) + defer cancel() + + // make the pool use the port already in use via a port range + _, portStr, _ := net.SplitHostPort(l.Addr().String()) + port, _ := strconv.Atoi(portStr) + pool, err := NewPool(PortRange(port, port+1)) + if err != nil { + t.Fatal(err) + } + + c, err := pool.Allocate(ctxt) + if err != nil { + want := "address already in use" + got := err.Error() + if !strings.Contains(got, want) { + t.Fatalf("wanted error to contain %q, but got %q", want, got) + } + } else { + t.Fatal("wanted Allocate to error if port is in use") + c.Release() + } +}