2014-06-19 00:08:03 -05:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2019-10-11 19:13:27 -05:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2014-06-19 00:08:03 -05:00
package process
import (
2017-11-13 09:51:45 -05:00
"context"
2023-02-03 18:11:48 -05:00
"log"
2022-03-25 07:47:12 -05:00
"runtime/pprof"
2021-11-30 15:06:32 -05:00
"strconv"
2017-01-17 00:58:58 -05:00
"sync"
2014-06-19 00:08:03 -05:00
"time"
)
2017-01-17 00:58:58 -05: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 16:32:36 -05:00
var (
2021-04-09 02:40:34 -05:00
manager * Manager
managerInit sync . Once
2019-11-30 09:40:22 -05:00
// DefaultContext is the default context to run processing commands in
DefaultContext = context . Background ( )
2014-07-06 16:32:36 -05:00
)
2022-03-31 12:01:43 -05:00
// DescriptionPProfLabel is a label set on goroutines that have a process attached
const DescriptionPProfLabel = "process-description"
// PIDPProfLabel is a label set on goroutines that have a process attached
const PIDPProfLabel = "pid"
// PPIDPProfLabel is a label set on goroutines that have a process attached
const PPIDPProfLabel = "ppid"
// ProcessTypePProfLabel is a label set on goroutines that have a process attached
const ProcessTypePProfLabel = "process-type"
2021-11-30 15:06:32 -05: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 00:08:03 -05:00
2023-02-03 18:11:48 -05:00
var Trace = defaultTrace // this global can be overridden by particular logging packages - thus avoiding import cycles
func defaultTrace ( start bool , pid IDType , description string , parentPID IDType , typ string ) {
if start && parentPID != "" {
log . Printf ( "start process %s: %s (from %s) (%s)" , pid , description , parentPID , typ )
} else if start {
log . Printf ( "start process %s: %s (%s)" , pid , description , typ )
} else {
log . Printf ( "end process %s: %s" , pid , description )
}
}
2021-11-30 15:06:32 -05:00
// Manager manages all processes and counts PIDs.
2017-01-17 00:58:58 -05:00
type Manager struct {
mutex sync . Mutex
2014-06-19 00:08:03 -05:00
2021-11-30 15:06:32 -05:00
next int64
lastTime int64
2022-03-31 12:01:43 -05:00
processMap map [ IDType ] * process
2017-01-17 00:58:58 -05:00
}
// GetManager returns a Manager and initializes one as singleton if there's none yet
func GetManager ( ) * Manager {
2021-02-03 16:36:38 -05:00
managerInit . Do ( func ( ) {
2017-01-17 00:58:58 -05:00
manager = & Manager {
2022-03-31 12:01:43 -05:00
processMap : make ( map [ IDType ] * process ) ,
next : 1 ,
2017-01-17 00:58:58 -05:00
}
2021-02-03 16:36:38 -05:00
} )
2017-01-17 00:58:58 -05:00
return manager
}
2021-11-30 15:06:32 -05: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-31 12:01:43 -05:00
ctx , _ , finished = pm . Add ( ctx , description , cancel , NormalProcessType , true )
2021-11-30 15:06:32 -05:00
2022-03-31 12:01:43 -05:00
return ctx , cancel , finished
}
// AddTypedContext 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 ) AddTypedContext ( parent context . Context , description , processType string , currentlyRunning bool ) ( ctx context . Context , cancel context . CancelFunc , finished FinishedFunc ) {
ctx , cancel = context . WithCancel ( parent )
ctx , _ , finished = pm . Add ( ctx , description , cancel , processType , currentlyRunning )
return ctx , cancel , finished
2021-11-30 15:06:32 -05:00
}
// AddContextTimeout creates a new context and add it as a process. Once the process is finished, finished must be called
2022-01-10 04:32:37 -05: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 15:06:32 -05: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 06:56:22 -05: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" )
}
2022-03-31 12:01:43 -05:00
2021-11-30 15:06:32 -05:00
ctx , cancel = context . WithTimeout ( parent , timeout )
2022-03-31 12:01:43 -05:00
ctx , _ , finshed = pm . Add ( ctx , description , cancel , NormalProcessType , true )
2021-11-30 15:06:32 -05:00
2022-03-31 12:01:43 -05:00
return ctx , cancel , finshed
2021-11-30 15:06:32 -05:00
}
// Add create a new process
2022-03-31 12:01:43 -05:00
func ( pm * Manager ) Add ( ctx context . Context , description string , cancel context . CancelFunc , processType string , currentlyRunning bool ) ( context . Context , IDType , FinishedFunc ) {
2022-03-25 07:47:12 -05:00
parentPID := GetParentPID ( ctx )
2017-01-17 00:58:58 -05:00
pm . mutex . Lock ( )
2021-11-30 15:06:32 -05:00
start , pid := pm . nextPID ( )
2022-03-31 12:01:43 -05:00
parent := pm . processMap [ parentPID ]
2021-11-30 15:06:32 -05:00
if parent == nil {
parentPID = ""
}
2022-03-31 12:01:43 -05:00
process := & process {
2017-01-17 00:58:58 -05:00
PID : pid ,
2021-11-30 15:06:32 -05:00
ParentPID : parentPID ,
2017-01-17 00:58:58 -05:00
Description : description ,
2021-11-30 15:06:32 -05:00
Start : start ,
2019-11-30 09:40:22 -05:00
Cancel : cancel ,
2022-03-31 12:01:43 -05:00
Type : processType ,
2017-01-17 00:58:58 -05:00
}
2021-11-30 15:06:32 -05:00
2022-03-31 12:01:43 -05:00
var finished FinishedFunc
if currentlyRunning {
finished = func ( ) {
cancel ( )
pm . remove ( process )
pprof . SetGoroutineLabels ( ctx )
}
} else {
finished = func ( ) {
cancel ( )
pm . remove ( process )
}
2021-11-30 15:06:32 -05:00
}
2022-03-31 12:01:43 -05:00
pm . processMap [ pid ] = process
2017-01-17 00:58:58 -05:00
pm . mutex . Unlock ( )
2023-02-03 18:11:48 -05:00
Trace ( true , pid , description , parentPID , processType )
2017-01-17 00:58:58 -05:00
2022-03-31 12:01:43 -05:00
pprofCtx := pprof . WithLabels ( ctx , pprof . Labels ( DescriptionPProfLabel , description , PPIDPProfLabel , string ( parentPID ) , PIDPProfLabel , string ( pid ) , ProcessTypePProfLabel , processType ) )
if currentlyRunning {
pprof . SetGoroutineLabels ( pprofCtx )
}
2022-03-25 07:47:12 -05:00
2022-03-31 12:01:43 -05:00
return & Context {
Context : pprofCtx ,
pid : pid ,
} , pid , finished
2021-11-30 15:06:32 -05: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 ) )
2022-06-20 05:02:49 -05:00
return start , pid
2014-06-19 00:08:03 -05:00
}
2022-03-31 12:01:43 -05:00
func ( pm * Manager ) remove ( process * process ) {
2021-11-30 15:06:32 -05:00
pm . mutex . Lock ( )
2022-03-31 12:01:43 -05:00
defer pm . mutex . Unlock ( )
if p := pm . processMap [ process . PID ] ; p == process {
delete ( pm . processMap , process . PID )
2023-02-03 18:11:48 -05:00
Trace ( false , process . PID , process . Description , process . ParentPID , process . Type )
2021-11-30 15:06:32 -05:00
}
}
2019-11-30 09:40:22 -05:00
// Cancel a process in the ProcessManager.
2021-11-30 15:06:32 -05:00
func ( pm * Manager ) Cancel ( pid IDType ) {
2019-11-30 09:40:22 -05:00
pm . mutex . Lock ( )
2022-03-31 12:01:43 -05:00
process , ok := pm . processMap [ pid ]
2019-11-30 09:40:22 -05:00
pm . mutex . Unlock ( )
2022-03-31 12:01:43 -05:00
if ok && process . Type != SystemProcessType {
2019-11-30 09:40:22 -05:00
process . Cancel ( )
}
}