-
-
Notifications
You must be signed in to change notification settings - Fork 207
Open
Labels
p2-bugSomething isn't workingSomething isn't working
Description
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:
- Main goroutine calling context.Close()
- Internal route handler goroutines executing user-registered handlers during page.Goto()
To Reproduce
This test demonstrates the race by:
- Setting up HAR replay via RouteFromHAR()
- Adding a custom route handler via Route()
- Starting navigation in background
- 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=10The 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
Labels
p2-bugSomething isn't workingSomething isn't working