2014-06-19 09:08:03 +04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package process
import (
"bytes"
2017-11-13 17:51:45 +03:00
"context"
2014-07-07 01:32:36 +04:00
"errors"
2014-06-19 09:08:03 +04:00
"fmt"
"os/exec"
2017-01-17 08:58:58 +03:00
"sync"
2014-06-19 09:08:03 +04:00
"time"
)
2017-01-17 08:58:58 +03:00
// TODO: This packages still uses a singleton for the Manager.
// Once there's a decent web framework and dependencies are passed around like they should,
// then we delete the singleton.
2014-07-07 01:32:36 +04:00
var (
2016-11-15 10:06:31 +03:00
// ErrExecTimeout represent a timeout error
2014-07-07 01:32:36 +04:00
ErrExecTimeout = errors . New ( "Process execution timeout" )
2017-01-17 08:58:58 +03:00
manager * Manager
2014-07-07 01:32:36 +04:00
)
2014-06-19 09:08:03 +04:00
// Process represents a working process inherit from Gogs.
type Process struct {
2017-01-17 08:58:58 +03:00
PID int64 // Process ID, not system one.
2014-06-19 09:08:03 +04:00
Description string
Start time . Time
Cmd * exec . Cmd
}
2017-01-17 08:58:58 +03:00
// Manager knows about all processes and counts PIDs.
type Manager struct {
mutex sync . Mutex
2014-06-19 09:08:03 +04:00
2017-01-17 08:58:58 +03:00
counter int64
Processes map [ int64 ] * Process
}
// GetManager returns a Manager and initializes one as singleton if there's none yet
func GetManager ( ) * Manager {
if manager == nil {
manager = & Manager {
Processes : make ( map [ int64 ] * Process ) ,
}
}
return manager
}
// Add a process to the ProcessManager and returns its PID.
func ( pm * Manager ) Add ( description string , cmd * exec . Cmd ) int64 {
pm . mutex . Lock ( )
pid := pm . counter + 1
pm . Processes [ pid ] = & Process {
PID : pid ,
Description : description ,
2014-06-19 09:08:03 +04:00
Start : time . Now ( ) ,
Cmd : cmd ,
2017-01-17 08:58:58 +03:00
}
pm . counter = pid
pm . mutex . Unlock ( )
2014-06-19 09:08:03 +04:00
return pid
}
2017-01-17 08:58:58 +03:00
// Remove a process from the ProcessManager.
func ( pm * Manager ) Remove ( pid int64 ) {
pm . mutex . Lock ( )
delete ( pm . Processes , pid )
pm . mutex . Unlock ( )
}
// Exec a command and use the default timeout.
func ( pm * Manager ) Exec ( desc , cmdName string , args ... string ) ( string , string , error ) {
return pm . ExecDir ( - 1 , "" , desc , cmdName , args ... )
}
// ExecTimeout a command and use a specific timeout duration.
func ( pm * Manager ) ExecTimeout ( timeout time . Duration , desc , cmdName string , args ... string ) ( string , string , error ) {
return pm . ExecDir ( timeout , "" , desc , cmdName , args ... )
}
// ExecDir a command and use the default timeout.
func ( pm * Manager ) ExecDir ( timeout time . Duration , dir , desc , cmdName string , args ... string ) ( string , string , error ) {
return pm . ExecDirEnv ( timeout , dir , desc , nil , cmdName , args ... )
}
2016-11-27 05:10:08 +03:00
// ExecDirEnv runs a command in given path and environment variables, and waits for its completion
2016-11-15 10:06:31 +03:00
// up to the given timeout (or DefaultTimeout if -1 is given).
// Returns its complete stdout and stderr
// outputs and an error, if any (including timeout)
2017-01-17 08:58:58 +03:00
func ( pm * Manager ) ExecDirEnv ( timeout time . Duration , dir , desc string , env [ ] string , cmdName string , args ... string ) ( string , string , error ) {
2014-07-07 01:32:36 +04:00
if timeout == - 1 {
2017-01-17 08:58:58 +03:00
timeout = 60 * time . Second
2014-07-07 01:32:36 +04:00
}
2017-01-17 08:58:58 +03:00
stdOut := new ( bytes . Buffer )
stdErr := new ( bytes . Buffer )
2014-06-19 09:08:03 +04:00
2017-11-13 17:51:45 +03:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , timeout )
defer cancel ( )
cmd := exec . CommandContext ( ctx , cmdName , args ... )
2014-06-19 09:08:03 +04:00
cmd . Dir = dir
2016-11-27 05:10:08 +03:00
cmd . Env = env
2017-01-17 08:58:58 +03:00
cmd . Stdout = stdOut
cmd . Stderr = stdErr
2014-07-07 01:32:36 +04:00
if err := cmd . Start ( ) ; err != nil {
2017-01-17 08:58:58 +03:00
return "" , "" , err
2014-07-07 01:32:36 +04:00
}
2014-06-19 09:08:03 +04:00
2017-01-17 08:58:58 +03:00
pid := pm . Add ( desc , cmd )
2017-11-13 17:51:45 +03:00
err := cmd . Wait ( )
2017-01-17 08:58:58 +03:00
pm . Remove ( pid )
2014-06-19 09:08:03 +04:00
2017-01-17 08:58:58 +03:00
if err != nil {
2017-11-13 17:51:45 +03:00
err = fmt . Errorf ( "exec(%d:%s) failed: %v(%v) stdout: %v stderr: %v" , pid , desc , err , ctx . Err ( ) , stdOut , stdErr )
2014-06-19 09:08:03 +04:00
}
2017-01-17 08:58:58 +03:00
2017-11-13 17:51:45 +03:00
return stdOut . String ( ) , stdErr . String ( ) , err
2014-06-19 09:08:03 +04:00
}
2017-01-17 08:58:58 +03:00
// Kill and remove a process from list.
func ( pm * Manager ) Kill ( pid int64 ) error {
if proc , exists := pm . Processes [ pid ] ; exists {
pm . mutex . Lock ( )
if proc . Cmd != nil &&
proc . Cmd . Process != nil &&
proc . Cmd . ProcessState != nil &&
! proc . Cmd . ProcessState . Exited ( ) {
if err := proc . Cmd . Process . Kill ( ) ; err != nil {
return fmt . Errorf ( "failed to kill process(%d/%s): %v" , pid , proc . Description , err )
2014-06-19 09:08:03 +04:00
}
}
2017-01-17 08:58:58 +03:00
delete ( pm . Processes , pid )
pm . mutex . Unlock ( )
2014-06-19 09:08:03 +04:00
}
2017-01-17 08:58:58 +03:00
2014-06-19 09:08:03 +04:00
return nil
}