2014-06-19 05:08:03 +00:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2019-10-12 00:13:27 +00:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2014-06-19 05:08:03 +00:00
// 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 14:51:45 +00:00
"context"
2014-06-19 05:08:03 +00:00
"fmt"
2019-10-12 00:13:27 +00:00
"io"
2014-06-19 05:08:03 +00:00
"os/exec"
2022-03-25 12:47:12 +00:00
"runtime/pprof"
2019-11-30 14:40:22 +00:00
"sort"
2021-11-30 20:06:32 +00:00
"strconv"
2017-01-17 05:58:58 +00:00
"sync"
2014-06-19 05:08:03 +00:00
"time"
)
2017-01-17 05:58:58 +00: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-06 21:32:36 +00:00
var (
2021-04-09 07:40:34 +00:00
manager * Manager
managerInit sync . Once
2019-11-30 14:40:22 +00:00
// DefaultContext is the default context to run processing commands in
DefaultContext = context . Background ( )
2014-07-06 21:32:36 +00:00
)
2021-11-30 20:06:32 +00:00
// IDType is a pid type
type IDType string
// FinishedFunc is a function that marks that the process is finished and can be removed from the process table
// - it is simply an alias for context.CancelFunc and is only for documentary purposes
type FinishedFunc = context . CancelFunc
2014-06-19 05:08:03 +00:00
2021-11-30 20:06:32 +00:00
// Manager manages all processes and counts PIDs.
2017-01-17 05:58:58 +00:00
type Manager struct {
mutex sync . Mutex
2014-06-19 05:08:03 +00:00
2021-11-30 20:06:32 +00:00
next int64
lastTime int64
processes map [ IDType ] * Process
2017-01-17 05:58:58 +00:00
}
// GetManager returns a Manager and initializes one as singleton if there's none yet
func GetManager ( ) * Manager {
2021-02-03 21:36:38 +00:00
managerInit . Do ( func ( ) {
2017-01-17 05:58:58 +00:00
manager = & Manager {
2021-11-30 20:06:32 +00:00
processes : make ( map [ IDType ] * Process ) ,
next : 1 ,
2017-01-17 05:58:58 +00:00
}
2021-02-03 21:36:38 +00:00
} )
2017-01-17 05:58:58 +00:00
return manager
}
2021-11-30 20:06:32 +00:00
// AddContext creates a new context and adds it as a process. Once the process is finished, finished must be called
// to remove the process from the process table. It should not be called until the process is finished but must always be called.
//
// cancel should be used to cancel the returned context, however it will not remove the process from the process table.
// finished will cancel the returned context and remove it from the process table.
//
// Most processes will not need to use the cancel function but there will be cases whereby you want to cancel the process but not immediately remove it from the
// process table.
func ( pm * Manager ) AddContext ( parent context . Context , description string ) ( ctx context . Context , cancel context . CancelFunc , finished FinishedFunc ) {
ctx , cancel = context . WithCancel ( parent )
2022-03-25 12:47:12 +00:00
ctx , pid , finished := pm . Add ( ctx , description , cancel )
2021-11-30 20:06:32 +00:00
return & Context {
Context : ctx ,
pid : pid ,
} , cancel , finished
}
// AddContextTimeout creates a new context and add it as a process. Once the process is finished, finished must be called
2022-01-10 09:32:37 +00:00
// to remove the process from the process table. It should not be called until the process is finished but must always be called.
2021-11-30 20:06:32 +00:00
//
// cancel should be used to cancel the returned context, however it will not remove the process from the process table.
// finished will cancel the returned context and remove it from the process table.
//
// Most processes will not need to use the cancel function but there will be cases whereby you want to cancel the process but not immediately remove it from the
// process table.
func ( pm * Manager ) AddContextTimeout ( parent context . Context , timeout time . Duration , description string ) ( ctx context . Context , cancel context . CancelFunc , finshed FinishedFunc ) {
2022-03-31 11:56:22 +00:00
if timeout <= 0 {
// it's meaningless to use timeout <= 0, and it must be a bug! so we must panic here to tell developers to make the timeout correct
panic ( "the timeout must be greater than zero, otherwise the context will be cancelled immediately" )
}
2021-11-30 20:06:32 +00:00
ctx , cancel = context . WithTimeout ( parent , timeout )
2022-03-25 12:47:12 +00:00
ctx , pid , finshed := pm . Add ( ctx , description , cancel )
2021-11-30 20:06:32 +00:00
return & Context {
Context : ctx ,
pid : pid ,
} , cancel , finshed
}
// Add create a new process
2022-03-25 12:47:12 +00:00
func ( pm * Manager ) Add ( ctx context . Context , description string , cancel context . CancelFunc ) ( context . Context , IDType , FinishedFunc ) {
parentPID := GetParentPID ( ctx )
2017-01-17 05:58:58 +00:00
pm . mutex . Lock ( )
2021-11-30 20:06:32 +00:00
start , pid := pm . nextPID ( )
parent := pm . processes [ parentPID ]
if parent == nil {
parentPID = ""
}
process := & Process {
2017-01-17 05:58:58 +00:00
PID : pid ,
2021-11-30 20:06:32 +00:00
ParentPID : parentPID ,
2017-01-17 05:58:58 +00:00
Description : description ,
2021-11-30 20:06:32 +00:00
Start : start ,
2019-11-30 14:40:22 +00:00
Cancel : cancel ,
2017-01-17 05:58:58 +00:00
}
2021-11-30 20:06:32 +00:00
finished := func ( ) {
cancel ( )
pm . remove ( process )
2022-03-25 12:47:12 +00:00
pprof . SetGoroutineLabels ( ctx )
2021-11-30 20:06:32 +00:00
}
if parent != nil {
parent . AddChild ( process )
}
pm . processes [ pid ] = process
2017-01-17 05:58:58 +00:00
pm . mutex . Unlock ( )
2022-03-25 12:47:12 +00:00
pprofCtx := pprof . WithLabels ( ctx , pprof . Labels ( "process-description" , description , "ppid" , string ( parentPID ) , "pid" , string ( pid ) ) )
pprof . SetGoroutineLabels ( pprofCtx )
return pprofCtx , pid , finished
2021-11-30 20:06:32 +00:00
}
// nextPID will return the next available PID. pm.mutex should already be locked.
func ( pm * Manager ) nextPID ( ) ( start time . Time , pid IDType ) {
start = time . Now ( )
startUnix := start . Unix ( )
if pm . lastTime == startUnix {
pm . next ++
} else {
pm . next = 1
}
pm . lastTime = startUnix
pid = IDType ( strconv . FormatInt ( start . Unix ( ) , 16 ) )
if pm . next == 1 {
return
}
pid = IDType ( string ( pid ) + "-" + strconv . FormatInt ( pm . next , 10 ) )
return
2014-06-19 05:08:03 +00:00
}
2017-01-17 05:58:58 +00:00
// Remove a process from the ProcessManager.
2021-11-30 20:06:32 +00:00
func ( pm * Manager ) Remove ( pid IDType ) {
2017-01-17 05:58:58 +00:00
pm . mutex . Lock ( )
2019-11-30 14:40:22 +00:00
delete ( pm . processes , pid )
2017-01-17 05:58:58 +00:00
pm . mutex . Unlock ( )
}
2021-11-30 20:06:32 +00:00
func ( pm * Manager ) remove ( process * Process ) {
pm . mutex . Lock ( )
if p := pm . processes [ process . PID ] ; p == process {
delete ( pm . processes , process . PID )
}
parent := pm . processes [ process . ParentPID ]
pm . mutex . Unlock ( )
if parent == nil {
return
}
parent . RemoveChild ( process )
}
2019-11-30 14:40:22 +00:00
// Cancel a process in the ProcessManager.
2021-11-30 20:06:32 +00:00
func ( pm * Manager ) Cancel ( pid IDType ) {
2019-11-30 14:40:22 +00:00
pm . mutex . Lock ( )
process , ok := pm . processes [ pid ]
pm . mutex . Unlock ( )
if ok {
process . Cancel ( )
}
}
// Processes gets the processes in a thread safe manner
2021-11-30 20:06:32 +00:00
func ( pm * Manager ) Processes ( onlyRoots bool ) [ ] * Process {
2019-11-30 14:40:22 +00:00
pm . mutex . Lock ( )
processes := make ( [ ] * Process , 0 , len ( pm . processes ) )
2021-11-30 20:06:32 +00:00
if onlyRoots {
for _ , process := range pm . processes {
if _ , has := pm . processes [ process . ParentPID ] ; ! has {
processes = append ( processes , process )
}
}
} else {
for _ , process := range pm . processes {
processes = append ( processes , process )
}
2019-11-30 14:40:22 +00:00
}
pm . mutex . Unlock ( )
2021-11-30 20:06:32 +00:00
sort . Slice ( processes , func ( i , j int ) bool {
left , right := processes [ i ] , processes [ j ]
return left . Start . Before ( right . Start )
} )
2019-11-30 14:40:22 +00:00
return processes
}
2017-01-17 05:58:58 +00:00
// Exec a command and use the default timeout.
func ( pm * Manager ) Exec ( desc , cmdName string , args ... string ) ( string , string , error ) {
2022-01-19 23:26:57 +00:00
return pm . ExecDir ( DefaultContext , - 1 , "" , desc , cmdName , args ... )
2017-01-17 05:58:58 +00:00
}
// ExecTimeout a command and use a specific timeout duration.
func ( pm * Manager ) ExecTimeout ( timeout time . Duration , desc , cmdName string , args ... string ) ( string , string , error ) {
2022-01-19 23:26:57 +00:00
return pm . ExecDir ( DefaultContext , timeout , "" , desc , cmdName , args ... )
2017-01-17 05:58:58 +00:00
}
// ExecDir a command and use the default timeout.
2022-01-19 23:26:57 +00:00
func ( pm * Manager ) ExecDir ( ctx context . Context , timeout time . Duration , dir , desc , cmdName string , args ... string ) ( string , string , error ) {
return pm . ExecDirEnv ( ctx , timeout , dir , desc , nil , cmdName , args ... )
2017-01-17 05:58:58 +00:00
}
2016-11-27 02:10:08 +00:00
// ExecDirEnv runs a command in given path and environment variables, and waits for its completion
2016-11-15 07:06:31 +00: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)
2022-01-19 23:26:57 +00:00
func ( pm * Manager ) ExecDirEnv ( ctx context . Context , timeout time . Duration , dir , desc string , env [ ] string , cmdName string , args ... string ) ( string , string , error ) {
return pm . ExecDirEnvStdIn ( ctx , timeout , dir , desc , env , nil , cmdName , args ... )
2019-10-12 00:13:27 +00:00
}
// ExecDirEnvStdIn runs a command in given path and environment variables with provided stdIN, and waits for its completion
// 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)
2022-01-19 23:26:57 +00:00
func ( pm * Manager ) ExecDirEnvStdIn ( ctx context . Context , timeout time . Duration , dir , desc string , env [ ] string , stdIn io . Reader , cmdName string , args ... string ) ( string , string , error ) {
2022-03-31 11:56:22 +00:00
if timeout <= 0 {
2017-01-17 05:58:58 +00:00
timeout = 60 * time . Second
2014-07-06 21:32:36 +00:00
}
2017-01-17 05:58:58 +00:00
stdOut := new ( bytes . Buffer )
stdErr := new ( bytes . Buffer )
2014-06-19 05:08:03 +00:00
2022-01-19 23:26:57 +00:00
ctx , _ , finished := pm . AddContextTimeout ( ctx , timeout , desc )
2021-11-30 20:06:32 +00:00
defer finished ( )
2017-11-13 14:51:45 +00:00
cmd := exec . CommandContext ( ctx , cmdName , args ... )
2014-06-19 05:08:03 +00:00
cmd . Dir = dir
2016-11-27 02:10:08 +00:00
cmd . Env = env
2017-01-17 05:58:58 +00:00
cmd . Stdout = stdOut
cmd . Stderr = stdErr
2019-10-12 00:13:27 +00:00
if stdIn != nil {
cmd . Stdin = stdIn
}
2014-07-06 21:32:36 +00:00
if err := cmd . Start ( ) ; err != nil {
2017-01-17 05:58:58 +00:00
return "" , "" , err
2014-07-06 21:32:36 +00:00
}
2014-06-19 05:08:03 +00:00
2017-11-13 14:51:45 +00:00
err := cmd . Wait ( )
2017-01-17 05:58:58 +00:00
if err != nil {
2020-06-13 21:47:31 +00:00
err = & Error {
2021-11-30 20:06:32 +00:00
PID : GetPID ( ctx ) ,
2020-06-13 21:47:31 +00:00
Description : desc ,
Err : err ,
CtxErr : ctx . Err ( ) ,
Stdout : stdOut . String ( ) ,
Stderr : stdErr . String ( ) ,
}
2014-06-19 05:08:03 +00:00
}
2017-01-17 05:58:58 +00:00
2017-11-13 14:51:45 +00:00
return stdOut . String ( ) , stdErr . String ( ) , err
2014-06-19 05:08:03 +00:00
}
2020-06-13 21:47:31 +00:00
// Error is a wrapped error describing the error results of Process Execution
type Error struct {
2021-11-30 20:06:32 +00:00
PID IDType
2020-06-13 21:47:31 +00:00
Description string
Err error
CtxErr error
Stdout string
Stderr string
}
func ( err * Error ) Error ( ) string {
2021-11-30 20:06:32 +00:00
return fmt . Sprintf ( "exec(%s:%s) failed: %v(%v) stdout: %s stderr: %s" , err . PID , err . Description , err . Err , err . CtxErr , err . Stdout , err . Stderr )
2020-06-13 21:47:31 +00:00
}
// Unwrap implements the unwrappable implicit interface for go1.13 Unwrap()
func ( err * Error ) Unwrap ( ) error {
return err . Err
}