Skip to content

[Bug]: Data race in browserContextImpl between Close() and onRoute() accessing closeWasCalled #566

@flimzy

Description

@flimzy

Environments

  • playwright-go Version: v0.5200.1
  • Browser: Chromium (headless)
  • OS and version: Linux (Debian 13, Ubuntu in GitHub Actions)

Bug description
Data race when calling BrowserContext.Close() while route handlers are actively processing requests during page navigation. This occurs when using RouteFromHAR() or Route() to register handlers, then closing the context while a page is navigating.

The race is between:

  1. Main goroutine calling context.Close()
  2. Internal route handler goroutines executing user-registered handlers during page.Goto()

To Reproduce

This test demonstrates the race by:

  1. Setting up HAR replay via RouteFromHAR()
  2. Adding a custom route handler via Route()
  3. Starting navigation in background
  4. Closing the context while navigation is in progress
package main

import (
"os"
"testing"
"time"

"github.com/playwright-community/playwright-go"
)

func TestDataRace(t *testing.T) {
// Create a minimal HAR file
harContent := `{
"log": {
"version": "1.2",
"creator": {"name": "test", "version": "1.0"},
"entries": [
{
        "request": {
        "method": "GET",
        "url": "https://example.com/",
        "httpVersion": "HTTP/2.0",
        "headers": [],
        "queryString": [],
        "cookies": [],
        "headersSize": -1,
        "bodySize": 0
        },
        "response": {
        "status": 200,
        "statusText": "OK",
        "httpVersion": "HTTP/2.0",
        "headers": [{"name": "Content-Type", "value": "text/html"}],
        "cookies": [],
        "content": {
        "size": 13,
        "mimeType": "text/html",
        "text": "Hello, World!"
        },
        "redirectURL": "",
        "headersSize": -1,
        "bodySize": 13
        },
        "cache": {},
        "timings": {"send": 0, "wait": 0, "receive": 0}
}
]
}
}`

harFile, err := os.CreateTemp("", "test-*.har")
if err != nil {
        t.Fatal(err)
}
defer os.Remove(harFile.Name())

if _, err = harFile.WriteString(harContent); err != nil {
        t.Fatal(err)
}
harFile.Close()

pw, err := playwright.Run()
if err != nil {
        t.Fatal(err)
}
defer pw.Stop()

browser, err := pw.Chromium.Launch()
if err != nil {
        t.Fatal(err)
}
defer browser.Close()

context, err := browser.NewContext()
if err != nil {
        t.Fatal(err)
}

// Set up HAR replay - this creates internal route handlers
err = context.RouteFromHAR(harFile.Name(), playwright.BrowserContextRouteFromHAROptions{
        NotFound: playwright.HarNotFoundAbort,
})
if err != nil {
        t.Fatal(err)
}

// Add custom route - mimics common usage pattern
err = context.Route("**/version.json*", func(route playwright.Route) {
        time.Sleep(5 * time.Millisecond) // Increase race window
        _ = route.Fulfill(playwright.RouteFulfillOptions{
                Status:      playwright.Int(200),
                ContentType: playwright.String("application/json"),
                Body:        playwright.String(`{"version": "1.0"}`),
        })
})
if err != nil {
        t.Fatal(err)
}

page, err := context.NewPage()
if err != nil {
        t.Fatal(err)
}

// Start navigation in background
done := make(chan error, 1)
go func() {
        _, err := page.Goto("https://example.com/")
        done <- err
}()

// Give route handlers time to start
time.Sleep(20 * time.Millisecond)

// Close context while navigation and route handlers are still active
// This triggers the race: Close() writes closeWasCalled
// while onRoute() reads it in multiple route handler goroutines
_ = context.Close()

// Wait for navigation to complete (will fail, but that's expected)
<-done
}

Run with:

go test -race -count=10

The race reproduces reliably (typically within 10 runs).

Additional context

Internally, the closeWasCalled boolean field is accessed from multiple goroutines without synchronization:

  • Written by Close()
  • Read by internal route handler dispatch code

Metadata

Metadata

Assignees

No one assigned

    Labels

    p2-bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions