2014-06-19 10:38:03 +05:30
// Copyright 2014 The Gogs Authors. All rights reserved.
2019-10-12 05:43:27 +05:30
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 23:50:29 +05:30
// SPDX-License-Identifier: MIT
2014-06-19 10:38:03 +05:30
package process
import (
2017-11-13 20:21:45 +05:30
"context"
2022-03-25 18:17:12 +05:30
"runtime/pprof"
2021-12-01 01:36:32 +05:30
"strconv"
2017-01-17 11:28:58 +05:30
"sync"
2014-06-19 10:38:03 +05:30
"time"
)
2017-01-17 11:28:58 +05:30
// 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 03:02:36 +05:30
var (
2021-04-09 13:10:34 +05:30
manager * Manager
managerInit sync . Once
2019-11-30 20:10:22 +05:30
// DefaultContext is the default context to run processing commands in
DefaultContext = context . Background ( )
2014-07-07 03:02:36 +05:30
)
2022-03-31 22:31:43 +05:30
// 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-12-01 01:36:32 +05:30
// 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 10:38:03 +05:30
2021-12-01 01:36:32 +05:30
// Manager manages all processes and counts PIDs.
2017-01-17 11:28:58 +05:30
type Manager struct {
mutex sync . Mutex
2014-06-19 10:38:03 +05:30
2021-12-01 01:36:32 +05:30
next int64
lastTime int64
2022-03-31 22:31:43 +05:30
processMap map [ IDType ] * process
2017-01-17 11:28:58 +05:30
}
// GetManager returns a Manager and initializes one as singleton if there's none yet
func GetManager ( ) * Manager {
2021-02-04 03:06:38 +05:30
managerInit . Do ( func ( ) {
2017-01-17 11:28:58 +05:30
manager = & Manager {
2022-03-31 22:31:43 +05:30
processMap : make ( map [ IDType ] * process ) ,
next : 1 ,
2017-01-17 11:28:58 +05:30
}
2021-02-04 03:06:38 +05:30
} )
2017-01-17 11:28:58 +05:30
return manager
}
2021-12-01 01:36:32 +05:30
// 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 22:31:43 +05:30
ctx , _ , finished = pm . Add ( ctx , description , cancel , NormalProcessType , true )
2021-12-01 01:36:32 +05:30
2022-03-31 22:31:43 +05:30
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-12-01 01:36:32 +05:30
}
// AddContextTimeout creates a new context and add it as a process. Once the process is finished, finished must be called
2022-01-10 15:02:37 +05:30
// to remove the process from the process table. It should not be called until the process is finished but must always be called.
2021-12-01 01:36:32 +05:30
//
// 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 17:26:22 +05:30
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 22:31:43 +05:30
2021-12-01 01:36:32 +05:30
ctx , cancel = context . WithTimeout ( parent , timeout )
2022-03-31 22:31:43 +05:30
ctx , _ , finshed = pm . Add ( ctx , description , cancel , NormalProcessType , true )
2021-12-01 01:36:32 +05:30
2022-03-31 22:31:43 +05:30
return ctx , cancel , finshed
2021-12-01 01:36:32 +05:30
}
// Add create a new process
2022-03-31 22:31:43 +05:30
func ( pm * Manager ) Add ( ctx context . Context , description string , cancel context . CancelFunc , processType string , currentlyRunning bool ) ( context . Context , IDType , FinishedFunc ) {
2022-03-25 18:17:12 +05:30
parentPID := GetParentPID ( ctx )
2017-01-17 11:28:58 +05:30
pm . mutex . Lock ( )
2021-12-01 01:36:32 +05:30
start , pid := pm . nextPID ( )
2022-03-31 22:31:43 +05:30
parent := pm . processMap [ parentPID ]
2021-12-01 01:36:32 +05:30
if parent == nil {
parentPID = ""
}
2022-03-31 22:31:43 +05:30
process := & process {
2017-01-17 11:28:58 +05:30
PID : pid ,
2021-12-01 01:36:32 +05:30
ParentPID : parentPID ,
2017-01-17 11:28:58 +05:30
Description : description ,
2021-12-01 01:36:32 +05:30
Start : start ,
2019-11-30 20:10:22 +05:30
Cancel : cancel ,
2022-03-31 22:31:43 +05:30
Type : processType ,
2017-01-17 11:28:58 +05:30
}
2021-12-01 01:36:32 +05:30
2022-03-31 22:31:43 +05:30
var finished FinishedFunc
if currentlyRunning {
finished = func ( ) {
cancel ( )
pm . remove ( process )
pprof . SetGoroutineLabels ( ctx )
}
} else {
finished = func ( ) {
cancel ( )
pm . remove ( process )
}
2021-12-01 01:36:32 +05:30
}
2022-03-31 22:31:43 +05:30
pm . processMap [ pid ] = process
2017-01-17 11:28:58 +05:30
pm . mutex . Unlock ( )
2022-03-31 22:31:43 +05:30
pprofCtx := pprof . WithLabels ( ctx , pprof . Labels ( DescriptionPProfLabel , description , PPIDPProfLabel , string ( parentPID ) , PIDPProfLabel , string ( pid ) , ProcessTypePProfLabel , processType ) )
if currentlyRunning {
pprof . SetGoroutineLabels ( pprofCtx )
}
2022-03-25 18:17:12 +05:30
2022-03-31 22:31:43 +05:30
return & Context {
Context : pprofCtx ,
pid : pid ,
} , pid , finished
2021-12-01 01:36:32 +05:30
}
// 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 15:32:49 +05:30
return start , pid
2014-06-19 10:38:03 +05:30
}
2017-01-17 11:28:58 +05:30
// Remove a process from the ProcessManager.
2021-12-01 01:36:32 +05:30
func ( pm * Manager ) Remove ( pid IDType ) {
2017-01-17 11:28:58 +05:30
pm . mutex . Lock ( )
2022-03-31 22:31:43 +05:30
delete ( pm . processMap , pid )
2017-01-17 11:28:58 +05:30
pm . mutex . Unlock ( )
}
2022-03-31 22:31:43 +05:30
func ( pm * Manager ) remove ( process * process ) {
2021-12-01 01:36:32 +05:30
pm . mutex . Lock ( )
2022-03-31 22:31:43 +05:30
defer pm . mutex . Unlock ( )
if p := pm . processMap [ process . PID ] ; p == process {
delete ( pm . processMap , process . PID )
2021-12-01 01:36:32 +05:30
}
}
2019-11-30 20:10:22 +05:30
// Cancel a process in the ProcessManager.
2021-12-01 01:36:32 +05:30
func ( pm * Manager ) Cancel ( pid IDType ) {
2019-11-30 20:10:22 +05:30
pm . mutex . Lock ( )
2022-03-31 22:31:43 +05:30
process , ok := pm . processMap [ pid ]
2019-11-30 20:10:22 +05:30
pm . mutex . Unlock ( )
2022-03-31 22:31:43 +05:30
if ok && process . Type != SystemProcessType {
2019-11-30 20:10:22 +05:30
process . Cancel ( )
}
}