Skip to content
This repository has been archived by the owner on Jan 30, 2025. It is now read-only.

Cross-platform browser process killer #110

Merged
merged 3 commits into from
Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions chromium/browser_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ func (b *BrowserType) Launch(opts goja.Value) api.Browser {
k6common.Throw(rt, fmt.Errorf("unable to allocate browser: %w", err))
}

// attach the browser process ID to the context
// so that we can kill it afterward if it lingers
// see: k6Throw function
b.Ctx = common.WithProcessID(b.Ctx, browserProc.Pid())
browser, err := common.NewBrowser(b.Ctx, b.CancelFn, browserProc, launchOpts)
if err != nil {
k6common.Throw(rt, err)
Expand Down
5 changes: 5 additions & 0 deletions common/browser_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,8 @@ func (p *BrowserProcess) Terminate() {
func (p *BrowserProcess) WsURL() string {
return p.wsURL
}

// Pid returns the browser process ID
func (p *BrowserProcess) Pid() int {
return p.process.Pid
}
12 changes: 12 additions & 0 deletions common/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type ctxKey int

const (
ctxKeyLaunchOptions ctxKey = iota
ctxKeyPid
ctxKeyHooks
)

Expand All @@ -52,3 +53,14 @@ func GetLaunchOptions(ctx context.Context) *LaunchOptions {
}
return v.(*LaunchOptions)
}

// WithProcessID saves the browser process ID to the context.
func WithProcessID(ctx context.Context, pid int) context.Context {
return context.WithValue(ctx, ctxKeyPid, pid)
}

// GetProcessID returns the browser process ID from the context.
func GetProcessID(ctx context.Context) int {
v, _ := ctx.Value(ctxKeyPid).(int)
return v // it will return zero on error
}
19 changes: 6 additions & 13 deletions common/frame_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import (
"github.com/chromedp/cdproto/security"
"github.com/chromedp/cdproto/target"
"github.com/grafana/xk6-browser/api"
k6common "go.k6.io/k6/js/common"
k6lib "go.k6.io/k6/lib"
k6stats "go.k6.io/k6/stats"
)
Expand Down Expand Up @@ -403,7 +402,6 @@ func (fs *FrameSession) navigateFrame(frame *Frame, url, referrer string) (strin

func (fs *FrameSession) onConsoleAPICalled(event *runtime.EventConsoleAPICalled) {
// TODO: switch to using browser logger instead of directly outputting to k6 logging system
rt := k6common.GetRuntime(fs.ctx)
state := k6lib.GetState(fs.ctx)
l := state.Logger.
WithTime(event.Timestamp.Time()).
Expand All @@ -416,7 +414,7 @@ func (fs *FrameSession) onConsoleAPICalled(event *runtime.EventConsoleAPICalled)
i, err := interfaceFromRemoteObject(arg)
if err != nil {
// TODO(fix): this should not throw!
k6common.Throw(rt, fmt.Errorf("unable to parse remote object value: %w", err))
k6Throw(fs.ctx, "unable to parse remote object value: %w", err)
}
convertedArgs = append(convertedArgs, i)
}
Expand All @@ -439,15 +437,14 @@ func (fs *FrameSession) onExceptionThrown(event *runtime.EventExceptionThrown) {
}

func (fs *FrameSession) onExecutionContextCreated(event *runtime.EventExecutionContextCreated) {
rt := k6common.GetRuntime(fs.ctx)
auxData := event.Context.AuxData
var i struct {
FrameID cdp.FrameID `json:"frameId"`
IsDefault bool `json:"isDefault"`
Type string `json:"type"`
}
if err := json.Unmarshal(auxData, &i); err != nil {
k6common.Throw(rt, fmt.Errorf("unable to unmarshal JSON: %w", err))
k6Throw(fs.ctx, "unable to unmarshal JSON: %w", err)
}
var world string = ""
frame := fs.manager.getFrameByID(i.FrameID)
Expand Down Expand Up @@ -509,19 +506,17 @@ func (fs *FrameSession) onFrameDetached(frameID cdp.FrameID) {
}

func (fs *FrameSession) onFrameNavigated(frame *cdp.Frame, initial bool) {
rt := k6common.GetRuntime(fs.ctx)
err := fs.manager.frameNavigated(frame.ID, frame.ParentID, frame.LoaderID.String(), frame.Name, frame.URL+frame.URLFragment, initial)
if err != nil {
k6common.Throw(rt, err)
k6Throw(fs.ctx, "cannot handle frame navigation: %w", err)
}
}

func (fs *FrameSession) onFrameRequestedNavigation(event *cdppage.EventFrameRequestedNavigation) {
rt := k6common.GetRuntime(fs.ctx)
if event.Disposition == "currentTab" {
err := fs.manager.frameRequestedNavigation(event.FrameID, event.URL, "")
if err != nil {
k6common.Throw(rt, err)
k6Throw(fs.ctx, "cannot handle frame requested navigation: %w", err)
}
}
}
Expand Down Expand Up @@ -655,8 +650,7 @@ func (fs *FrameSession) onAttachedToTarget(event *target.EventAttachedToTarget)
// If we're no longer connected to browser, then ignore WebSocket errors
return
}
rt := k6common.GetRuntime(fs.ctx)
k6common.Throw(rt, err)
k6Throw(fs.ctx, "cannot create frame session: %w", err)
}
fs.page.frameSessions[cdp.FrameID(targetID)] = frameSession
return
Expand All @@ -676,8 +670,7 @@ func (fs *FrameSession) onAttachedToTarget(event *target.EventAttachedToTarget)
// If we're no longer connected to browser, then ignore WebSocket errors
return
}
rt := k6common.GetRuntime(fs.ctx)
k6common.Throw(rt, err)
k6Throw(fs.ctx, "cannot create new worker: %w", err)
}
fs.page.workers[session.id] = w
}
Expand Down
23 changes: 21 additions & 2 deletions common/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"errors"
"fmt"
"math"
"os"
"reflect"
"runtime"
"strconv"
Expand Down Expand Up @@ -308,12 +309,30 @@ func waitForEvent(ctx context.Context, emitter EventEmitter, events []string, pr
return nil, nil
}

// k6Throw throws a k6 error
// k6Throw throws a k6 error, and before throwing the error, it finds the
// browser process from the context and kills it if it still exists.
// TODO: test
func k6Throw(ctx context.Context, format string, a ...interface{}) {
rt := k6common.GetRuntime(ctx)
if rt == nil {
// this should never happen unless a programmer error
panic("cannot get k6 runtime")
}
k6common.Throw(rt, fmt.Errorf(format, a...))
defer k6common.Throw(rt, fmt.Errorf(format, a...))

pid := GetProcessID(ctx)
if pid == 0 {
// this should never happen unless a programmer error
panic("cannot find process id")
}
p, err := os.FindProcess(pid)
if err != nil {
// optimistically return and don't kill the process
return
}
// no need to check the error for waiting the process to release
// its resources or whether we could kill it as we're already
// dying.
_ = p.Release()
_ = p.Kill()
}