Go Home Page
The Go Programming Language

Source file src/pkg/exp/ogle/event.go

// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ogle

import (
    "debug/proc"
    "fmt"
    "os"
)

/*
 * Hooks and events
 */

// An EventHandler is a function that takes an event and returns a
// response to that event and possibly an error.  If an event handler
// returns an error, the process stops and no other handlers for that
// event are executed.
type EventHandler func(e Event) (EventAction, os.Error)

// An EventAction is an event handler's response to an event.  If all
// of an event's handlers execute without returning errors, their
// results are combined as follows: If any handler returned
// EAContinue, then the process resumes (without returning from
// WaitStop); otherwise, if any handler returned EAStop, the process
// remains stopped; otherwise, if all handlers returned EADefault, the
// process resumes.  A handler may return EARemoveSelf bit-wise or'd
// with any other action to indicate that the handler should be
// removed from the hook.
type EventAction int

const (
    EARemoveSelf EventAction = 0x100
    EADefault    EventAction = iota
    EAStop
    EAContinue
)

// A EventHook allows event handlers to be added and removed.
type EventHook interface {
    AddHandler(EventHandler)
    RemoveHandler(EventHandler)
    NumHandler() int
    handle(e Event) (EventAction, os.Error)
    String() string
}

// EventHook is almost, but not quite, suitable for user-defined
// events.  If we want user-defined events, make EventHook a struct,
// special-case adding and removing handlers in breakpoint hooks, and
// provide a public interface for posting events to hooks.

type Event interface {
    Process() *Process
    Goroutine() *Goroutine
    String() string
}

type commonHook struct {
    // Head of handler chain
    head *handler
    // Number of non-internal handlers
    len int
}

type handler struct {
    eh EventHandler
    // True if this handler must be run before user-defined
    // handlers in order to ensure correctness.
    internal bool
    // True if this handler has been removed from the chain.
    removed bool
    next    *handler
}

func (h *commonHook) AddHandler(eh EventHandler) {
    h.addHandler(eh, false)
}

func (h *commonHook) addHandler(eh EventHandler, internal bool) {
    // Ensure uniqueness of handlers
    h.RemoveHandler(eh)

    if !internal {
        h.len++
    }
    // Add internal handlers to the beginning
    if internal || h.head == nil {
        h.head = &handler{eh, internal, false, h.head}
        return
    }
    // Add handler after internal handlers
    // TODO(austin) This should probably go on the end instead
    prev := h.head
    for prev.next != nil && prev.internal {
        prev = prev.next
    }
    prev.next = &handler{eh, internal, false, prev.next}
}

func (h *commonHook) RemoveHandler(eh EventHandler) {
    plink := &h.head
    for l := *plink; l != nil; plink, l = &l.next, l.next {
        if l.eh == eh {
            if !l.internal {
                h.len--
            }
            l.removed = true
            *plink = l.next
            break
        }
    }
}

func (h *commonHook) NumHandler() int { return h.len }

func (h *commonHook) handle(e Event) (EventAction, os.Error) {
    action := EADefault
    plink := &h.head
    for l := *plink; l != nil; plink, l = &l.next, l.next {
        if l.removed {
            continue
        }
        a, err := l.eh(e)
        if a&EARemoveSelf == EARemoveSelf {
            if !l.internal {
                h.len--
            }
            l.removed = true
            *plink = l.next
            a &^= EARemoveSelf
        }
        if err != nil {
            return EAStop, err
        }
        if a > action {
            action = a
        }
    }
    return action, nil
}

type commonEvent struct {
    // The process of this event
    p *Process
    // The goroutine of this event.
    t *Goroutine
}

func (e *commonEvent) Process() *Process { return e.p }

func (e *commonEvent) Goroutine() *Goroutine { return e.t }

/*
 * Standard event handlers
 */

// EventPrint is a standard event handler that prints events as they
// occur.  It will not cause the process to stop.
func EventPrint(ev Event) (EventAction, os.Error) {
    // TODO(austin) Include process name here?
    fmt.Fprintf(os.Stderr, "*** %v\n", ev.String())
    return EADefault, nil
}

// EventStop is a standard event handler that causes the process to stop.
func EventStop(ev Event) (EventAction, os.Error) {
    return EAStop, nil
}

/*
 * Breakpoints
 */

type breakpointHook struct {
    commonHook
    p  *Process
    pc proc.Word
}

// A Breakpoint event occurs when a process reaches a particular
// program counter.  When this event is handled, the current goroutine
// will be the goroutine that reached the program counter.
type Breakpoint struct {
    commonEvent
    osThread proc.Thread
    pc       proc.Word
}

func (h *breakpointHook) AddHandler(eh EventHandler) {
    h.addHandler(eh, false)
}

func (h *breakpointHook) addHandler(eh EventHandler, internal bool) {
    // We register breakpoint events lazily to avoid holding
    // references to breakpoints without handlers.  Be sure to use
    // the "canonical" breakpoint if there is one.
    if cur, ok := h.p.breakpointHooks[h.pc]; ok {
        h = cur
    }
    oldhead := h.head
    h.commonHook.addHandler(eh, internal)
    if oldhead == nil && h.head != nil {
        h.p.proc.AddBreakpoint(h.pc)
        h.p.breakpointHooks[h.pc] = h
    }
}

func (h *breakpointHook) RemoveHandler(eh EventHandler) {
    oldhead := h.head
    h.commonHook.RemoveHandler(eh)
    if oldhead != nil && h.head == nil {
        h.p.proc.RemoveBreakpoint(h.pc)
        h.p.breakpointHooks[h.pc] = nil, false
    }
}

func (h *breakpointHook) String() string {
    // TODO(austin) Include process name?
    // TODO(austin) Use line:pc or at least sym+%#x
    return fmt.Sprintf("breakpoint at %#x", h.pc)
}

func (b *Breakpoint) PC() proc.Word { return b.pc }

func (b *Breakpoint) String() string {
    // TODO(austin) Include process name and goroutine
    // TODO(austin) Use line:pc or at least sym+%#x
    return fmt.Sprintf("breakpoint at %#x", b.pc)
}

/*
 * Goroutine create/exit
 */

type goroutineCreateHook struct {
    commonHook
}

func (h *goroutineCreateHook) String() string { return "goroutine create" }

// A GoroutineCreate event occurs when a process creates a new
// goroutine.  When this event is handled, the current goroutine will
// be the newly created goroutine.
type GoroutineCreate struct {
    commonEvent
    parent *Goroutine
}

// Parent returns the goroutine that created this goroutine.  May be
// nil if this event is the creation of the first goroutine.
func (e *GoroutineCreate) Parent() *Goroutine { return e.parent }

func (e *GoroutineCreate) String() string {
    // TODO(austin) Include process name
    if e.parent == nil {
        return fmt.Sprintf("%v created", e.t)
    }
    return fmt.Sprintf("%v created by %v", e.t, e.parent)
}

type goroutineExitHook struct {
    commonHook
}

func (h *goroutineExitHook) String() string { return "goroutine exit" }

// A GoroutineExit event occurs when a Go goroutine exits.
type GoroutineExit struct {
    commonEvent
}

func (e *GoroutineExit) String() string {
    // TODO(austin) Include process name
    //return fmt.Sprintf("%v exited", e.t);
    // For debugging purposes
    return fmt.Sprintf("goroutine %#x exited", e.t.g.addr().base)
}