Commit ccacab64 authored by Rob Pike's avatar Rob Pike

os: replace non-portable Waitmsg with portable ProcessState

Use methods for key questions.
Provide access to non-portable pieces through portable methods.
Windows and Plan 9 updated.

R=golang-dev, bradfitz, bradfitz, r, dsymonds, rsc, iant, iant
CC=golang-dev
https://golang.org/cl/5673077
parent 22c41ff0
......@@ -56,11 +56,11 @@ func run(stdin []byte, argv []string) (stdout, stderr []byte, ok bool) {
<-c
<-c
w, err := p.Wait()
state, err := p.Wait()
if err != nil {
fatalf("%s", err)
}
ok = w.Exited() && w.ExitStatus() == 0
ok = state.Success()
return
}
......
......@@ -109,10 +109,10 @@ func exec(rw http.ResponseWriter, args []string) (status int) {
log.Printf("os.Wait(%d, 0): %v", p.Pid, err)
return 2
}
status = wait.ExitStatus()
if !wait.Exited() || status > 1 {
if !wait.Success() {
os.Stderr.Write(buf.Bytes())
log.Printf("executing %v failed (exit status = %d)", args, status)
log.Printf("executing %v failed", args)
status = 1 // See comment in default case in dosync.
return
}
......@@ -143,6 +143,8 @@ func dosync(w http.ResponseWriter, r *http.Request) {
// don't change the package tree
syncDelay.set(time.Duration(*syncMin) * time.Minute) // revert to regular sync schedule
default:
// TODO(r): this cannot happen now, since Wait has a boolean exit condition,
// not an integer.
// sync failed because of an error - back off exponentially, but try at least once a day
syncDelay.backoff(24 * time.Hour)
}
......
......@@ -79,9 +79,9 @@ type Cmd struct {
// Process is the underlying process, once started.
Process *os.Process
// Waitmsg contains information about an exited process,
// ProcessState contains information about an exited process,
// available after a call to Wait or Run.
Waitmsg *os.Waitmsg
ProcessState *os.ProcessState
err error // last error (from LookPath, stdin, stdout, stderr)
finished bool // when Wait was called
......@@ -266,11 +266,11 @@ func (c *Cmd) Start() error {
// An ExitError reports an unsuccessful exit by a command.
type ExitError struct {
*os.Waitmsg
*os.ProcessState
}
func (e *ExitError) Error() string {
return e.Waitmsg.String()
return e.ProcessState.String()
}
// Wait waits for the command to exit.
......@@ -291,8 +291,8 @@ func (c *Cmd) Wait() error {
return errors.New("exec: Wait was already called")
}
c.finished = true
msg, err := c.Process.Wait()
c.Waitmsg = msg
state, err := c.Process.Wait()
c.ProcessState = state
var copyError error
for _ = range c.goroutine {
......@@ -307,8 +307,8 @@ func (c *Cmd) Wait() error {
if err != nil {
return err
} else if !msg.Exited() || msg.ExitStatus() != 0 {
return &ExitError{msg}
} else if !state.Success() {
return &ExitError{state}
}
return copyError
......
......@@ -8,6 +8,7 @@ import (
"errors"
"runtime"
"syscall"
"time"
)
// StartProcess starts a new process with the program, arguments and attributes
......@@ -64,14 +65,9 @@ func (p *Process) Kill() error {
return e
}
// Waitmsg stores the information about an exited process as reported by Wait.
type Waitmsg struct {
syscall.Waitmsg
}
// Wait waits for the Process to exit or stop, and then returns a
// Waitmsg describing its status and an error, if any.
func (p *Process) Wait() (w *Waitmsg, err error) {
func (p *Process) Wait() (ps *ProcessState, err error) {
var waitmsg syscall.Waitmsg
if p.Pid == -1 {
......@@ -91,7 +87,11 @@ func (p *Process) Wait() (w *Waitmsg, err error) {
}
}
return &Waitmsg{waitmsg}, nil
ps = &ProcessState{
pid: waitmsg.Pid,
status: waitmsg,
}
return ps, nil
}
// Release releases any resources associated with the Process.
......@@ -108,9 +108,57 @@ func findProcess(pid int) (p *Process, err error) {
return newProcess(pid, 0), nil
}
func (w *Waitmsg) String() string {
if w == nil {
// ProcessState stores information about process as reported by Wait.
type ProcessState struct {
pid int // The process's id.
status syscall.Waitmsg // System-dependent status info.
}
// Pid returns the process id of the exited process.
func (p *ProcessState) Pid() int {
return p.pid
}
// Exited returns whether the program has exited.
func (p *ProcessState) Exited() bool {
return p.status.Exited()
}
// Success reports whether the program exited successfully,
// such as with exit status 0 on Unix.
func (p *ProcessState) Success() bool {
return p.status.ExitStatus() == 0
}
// Sys returns system-dependent exit information about
// the process. Convert it to the appropriate underlying
// type, such as *syscall.Waitmsg on Plan 9, to access its contents.
func (p *ProcessState) Sys() interface{} {
return &p.status
}
// SysUsage returns system-dependent resource usage information about
// the exited process. Convert it to the appropriate underlying
// type, such as *syscall.Waitmsg on Unix, to access its contents.
func (p *ProcessState) SysUsage() interface{} {
return &p.status
}
// UserTime returns the user CPU time of the exited process and its children.
// It is always reported as 0 on Windows.
func (p *ProcessState) UserTime() time.Duration {
return time.Duration(p.status.Time[0]) * time.Millisecond
}
// SystemTime returns the system CPU time of the exited process and its children.
// It is always reported as 0 on Windows.
func (p *ProcessState) SystemTime() time.Duration {
return time.Duration(p.status.Time[1]) * time.Millisecond
}
func (p *ProcessState) String() string {
if p == nil {
return "<nil>"
}
return "exit status: " + w.Msg
return "exit status: " + p.status.Msg
}
......@@ -42,18 +42,41 @@ func (p *Process) Kill() error {
return p.Signal(Kill)
}
// TODO(rsc): Should os implement its own syscall.WaitStatus
// wrapper with the methods, or is exposing the underlying one enough?
//
// TODO(rsc): Certainly need to have Rusage struct,
// since syscall one might have different field types across
// different OS.
// Waitmsg stores the information about an exited process as reported by Wait.
type Waitmsg struct {
Pid int // The process's id.
syscall.WaitStatus // System-dependent status info.
Rusage *syscall.Rusage // System-dependent resource usage info.
// ProcessState stores information about process as reported by Wait.
type ProcessState struct {
pid int // The process's id.
status *syscall.WaitStatus // System-dependent status info.
rusage *syscall.Rusage
}
// Pid returns the process id of the exited process.
func (p *ProcessState) Pid() int {
return p.pid
}
// Exited returns whether the program has exited.
func (p *ProcessState) Exited() bool {
return p.status.Exited()
}
// Success reports whether the program exited successfully,
// such as with exit status 0 on Unix.
func (p *ProcessState) Success() bool {
return p.status.ExitStatus() == 0
}
// Sys returns system-dependent exit information about
// the process. Convert it to the appropriate underlying
// type, such as *syscall.WaitStatus on Unix, to access its contents.
func (p *ProcessState) Sys() interface{} {
return p.status
}
// SysUsage returns system-dependent resource usage information about
// the exited process. Convert it to the appropriate underlying
// type, such as *syscall.Rusage on Unix, to access its contents.
func (p *ProcessState) SysUsage() interface{} {
return p.rusage
}
// Convert i to decimal string.
......@@ -83,26 +106,26 @@ func itod(i int) string {
return string(b[bp:])
}
func (w *Waitmsg) String() string {
if w == nil {
func (p *ProcessState) String() string {
if p == nil {
return "<nil>"
}
// TODO(austin) Use signal names when possible?
status := p.Sys().(*syscall.WaitStatus)
res := ""
switch {
case w.Exited():
res = "exit status " + itod(w.ExitStatus())
case w.Signaled():
res = "signal " + itod(int(w.Signal()))
case w.Stopped():
res = "stop signal " + itod(int(w.StopSignal()))
if w.StopSignal() == syscall.SIGTRAP && w.TrapCause() != 0 {
res += " (trap " + itod(w.TrapCause()) + ")"
case status.Exited():
res = "exit status " + itod(status.ExitStatus())
case status.Signaled():
res = "signal " + itod(int(status.Signal()))
case status.Stopped():
res = "stop signal " + itod(int(status.StopSignal()))
if status.StopSignal() == syscall.SIGTRAP && status.TrapCause() != 0 {
res += " (trap " + itod(status.TrapCause()) + ")"
}
case w.Continued():
case status.Continued():
res = "continued"
}
if w.CoreDump() {
if status.CoreDump() {
res += " (core dumped)"
}
return res
......
......@@ -10,26 +10,30 @@ import (
"errors"
"runtime"
"syscall"
"time"
)
// Wait waits for the Process to exit or stop, and then returns a
// Waitmsg describing its status and an error, if any.
func (p *Process) Wait() (w *Waitmsg, err error) {
// ProcessState describing its status and an error, if any.
func (p *Process) Wait() (ps *ProcessState, err error) {
if p.Pid == -1 {
return nil, syscall.EINVAL
}
var status syscall.WaitStatus
pid1, e := syscall.Wait4(p.Pid, &status, 0, nil)
var rusage syscall.Rusage
pid1, e := syscall.Wait4(p.Pid, &status, 0, &rusage)
if e != nil {
return nil, NewSyscallError("wait", e)
}
if pid1 != 0 {
p.done = true
}
w = new(Waitmsg)
w.Pid = pid1
w.WaitStatus = status
return w, nil
ps = &ProcessState{
pid: pid1,
status: &status,
rusage: &rusage,
}
return ps, nil
}
// Signal sends a signal to the Process.
......@@ -60,3 +64,13 @@ func findProcess(pid int) (p *Process, err error) {
// NOOP for unix.
return newProcess(pid, 0), nil
}
// UserTime returns the user CPU time of the exited process and its children.
func (p *ProcessState) UserTime() time.Duration {
return time.Duration(p.rusage.Utime.Nano()) * time.Nanosecond
}
// SystemTime returns the system CPU time of the exited process and its children.
func (p *ProcessState) SystemTime() time.Duration {
return time.Duration(p.rusage.Stime.Nano()) * time.Nanosecond
}
......@@ -8,12 +8,13 @@ import (
"errors"
"runtime"
"syscall"
"time"
"unsafe"
)
// Wait waits for the Process to exit or stop, and then returns a
// Waitmsg describing its status and an error, if any.
func (p *Process) Wait() (w *Waitmsg, err error) {
// ProcessState describing its status and an error, if any.
func (p *Process) Wait() (ps *ProcessState, err error) {
s, e := syscall.WaitForSingleObject(syscall.Handle(p.handle), syscall.INFINITE)
switch s {
case syscall.WAIT_OBJECT_0:
......@@ -29,7 +30,7 @@ func (p *Process) Wait() (w *Waitmsg, err error) {
return nil, NewSyscallError("GetExitCodeProcess", e)
}
p.done = true
return &Waitmsg{p.Pid, syscall.WaitStatus{Status: s, ExitCode: ec}, new(syscall.Rusage)}, nil
return &ProcessState{p.Pid, &syscall.WaitStatus{Status: s, ExitCode: ec}, new(syscall.Rusage)}, nil
}
// Signal sends a signal to the Process.
......@@ -83,3 +84,15 @@ func init() {
Args[i] = string(syscall.UTF16ToString((*v)[:]))
}
}
// UserTime returns the user CPU time of the exited process and its children.
// For now, it is always reported as 0 on Windows.
func (p *ProcessState) UserTime() time.Duration {
return 0
}
// SystemTime returns the system CPU time of the exited process and its children.
// For now, it is always reported as 0 on Windows.
func (p *ProcessState) SystemTime() time.Duration {
return 0
}
......@@ -1007,10 +1007,10 @@ func TestStatDirWithTrailingSlash(t *testing.T) {
}
}
func TestNilWaitmsgString(t *testing.T) {
var w *Waitmsg
s := w.String()
func TestNilProcessStateString(t *testing.T) {
var ps *ProcessState
s := ps.String()
if s != "<nil>" {
t.Errorf("(*Waitmsg)(nil).String() = %q, want %q", s, "<nil>")
t.Errorf("(*ProcessState)(nil).String() = %q, want %q", s, "<nil>")
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment