Compare commits

...

10 Commits

Author SHA1 Message Date
eleksir
3e532f46aa Add System V Init script and systemd unit, update README.md 2024-04-29 14:11:55 +03:00
eleksir
887643f62d Small refactor, move config file path detection out of main() 2024-04-29 13:20:00 +03:00
eleksir
b5bacc5afe Less agressively evict idle workers from worker pool 2024-04-14 10:27:00 +03:00
eleksir
b2788b9ef1 Optimize work with signals 2024-04-14 03:19:17 +03:00
eleksir
e569c6bfce Remove unneeded loop and reduce cpu usage 2024-04-14 02:43:13 +03:00
eleksir
227b9006dd Do no set again nicelevel if it is already set, reduce cpu usage
* Fix logic thar gets nice level to avoide useless renice operations.
* Use maps instead of loop for process name matching
2024-04-13 12:41:55 +03:00
eleksir
cf9c8f0b29 Fix config path for macos if reniced run via sudo 2024-04-08 10:47:59 +03:00
eleksir
d6f9966999 Update README.md 2024-04-06 12:53:16 +03:00
eleksir
dc89d197d9 Add task pool, minor changes
- Add task pool to smooth impact when list in config is pretty long
- Update dependencies
- Update config
2024-04-06 12:39:41 +03:00
eleksir
4d04731aeb Add ability to daemonize program, update sample config, README.md, add .gitignore 2024-03-13 20:08:08 +03:00
51 changed files with 2916 additions and 211 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
reniced

View File

@@ -26,7 +26,6 @@ linters:
- errchkjson
- errorlint
- exhaustive
- exhaustruct
- exportloopref
- ginkgolinter
- gci

View File

@@ -1,57 +1,96 @@
cmd_delay: 33
# Fork and run as real unix daemon.
daemon: true
# PID file for daemon.
pidfile: /var/run/reniced.pid
# Delay between addition and execution of tasks to task pool, to lower cpu load peaks.
cmd_delay: 20
# Delay among iterations.
loop_delay: 5000
# Whether to print debug messages to stdout.
debug: true
# Maximum worker in command pool.
max_workers: 3
# Capacity of non-blocking tasks. All tasks that exceeding this capacity will block submission of new tasks until queue
# has vacant place for new task.
nb_capacity: 30
kill:
- sig: stop
name:
# Backup program. I don't really want it, but have to keep.
- commvault
# Distributed notification service. Sometimes can be naughty.
- distnoted
# This service can be greedy and may make kernel_tak to hig cpu too
- coreduetd
prio:
- nice: -11
name:
# Main audio service, to play sounds smoothly, must have high prio.
- coreaudiod
# Window manager service.
- WindowServer
# Isolation helper.
- "com.apple.audio.SandboxHelper"
# Audio driver helper.
- "com.apple.audio.DriverHelper"
# Used for syncing timers for different audio sources.
- audioclocksyncd
- nice: -5
name:
# Video playback helper.
- VTDecoderXPCService
# Corporate messenger.
- "VK Teams"
# Responds to apple broadcast dns requests. In linux world it is avahi.
- mDNSResponder
# Helper service for mDNSResponder.
- mDNSResponderHelper
# Part of AudioToolbox framework.
- AudioComponentRegistrar
# Mac OS X Core Virtual Machine Server. Something related to isolation of system processes.
- CVMServer
- nice: -3
name:
# 3-rd party tool to make tricks with keyboard. (remap/swap buttons)
- "Karabiner-Elements"
# 3-rd party tool to automate routine actions.
- Hammerspoon
# Compiler for metal shaders, that can pass information directly to the GPU.
- MTLCompilerService
- "com.apple.appkit.xpc.openAndSavePanelService"
- nice: 3
name:
# System logger.
- logd
# System logger.
- syslogd
# Something related to binary signature checks. To ensure system integrity. Part of DRM protection.
# It rubs berfore application starts, so we definitely don't want to low its prio heavily.
- syspolicyd
# Unix periodic task scheduler.
- cron
# Ditto.
- softwareupdated
# Handles the scheduling of the periodic command
- "periodic-wrapper"
- iconservicesagent
- "com.apple.quicklook.ThumbnailsAgent"
- appstored
- nice: 5
name:
# Dynamic brightness tuning service.
- corebrightnessd
- airportd
- containermanagerd
- searchpartyd
- corespeechd
- peopled
- AssetCacheLocatorService
@@ -121,7 +160,5 @@ prio:
- biomesyncd
- siriknowledged
- triald
- syspolicyd
# vim: set ft=yaml et ai ts=2 sw=2 sts=2:

View File

@@ -1,16 +1,16 @@
# Reniced
Helper utility intended to run as a service or as background process (say in tmux) that can make system more respositive
under certain load.
Helper utility intended to run as a service or as background process (say, in tmux) that can make system more
respositive under certain type of load.
## Why it was made
In general case os can handle load well fairly share resources among processes. But in some cases it make it poorly.
As an exanple it can be ill-minded pseudo-security stuff like antiviruses or mediaanalysers that searches forbidden
content on your mac (due to gov.polices that apple have to accept). Or it can be backup that crawls in background.
In general case os can handle load well, fairly sharing resources among processes. But in some cases it makes it poorly.
As an example it can be ill-minded pseudo-security stuff like antiviruses or mediaanalysers that searches forbidden
content on your mac (due to gov.policies that apple have to accept). Or it can be backup that crawls fs in background.
To help resolve such situation this utility was made. Some malicious processes can be renice to lower nicelevels (say,
19) and for some good processes nicelevel can be rised to higher values(-19 if reniced run as superuser or 0 if as
To help resolve such situation this utility was made. Some malicious processes can be reniced to lower nicelevels (say,
19) and for some good processes nicelevel can be raisen to higher values (-19 if reniced run as superuser or 0 if as
normal).
In some critical cases process can be killed (if you want to know more about it see "man kill" in terminal).
@@ -19,7 +19,7 @@ In some critical cases process can be killed (if you want to know more about it
You will need Go lang 1.21 or newer, gnu make, terminal and possibly internets to build this utility.
To build reniced just type in src dir
To build reniced just type in project dir
```bash
make
@@ -40,7 +40,25 @@ if you have no access to superuser or
sudo ./reniced
```
if you can sudo. Thats it.
if you can sudo. That's it.
Note: negative niceness values can be set only by root.
Note2: MacOS has too tight (or intrusive) security measures, so running something as system daemon is very difficult
task. Reniced can be run via sudo as daemon or backbround process (say, in tmux). On linux systems usually there is no
such obstacle and if reniced run from user with id=0, its config should be placed at **/etc/reniced.yaml**
## Install as systemd service
Supplied systemd unit is ready to install in system as-is and should work ok. It expect to see reniced at
**/usr/sbin/reniced** and assume that reniced runs as foreground task - in config file "daemon" setting either absent or
set to false.
## Install as System V Init service
Unlike supplpied systemd unit, supplied System V Init script assumes reniced runs as daemon (in config file "daemon"
setting must be set to true). Script also expects to find pid-file at **/var/run/reniced.pid** and executable itself at
**/usr/sbin/reniced**.
## Special "thanks"

64
contrib/reniced.init Normal file
View File

@@ -0,0 +1,64 @@
#!/bin/bash
# Start/stop/restart reniced.
### BEGIN INIT INFO
# Provides: reniced
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Start the reniced service.
# Description: Service that periodically checks and changes runtime niceness of given (by name) processes.
### END INIT INFO
# Start reniced:
_start() {
echo -n "Starting Reniced daemon: /usr/sbin/reniced"
/usr/sbin/reniced
echo
}
# Stop reniced:
_stop() {
echo -n "Stopping Reniced daemon..."
if [ -r /var/run/reniced.pid ]; then
kill -HUP "$(cat /var/run/reniced.pid)"
rm -f "/var/run/reniced.pid"
else
killall -HUP -q reniced
fi
echo
}
# Restart renicedd:
_restart() {
_stop
sleep 1
_start
}
# Check if reniced is running
_status() {
if [ -e /var/run/reniced.pid ]; then
echo "reniced is running as pid $(cat /run/reniced.pid)."
else
echo "reniced is stopped."
exit 1
fi
}
case "$1" in
'start')
_start
;;
'stop')
_stop
;;
'restart')
_restart
;;
'status')
_status
;;
*)
echo "usage $0 start|stop|restart|status"
esac

10
contrib/reniced.service Normal file
View File

@@ -0,0 +1,10 @@
[Unit]
Description=Reniced Service
[Service]
ExecStart=/usr/sbin/reniced
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target

7
go.mod
View File

@@ -3,17 +3,20 @@ module reniced
go 1.21.4
require (
github.com/shirou/gopsutil/v3 v3.24.2
github.com/alitto/pond v1.8.3
github.com/sevlyar/go-daemon v0.1.6
github.com/shirou/gopsutil/v3 v3.24.3
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/sys v0.19.0 // indirect
)

18
go.sum
View File

@@ -1,3 +1,5 @@
github.com/alitto/pond v1.8.3 h1:ydIqygCLVPqIX/USe5EaV/aSRXTRXDEI9JwuDdu+/xs=
github.com/alitto/pond v1.8.3/go.mod h1:CmvIIGd5jKLasGI3D87qDkQxjzChdKMmnXMg3fG6M6Q=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -8,6 +10,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNeKIkUct/gl9eod1TcXuj8stxvi/GoI=
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
@@ -16,8 +20,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y=
github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk=
github.com/sevlyar/go-daemon v0.1.6 h1:EUh1MDjEM4BI109Jign0EaknA2izkOyi0LV3ro3QQGs=
github.com/sevlyar/go-daemon v0.1.6/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE=
github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE=
github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@@ -25,10 +31,12 @@ github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnj
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
@@ -42,9 +50,9 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

275
main.go
View File

@@ -2,27 +2,79 @@ package main
import (
"log"
"math"
"os"
"os/signal"
"runtime"
"strings"
"syscall"
"time"
"github.com/alitto/pond"
"github.com/sevlyar/go-daemon"
proc "github.com/shirou/gopsutil/v3/process"
)
// Пул воркеров.
var pool *pond.WorkerPool
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов.
var sigChan = make(chan os.Signal, 1)
var kill = make(map[string]syscall.Signal)
var renice = make(map[string]int)
// main основная функция программы.
func main() {
if os.Getenv("HOME") == "" {
log.Fatalln("Unable to get HOME environment variable value")
}
// Самое время поставить траппер сигналов.
signal.Notify(sigChan,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT)
cnf, err := readConf(os.Getenv("HOME") + "/.reniced.yaml")
go sigHandler()
var (
cnf Config
err error
)
cnf, err = readConf()
if err != nil {
log.Fatalf("unable to parse config: %s", err)
}
// Основной цикл.
if cnf.Daemon {
cntxt := &daemon.Context{
WorkDir: "/",
Args: []string{"reniced"},
}
if cnf.Pidfile != "" {
cntxt.PidFileName = cnf.Pidfile
}
if d, err := cntxt.Reborn(); err != nil {
log.Fatal("Unable to run: ", err)
return
} else if d != nil {
return
}
defer cntxt.Release() //nolint: errcheck
}
// То, ради чего всё затевалось.
reniced(cnf)
}
// reniced основная логика.
func reniced(cnf Config) {
// Создаём пул со статическим количеством воркеров и длиной очереди неблокируемых задач, равной NbCapacity.
pool = pond.New(cnf.MaxWorkers, cnf.NbCapacity, pond.Strategy(pond.Lazy()))
for {
processList, err := proc.Processes()
@@ -32,143 +84,77 @@ func main() {
// Не самый эффективный, зато работающий способ - простой перебор массивов.
for _, p := range processList {
if processName, err := p.Name(); err == nil {
for _, k := range cnf.Kill {
for _, killProcessName := range k.Name {
if processName == killProcessName {
switch k.Sig {
case "kill":
_ = p.SendSignal(syscall.SIGKILL)
ok := pool.TrySubmit(func() {
if processName, err := p.Name(); err == nil {
if killSignal := kill[processName]; killSignal != 0 {
switch killSignal { //nolint:exhaustive
case syscall.SIGSTOP:
_ = p.SendSignal(killSignal)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent",
processName,
strings.ToUpper(killSignal.String()),
)
}
default:
_ = p.SendSignal(killSignal)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent",
processName,
strings.ToUpper(killSignal.String()),
)
}
}
}
// Для каждого процесса извлекаем его текущий priority.
if currentPrioLevel, err := syscall.Getpriority(syscall.PRIO_PROCESS, int(p.Pid)); err == nil {
if niceLevel := renice[processName]; niceLevel != 0 {
prioLevel := niceLevel
currentNiceLevel := currentPrioLevel
if runtime.GOOS == "linux" {
// Value of PR = 20 + (-20 to +19) is 0 to 39
switch {
case niceLevel > 0:
prioLevel = 20 - niceLevel
case niceLevel < 0:
prioLevel = 20 + int(math.Abs(float64(niceLevel)))
default:
prioLevel = 20
}
currentNiceLevel = 20 - currentPrioLevel
}
if currentPrioLevel != prioLevel {
// on Linux Setpriority actually operates niceness value.
_ = syscall.Setpriority(syscall.PRIO_PROCESS, int(p.Pid), niceLevel)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent",
"Set niceness for %s(%d) to %d (was %d)",
processName,
strings.ToUpper(k.Sig),
)
}
case "stop":
if s, err := p.Status(); err == nil && s[0] != "stop" {
_ = p.SendSignal(syscall.SIGSTOP)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent",
processName,
strings.ToUpper(k.Sig),
)
}
} else if err == nil && s[0] == "stop" && cnf.Debug {
log.Printf(
"Matching processName with killProcessname (%s) and it is already stopped",
processName,
)
}
case "term":
_ = p.SendSignal(syscall.SIGTERM)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent",
processName,
strings.ToUpper(k.Sig),
)
}
case "int":
_ = p.SendSignal(syscall.SIGINT)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent",
processName,
strings.ToUpper(k.Sig),
)
}
case "quit":
_ = p.SendSignal(syscall.SIGQUIT)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent",
processName,
strings.ToUpper(k.Sig),
)
}
case "abrt":
_ = p.SendSignal(syscall.SIGABRT)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent",
processName,
strings.ToUpper(k.Sig),
)
}
case "hup":
_ = p.SendSignal(syscall.SIGHUP)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent",
processName,
strings.ToUpper(k.Sig),
)
}
case "usr1":
_ = p.SendSignal(syscall.SIGUSR1)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent",
processName,
strings.ToUpper(k.Sig),
)
}
case "usr2":
_ = p.SendSignal(syscall.SIGUSR2)
if cnf.Debug {
log.Printf(
"Matching processName with killProcessname (%s), SIG%s sent",
processName,
strings.ToUpper(k.Sig),
p.Pid,
niceLevel,
currentNiceLevel,
)
}
} else if cnf.Debug {
log.Printf("Niceness for %d already set to %d", p.Pid, niceLevel)
}
}
}
}
})
// Для каждого процесса извлекаем его текущий nicelevel.
if currentNiceLevel, err := p.Nice(); err == nil {
// Вынимаем список приоритетов.
for _, n := range cnf.Prio {
// Для каждого приоритета, проходимся по списку имён процессов.
for _, name := range n.Name {
// Достаём имя текущего процесса.
if processName, err = p.Name(); err == nil {
// Если имя процесса совпадает с одним из списка в конфиге, то пробуем менять ему nice.
if processName == name {
if currentNiceLevel != n.Nice {
_ = syscall.Setpriority(syscall.PRIO_PROCESS, int(p.Pid), int(n.Nice))
if cnf.Debug {
log.Printf(
"Set niceness for %s(%d) to %d (was %d)",
name,
p.Pid,
n.Nice,
currentNiceLevel,
)
}
} else if cnf.Debug {
log.Printf("Nicelevel for %d already set to %d", p.Pid, n.Nice)
}
}
}
}
}
}
if !ok {
log.Printf("Unable to add task to pool")
}
time.Sleep(time.Millisecond * time.Duration(cnf.CmdDelay))
@@ -176,6 +162,33 @@ func main() {
time.Sleep(time.Millisecond * time.Duration(cnf.LoopDelay))
}
// Мы сюда никогда не попадём.
// pool.StopAndWait()
} //nolint:wsl
// sigHandler хэндлер сигналов. Работает на выходе приложения. Держит INT, TERM, QUIT.
func sigHandler() {
for {
var s = <-sigChan
switch s {
case syscall.SIGINT:
log.Print("Got SIGINT, quitting")
case syscall.SIGTERM:
log.Print("Got SIGTERM, quitting")
case syscall.SIGQUIT:
log.Print("Got SIGQUIT, quitting")
// Заходим на новую итерацию, если у нас "неинтересный" сигнал
default:
continue
}
// Закрываем пул воркеров и освобождаем ресурсы.
pool.StopAndWait()
os.Exit(0)
}
}
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */

View File

@@ -1,15 +1,28 @@
package main
type Config struct {
// Weather we fork to backgoround or stay as-is.
Daemon bool `yaml:"daemon,omitempty"`
// Where to put pid-file.
Pidfile string `yaml:"pidfile,omitempty"`
// LoopDelay in milliseconds - whole set of action delay period.
LoopDelay int `yaml:"loop_delay"`
LoopDelay int `yaml:"loop_delay,omitempty"`
// CmdDelay in milliseconds - delay between each change application.
CmdDelay int `yaml:"cmd_delay"`
CmdDelay int `yaml:"cmd_delay,omitempty"`
// Whether to print debug info on stdout.
Debug bool `yaml:"debug,omitempty"`
// Maximum workers in pool.
MaxWorkers int `yaml:"max_workers,omitempty"`
// Capacity of non-blocking tasks. All tasks that exceeding this capacity will block submission of new tasks until
// queue has vacant place for new task.
NbCapacity int `yaml:"nb_capacity,omitempty"`
// В гошке yaml не позволяет сделать удобный парсер для конструкций, аналогичных json-овскому
// {kill:{"stop":["proc1","proc2"], "term":["proc3","proc4"]}}
// Поэтому мы просто возьмём массив и каждому элементу добавим тип сигнала.
@@ -19,7 +32,7 @@ type Config struct {
}
type Prio []struct {
Nice int32 `yaml:"nice,omitempty"`
Nice int `yaml:"nice,omitempty"`
Name []string `yaml:"name,omitempty"`
}

103
util.go
View File

@@ -4,13 +4,44 @@ import (
"fmt"
"log"
"os"
"os/user"
"runtime"
"strings"
"syscall"
"gopkg.in/yaml.v3"
)
// readConf парсит даденный конфиг в глобальную структуру, с которой работает программа.
func readConf(path string) (Config, error) {
// readConf вычиляет, от какого пользователя запущен процесс и в зависимости от результата, скармливает readConfFile тот
// или иной путь, по которому предположительно лежит конфиг.
func readConf() (Config, error) {
var (
cnf Config
err error
)
u, err := user.Current()
if err != nil {
log.Fatalf("Unable to get user info for current user: %s", err)
}
// Для рута и для других пользователей конфиги находятся в разных местах.
if u.Uid != "0" || runtime.GOOS == "darwin" {
if os.Getenv("HOME") == "" {
log.Fatalln("Unable to get HOME environment variable value")
}
cnf, err = readConfFile(os.Getenv("HOME") + "/.reniced.yaml")
} else {
cnf, err = readConfFile("/etc/reniced.yaml")
}
return cnf, err
}
// readConfFile парсит даденный конфиг в глобальную структуру, с которой работает программа.
func readConfFile(path string) (Config, error) {
var cfg Config
fileInfo, err := os.Stat(path)
@@ -22,7 +53,7 @@ func readConf(path string) (Config, error) {
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата.
if fileInfo.Size() > 65535 {
err := fmt.Errorf("Config file %s is too long for config, skipping", path)
err := fmt.Errorf("config file %s is too long for config, skipping", path)
return Config{}, err
}
@@ -39,27 +70,36 @@ func readConf(path string) (Config, error) {
}
if cfg.CmdDelay == 0 {
log.Println("cmd_delay can not be 0, setting to 200 milliseconds")
cfg.CmdDelay = 200
cfg.CmdDelay = 20
log.Printf("cmd_delay can not be 0, setting to %d milliseconds", cfg.CmdDelay)
}
if cfg.LoopDelay == 0 {
log.Println("loop_delay can not be 0, setting to 2000 milliseconds")
cfg.LoopDelay = 2000
log.Printf("loop_delay set to %d milliseconds", cfg.LoopDelay)
}
if cfg.Debug {
log.Println("debug set to true")
} else {
log.Println("debug set to false")
}
newPrio := make(Prio, 0)
if cfg.MaxWorkers == 0 {
cfg.MaxWorkers = 5
log.Printf("max_workers set to %d", cfg.MaxWorkers)
}
if cfg.NbCapacity == 0 {
cfg.NbCapacity = 100
log.Printf("nb_capacity set to %d", cfg.NbCapacity)
}
if cfg.NbCapacity < cfg.MaxWorkers {
cfg.NbCapacity = cfg.MaxWorkers
log.Printf("nb_capacity set to same value as max_workers: %d", cfg.MaxWorkers)
}
// Обычный юзер не может задрать nice больше 0, только руту такое можно.
var maxNice int32
var maxNice int
if os.Getuid() == 0 {
maxNice = -20
@@ -68,10 +108,10 @@ func readConf(path string) (Config, error) {
for _, v := range cfg.Prio {
switch {
case len(v.Name) != 0 && (v.Nice < 20 && v.Nice > maxNice):
newPrio = append(newPrio, v)
for _, procName := range v.Name {
renice[procName] = v.Nice
if cfg.Debug {
for _, procName := range v.Name {
if cfg.Debug {
log.Printf("Add %s to list of nicelevel %d processes.", procName, v.Nice)
}
}
@@ -82,18 +122,35 @@ func readConf(path string) (Config, error) {
}
}
cfg.Prio = newPrio
newKill := make(Kill, 0)
for _, v := range cfg.Kill {
if len(v.Name) != 0 {
switch v.Sig {
case "kill", "stop", "term", "int", "quit", "abrt", "hup", "usr1", "usr2":
newKill = append(newKill, v)
for _, procName := range v.Name {
switch v.Sig {
case "kill":
kill[procName] = syscall.SIGKILL
case "stop":
kill[procName] = syscall.SIGSTOP
case "term":
kill[procName] = syscall.SIGTERM
case "int":
kill[procName] = syscall.SIGINT
case "quit":
kill[procName] = syscall.SIGQUIT
case "abrt":
kill[procName] = syscall.SIGABRT
case "hup":
kill[procName] = syscall.SIGHUP
case "usr1":
kill[procName] = syscall.SIGUSR1
case "usr2":
kill[procName] = syscall.SIGUSR2
default:
continue
}
if cfg.Debug {
for _, procName := range v.Name {
if cfg.Debug {
log.Printf(
"Add %s to list of processes that should be killed with SIG%s signal.",
procName,
@@ -109,8 +166,6 @@ func readConf(path string) (Config, error) {
}
}
cfg.Kill = newKill
return cfg, nil
}

12
vendor/github.com/alitto/pond/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,12 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out

21
vendor/github.com/alitto/pond/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Alejandro Durante
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

5
vendor/github.com/alitto/pond/Makefile generated vendored Normal file
View File

@@ -0,0 +1,5 @@
test:
go test -race -v ./
coverage:
go test -race -v -coverprofile=coverage.out -covermode=atomic ./

293
vendor/github.com/alitto/pond/README.md generated vendored Normal file
View File

@@ -0,0 +1,293 @@
<a title="Codecov" target="_blank" href="https://github.com/alitto/pond/actions"><img alt="Build status" src="https://github.com/alitto/pond/actions/workflows/main.yml/badge.svg?branch=master&event=push"/></a>
<a title="Codecov" target="_blank" href="https://codecov.io/gh/alitto/pond"><img src="https://codecov.io/gh/alitto/pond/branch/master/graph/badge.svg"/></a>
<a title="Release" target="_blank" href="https://github.com/alitto/pond/releases"><img src="https://img.shields.io/github/v/release/alitto/pond"/></a>
<a title="Go Report Card" target="_blank" href="https://goreportcard.com/report/github.com/alitto/pond"><img src="https://goreportcard.com/badge/github.com/alitto/pond"/></a>
# pond
Minimalistic and High-performance goroutine worker pool written in Go
## Motivation
This library is meant to provide a simple way to limit concurrency when executing some function over a limited resource or service.
Some common scenarios include:
- Executing queries against a Database with a limited no. of connections
- Sending HTTP requests to a a rate/concurrency limited API
## Features:
- Zero dependencies
- Create pools with fixed or dynamic size
- Worker goroutines are only created when needed (backpressure detection) and automatically purged after being idle for some time (configurable)
- Minimalistic APIs for:
- Creating worker pools with fixed or dynamic size
- Submitting tasks to a pool in a fire-and-forget fashion
- Submitting tasks to a pool and waiting for them to complete
- Submitting tasks to a pool with a deadline
- Submitting a group of tasks and waiting for them to complete
- Submitting a group of tasks associated to a Context
- Getting the number of running workers (goroutines)
- Stopping a worker pool
- Task panics are handled gracefully (configurable panic handler)
- Supports Non-blocking and Blocking task submission modes (buffered / unbuffered)
- Very high performance and efficient resource usage under heavy workloads, even outperforming unbounded goroutines in some scenarios (See [benchmarks](https://github.com/alitto/pond-benchmarks))
- Configurable pool resizing strategy, with 3 presets for common scenarios: Eager, Balanced and Lazy.
- Complete pool metrics such as number of running workers, tasks waiting in the queue [and more](#metrics--monitoring).
- **New (since v1.7.0)**: configurable parent context and graceful shutdown with deadline.
- [API reference](https://pkg.go.dev/github.com/alitto/pond)
## How to install
```bash
go get -u github.com/alitto/pond
```
## How to use
### Worker pool with dynamic size
``` go
package main
import (
"fmt"
"github.com/alitto/pond"
)
func main() {
// Create a buffered (non-blocking) pool that can scale up to 100 workers
// and has a buffer capacity of 1000 tasks
pool := pond.New(100, 1000)
// Submit 1000 tasks
for i := 0; i < 1000; i++ {
n := i
pool.Submit(func() {
fmt.Printf("Running task #%d\n", n)
})
}
// Stop the pool and wait for all submitted tasks to complete
pool.StopAndWait()
}
```
### Worker pool with fixed size
``` go
package main
import (
"fmt"
"github.com/alitto/pond"
)
func main() {
// Create an unbuffered (blocking) pool with a fixed
// number of workers
pool := pond.New(10, 0, pond.MinWorkers(10))
// Submit 1000 tasks
for i := 0; i < 1000; i++ {
n := i
pool.Submit(func() {
fmt.Printf("Running task #%d\n", n)
})
}
// Stop the pool and wait for all submitted tasks to complete
pool.StopAndWait()
}
```
### Submitting a group of tasks
``` go
package main
import (
"fmt"
"github.com/alitto/pond"
)
func main() {
// Create a pool
pool := pond.New(10, 1000)
defer pool.StopAndWait()
// Create a task group
group := pool.Group()
// Submit a group of tasks
for i := 0; i < 20; i++ {
n := i
group.Submit(func() {
fmt.Printf("Running group task #%d\n", n)
})
}
// Wait for all tasks in the group to complete
group.Wait()
}
```
### Submitting a group of tasks associated to a context (**since v1.8.0**)
This feature provides synchronization, error propagation, and Context cancelation for subtasks of a common task. Similar to `errgroup.Group` from [`golang.org/x/sync/errgroup`](https://pkg.go.dev/golang.org/x/sync/errgroup) package with concurrency bounded by the worker pool.
``` go
package main
import (
"context"
"fmt"
"net/http"
"github.com/alitto/pond"
)
func main() {
// Create a worker pool
pool := pond.New(10, 1000)
defer pool.StopAndWait()
// Create a task group associated to a context
group, ctx := pool.GroupContext(context.Background())
var urls = []string{
"https://www.golang.org/",
"https://www.google.com/",
"https://www.github.com/",
}
// Submit tasks to fetch each URL
for _, url := range urls {
url := url
group.Submit(func() error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
resp, err := http.DefaultClient.Do(req)
if err == nil {
resp.Body.Close()
}
return err
})
}
// Wait for all HTTP requests to complete.
err := group.Wait()
if err != nil {
fmt.Printf("Failed to fetch URLs: %v", err)
} else {
fmt.Println("Successfully fetched all URLs")
}
}
```
### Pool Configuration Options
- **MinWorkers**: Specifies the minimum number of worker goroutines that must be running at any given time. These goroutines are started when the pool is created. The default value is 0. Example:
``` go
// This will create a pool with 5 running worker goroutines
pool := pond.New(10, 1000, pond.MinWorkers(5))
```
- **IdleTimeout**: Defines how long to wait before removing idle worker goroutines from the pool. The default value is 5 seconds. Example:
``` go
// This will create a pool that will remove workers 100ms after they become idle
pool := pond.New(10, 1000, pond.IdleTimeout(100 * time.Millisecond))
```
- **PanicHandler**: Allows to configure a custom function to handle panics thrown by tasks submitted to the pool. The default handler just writes a message to standard output using `fmt.Printf` with the following contents: `Worker exits from a panic: [panic] \n Stack trace: [stack trace]`). Example:
```go
// Custom panic handler function
panicHandler := func(p interface{}) {
fmt.Printf("Task panicked: %v", p)
}
// This will create a pool that will handle panics using a custom panic handler
pool := pond.New(10, 1000, pond.PanicHandler(panicHandler)))
```
- **Strategy**: Configures the strategy used to resize the pool when backpressure is detected. You can create a custom strategy by implementing the `pond.ResizingStrategy` interface or choose one of the 3 presets:
- **Eager**: maximizes responsiveness at the expense of higher resource usage, which can reduce throughput under certain conditions. This strategy is meant for worker pools that will operate at a small percentage of their capacity most of the time and may occasionally receive bursts of tasks. This is the default strategy.
- **Balanced**: tries to find a balance between responsiveness and throughput. It's suitable for general purpose worker pools or those that will operate close to 50% of their capacity most of the time.
- **Lazy**: maximizes throughput at the expense of responsiveness. This strategy is meant for worker pools that will operate close to their max. capacity most of the time.
``` go
// Example: create pools with different resizing strategies
eagerPool := pond.New(10, 1000, pond.Strategy(pond.Eager()))
balancedPool := pond.New(10, 1000, pond.Strategy(pond.Balanced()))
lazyPool := pond.New(10, 1000, pond.Strategy(pond.Lazy()))
```
- **Context**: Configures a parent context on this pool to stop all workers when it is cancelled. The default value `context.Background()`. Example:
``` go
// This creates a pool that is stopped when myCtx is cancelled
pool := pond.New(10, 1000, pond.Context(myCtx))
```
### Resizing strategies
The following chart illustrates the behaviour of the different pool resizing strategies as the number of submitted tasks increases. Each line represents the number of worker goroutines in the pool (pool size) and the x-axis reflects the number of submitted tasks (cumulative).
![Pool resizing strategies behaviour](./docs/strategies.svg)
As the name suggests, the "Eager" strategy always spawns an extra worker when there are no idles, which causes the pool to grow almost linearly with the number of submitted tasks. On the other end, the "Lazy" strategy creates one worker every N submitted tasks, where N is the maximum number of available CPUs ([GOMAXPROCS](https://golang.org/pkg/runtime/#GOMAXPROCS)). The "Balanced" strategy represents a middle ground between the previous two because it creates a worker every N/2 submitted tasks.
### Stopping a pool
There are 3 methods available to stop a pool and release associated resources:
- `pool.Stop()`: stop accepting new tasks and signal all workers to stop processing new tasks. Tasks being processed by workers will continue until completion unless the process is terminated.
- `pool.StopAndWait()`: stop accepting new tasks and wait until all running and queued tasks have completed before returning.
- `pool.StopAndWaitFor(deadline time.Duration)`: similar to `StopAndWait` but with a deadline to prevent waiting indefinitely.
### Metrics & monitoring
Each worker pool instance exposes useful metrics that can be queried through the following methods:
- `pool.RunningWorkers() int`: Current number of running workers
- `pool.IdleWorkers() int`: Current number of idle workers
- `pool.MinWorkers() int`: Minimum number of worker goroutines
- `pool.MaxWorkers() int`: Maxmimum number of worker goroutines
- `pool.MaxCapacity() int`: Maximum number of tasks that can be waiting in the queue at any given time (queue capacity)
- `pool.SubmittedTasks() uint64`: Total number of tasks submitted since the pool was created
- `pool.WaitingTasks() uint64`: Current number of tasks in the queue that are waiting to be executed
- `pool.SuccessfulTasks() uint64`: Total number of tasks that have successfully completed their exection since the pool was created
- `pool.FailedTasks() uint64`: Total number of tasks that completed with panic since the pool was created
- `pool.CompletedTasks() uint64`: Total number of tasks that have completed their exection either successfully or with panic since the pool was created
In our [Prometheus example](./examples/prometheus/prometheus.go) we showcase how to configure collectors for these metrics and expose them to Prometheus.
## Examples
- [Creating a worker pool with dynamic size](./examples/dynamic_size/dynamic_size.go)
- [Creating a worker pool with fixed size](./examples/fixed_size/fixed_size.go)
- [Creating a worker pool with a Context](./examples/pool_context/pool_context.go)
- [Exporting worker pool metrics to Prometheus](./examples/prometheus/prometheus.go)
- [Submitting a group of tasks](./examples/task_group/task_group.go)
- [Submitting a group of tasks associated to a context](./examples/group_context/group_context.go)
## API Reference
Full API reference is available at https://pkg.go.dev/github.com/alitto/pond
## Benchmarks
See [Benchmarks](https://github.com/alitto/pond-benchmarks).
## Resources
Here are some of the resources which have served as inspiration when writing this library:
- http://marcio.io/2015/07/handling-1-million-requests-per-minute-with-golang/
- https://brandur.org/go-worker-pool
- https://gobyexample.com/worker-pools
- https://github.com/panjf2000/ants
- https://github.com/gammazero/workerpool
## Contribution & Support
Feel free to send a pull request if you consider there's something which can be improved. Also, please open up an issue if you run into a problem when using this library or just have a question.

106
vendor/github.com/alitto/pond/group.go generated vendored Normal file
View File

@@ -0,0 +1,106 @@
package pond
import (
"context"
"sync"
)
// TaskGroup represents a group of related tasks
type TaskGroup struct {
pool *WorkerPool
waitGroup sync.WaitGroup
}
// Submit adds a task to this group and sends it to the worker pool to be executed
func (g *TaskGroup) Submit(task func()) {
g.waitGroup.Add(1)
g.pool.Submit(func() {
defer g.waitGroup.Done()
task()
})
}
// Wait waits until all the tasks in this group have completed
func (g *TaskGroup) Wait() {
// Wait for all tasks to complete
g.waitGroup.Wait()
}
// TaskGroupWithContext represents a group of related tasks associated to a context
type TaskGroupWithContext struct {
TaskGroup
ctx context.Context
cancel context.CancelFunc
errSync struct {
once sync.Once
guard sync.RWMutex
}
err error
}
// Submit adds a task to this group and sends it to the worker pool to be executed
func (g *TaskGroupWithContext) Submit(task func() error) {
g.waitGroup.Add(1)
g.pool.Submit(func() {
defer g.waitGroup.Done()
// If context has already been cancelled, skip task execution
select {
case <-g.ctx.Done():
return
default:
}
// don't actually ignore errors
err := task()
if err != nil {
g.setError(err)
}
})
}
// Wait blocks until either all the tasks submitted to this group have completed,
// one of them returned a non-nil error or the context associated to this group
// was canceled.
func (g *TaskGroupWithContext) Wait() error {
// Wait for all tasks to complete
tasksCompleted := make(chan struct{})
go func() {
g.waitGroup.Wait()
tasksCompleted <- struct{}{}
}()
select {
case <-tasksCompleted:
// If context was provided, cancel it to signal all running tasks to stop
g.cancel()
case <-g.ctx.Done():
g.setError(g.ctx.Err())
}
return g.getError()
}
func (g *TaskGroupWithContext) getError() error {
g.errSync.guard.RLock()
err := g.err
g.errSync.guard.RUnlock()
return err
}
func (g *TaskGroupWithContext) setError(err error) {
g.errSync.once.Do(func() {
g.errSync.guard.Lock()
g.err = err
g.errSync.guard.Unlock()
// Cancel execution of any pending task in this group
g.cancel()
})
}

549
vendor/github.com/alitto/pond/pond.go generated vendored Normal file
View File

@@ -0,0 +1,549 @@
package pond
import (
"context"
"errors"
"fmt"
"runtime/debug"
"sync"
"sync/atomic"
"time"
)
const (
// defaultIdleTimeout defines the default idle timeout to use when not explicitly specified
// via the IdleTimeout() option
defaultIdleTimeout = 5 * time.Second
)
var (
// ErrSubmitOnStoppedPool is thrown when attempting to submit a task to a pool that has been stopped
ErrSubmitOnStoppedPool = errors.New("worker pool has been stopped and is no longer accepting tasks")
)
// defaultPanicHandler is the default panic handler
func defaultPanicHandler(panic interface{}) {
fmt.Printf("Worker exits from a panic: %v\nStack trace: %s\n", panic, string(debug.Stack()))
}
// ResizingStrategy represents a pool resizing strategy
type ResizingStrategy interface {
Resize(runningWorkers, minWorkers, maxWorkers int) bool
}
// Option represents an option that can be passed when instantiating a worker pool to customize it
type Option func(*WorkerPool)
// IdleTimeout allows to change the idle timeout for a worker pool
func IdleTimeout(idleTimeout time.Duration) Option {
return func(pool *WorkerPool) {
pool.idleTimeout = idleTimeout
}
}
// MinWorkers allows to change the minimum number of workers of a worker pool
func MinWorkers(minWorkers int) Option {
return func(pool *WorkerPool) {
pool.minWorkers = minWorkers
}
}
// Strategy allows to change the strategy used to resize the pool
func Strategy(strategy ResizingStrategy) Option {
return func(pool *WorkerPool) {
pool.strategy = strategy
}
}
// PanicHandler allows to change the panic handler function of a worker pool
func PanicHandler(panicHandler func(interface{})) Option {
return func(pool *WorkerPool) {
pool.panicHandler = panicHandler
}
}
// Context configures a parent context on a worker pool to stop all workers when it is cancelled
func Context(parentCtx context.Context) Option {
return func(pool *WorkerPool) {
pool.context, pool.contextCancel = context.WithCancel(parentCtx)
}
}
// WorkerPool models a pool of workers
type WorkerPool struct {
// Atomic counters, should be placed first so alignment is guaranteed
// for atomic operations.
workerCount int32
idleWorkerCount int32
waitingTaskCount uint64
submittedTaskCount uint64
successfulTaskCount uint64
failedTaskCount uint64
// Configurable settings
maxWorkers int
maxCapacity int
minWorkers int
idleTimeout time.Duration
strategy ResizingStrategy
panicHandler func(interface{})
context context.Context
contextCancel context.CancelFunc
// Private properties
tasks chan func()
tasksCloseOnce sync.Once
workersWaitGroup sync.WaitGroup
tasksWaitGroup sync.WaitGroup
mutex sync.Mutex
stopped int32
}
// New creates a worker pool with that can scale up to the given maximum number of workers (maxWorkers).
// The maxCapacity parameter determines the number of tasks that can be submitted to this pool without blocking,
// because it defines the size of the buffered channel used to receive tasks.
// The options parameter can take a list of functions to customize configuration values on this worker pool.
func New(maxWorkers, maxCapacity int, options ...Option) *WorkerPool {
// Instantiate the pool
pool := &WorkerPool{
maxWorkers: maxWorkers,
maxCapacity: maxCapacity,
idleTimeout: defaultIdleTimeout,
strategy: Eager(),
panicHandler: defaultPanicHandler,
}
// Apply all options
for _, opt := range options {
opt(pool)
}
// Make sure options are consistent
if pool.maxWorkers <= 0 {
pool.maxWorkers = 1
}
if pool.minWorkers > pool.maxWorkers {
pool.minWorkers = pool.maxWorkers
}
if pool.maxCapacity < 0 {
pool.maxCapacity = 0
}
if pool.idleTimeout < 0 {
pool.idleTimeout = defaultIdleTimeout
}
// Initialize base context (if not already set)
if pool.context == nil {
Context(context.Background())(pool)
}
// Create tasks channel
pool.tasks = make(chan func(), pool.maxCapacity)
// Start purger goroutine
pool.workersWaitGroup.Add(1)
go pool.purge()
// Start minWorkers workers
if pool.minWorkers > 0 {
for i := 0; i < pool.minWorkers; i++ {
pool.maybeStartWorker(nil)
}
}
return pool
}
// RunningWorkers returns the current number of running workers
func (p *WorkerPool) RunningWorkers() int {
return int(atomic.LoadInt32(&p.workerCount))
}
// IdleWorkers returns the current number of idle workers
func (p *WorkerPool) IdleWorkers() int {
return int(atomic.LoadInt32(&p.idleWorkerCount))
}
// MinWorkers returns the minimum number of worker goroutines
func (p *WorkerPool) MinWorkers() int {
return p.minWorkers
}
// MaxWorkers returns the maximum number of worker goroutines
func (p *WorkerPool) MaxWorkers() int {
return p.maxWorkers
}
// MaxCapacity returns the maximum number of tasks that can be waiting in the queue
// at any given time (queue size)
func (p *WorkerPool) MaxCapacity() int {
return p.maxCapacity
}
// Strategy returns the configured pool resizing strategy
func (p *WorkerPool) Strategy() ResizingStrategy {
return p.strategy
}
// SubmittedTasks returns the total number of tasks submitted since the pool was created
func (p *WorkerPool) SubmittedTasks() uint64 {
return atomic.LoadUint64(&p.submittedTaskCount)
}
// WaitingTasks returns the current number of tasks in the queue that are waiting to be executed
func (p *WorkerPool) WaitingTasks() uint64 {
return atomic.LoadUint64(&p.waitingTaskCount)
}
// SuccessfulTasks returns the total number of tasks that have successfully completed their exection
// since the pool was created
func (p *WorkerPool) SuccessfulTasks() uint64 {
return atomic.LoadUint64(&p.successfulTaskCount)
}
// FailedTasks returns the total number of tasks that completed with panic since the pool was created
func (p *WorkerPool) FailedTasks() uint64 {
return atomic.LoadUint64(&p.failedTaskCount)
}
// CompletedTasks returns the total number of tasks that have completed their exection either successfully
// or with panic since the pool was created
func (p *WorkerPool) CompletedTasks() uint64 {
return p.SuccessfulTasks() + p.FailedTasks()
}
// Stopped returns true if the pool has been stopped and is no longer accepting tasks, and false otherwise.
func (p *WorkerPool) Stopped() bool {
return atomic.LoadInt32(&p.stopped) == 1
}
// Submit sends a task to this worker pool for execution. If the queue is full,
// it will wait until the task is dispatched to a worker goroutine.
func (p *WorkerPool) Submit(task func()) {
p.submit(task, true)
}
// TrySubmit attempts to send a task to this worker pool for execution. If the queue is full,
// it will not wait for a worker to become idle. It returns true if it was able to dispatch
// the task and false otherwise.
func (p *WorkerPool) TrySubmit(task func()) bool {
return p.submit(task, false)
}
func (p *WorkerPool) submit(task func(), mustSubmit bool) (submitted bool) {
if task == nil {
return
}
if p.Stopped() {
// Pool is stopped and caller must submit the task
if mustSubmit {
panic(ErrSubmitOnStoppedPool)
}
return
}
// Increment submitted and waiting task counters as soon as we receive a task
atomic.AddUint64(&p.submittedTaskCount, 1)
atomic.AddUint64(&p.waitingTaskCount, 1)
p.tasksWaitGroup.Add(1)
defer func() {
if !submitted {
// Task was not sumitted to the pool, decrement submitted and waiting task counters
atomic.AddUint64(&p.submittedTaskCount, ^uint64(0))
atomic.AddUint64(&p.waitingTaskCount, ^uint64(0))
p.tasksWaitGroup.Done()
}
}()
// Start a worker as long as we haven't reached the limit
if submitted = p.maybeStartWorker(task); submitted {
return
}
if !mustSubmit {
// Attempt to dispatch to an idle worker without blocking
select {
case p.tasks <- task:
submitted = true
return
default:
// Channel is full and can't wait for an idle worker, so need to exit
return
}
}
// Submit the task to the tasks channel and wait for it to be picked up by a worker
p.tasks <- task
submitted = true
return
}
// SubmitAndWait sends a task to this worker pool for execution and waits for it to complete
// before returning
func (p *WorkerPool) SubmitAndWait(task func()) {
if task == nil {
return
}
done := make(chan struct{})
p.Submit(func() {
defer close(done)
task()
})
<-done
}
// SubmitBefore attempts to send a task for execution to this worker pool but aborts it
// if the task did not start before the given deadline.
func (p *WorkerPool) SubmitBefore(task func(), deadline time.Duration) {
if task == nil {
return
}
timer := time.NewTimer(deadline)
p.Submit(func() {
select {
case <-timer.C:
// Deadline was reached, abort the task
default:
// Deadline not reached, execute the task
defer timer.Stop()
task()
}
})
}
// Stop causes this pool to stop accepting new tasks and signals all workers to exit.
// Tasks being executed by workers will continue until completion (unless the process is terminated).
// Tasks in the queue will not be executed.
func (p *WorkerPool) Stop() {
go p.stop(false)
}
// StopAndWait causes this pool to stop accepting new tasks and then waits for all tasks in the queue
// to complete before returning.
func (p *WorkerPool) StopAndWait() {
p.stop(true)
}
// StopAndWaitFor stops this pool and waits until either all tasks in the queue are completed
// or the given deadline is reached, whichever comes first.
func (p *WorkerPool) StopAndWaitFor(deadline time.Duration) {
// Launch goroutine to detect when worker pool has stopped gracefully
workersDone := make(chan struct{})
go func() {
p.stop(true)
workersDone <- struct{}{}
}()
// Wait until either all workers have exited or the deadline is reached
select {
case <-workersDone:
return
case <-time.After(deadline):
p.contextCancel()
return
}
}
func (p *WorkerPool) stop(waitForQueuedTasksToComplete bool) {
// Mark pool as stopped
atomic.StoreInt32(&p.stopped, 1)
if waitForQueuedTasksToComplete {
// Wait for all queued tasks to complete
p.tasksWaitGroup.Wait()
}
// Reset worker count
p.resetWorkerCount()
// Terminate all workers & purger goroutine
p.contextCancel()
// Wait for all workers & purger goroutine to exit
p.workersWaitGroup.Wait()
// close tasks channel (only once, in case multiple concurrent calls to StopAndWait are made)
p.tasksCloseOnce.Do(func() {
close(p.tasks)
})
}
// purge represents the work done by the purger goroutine
func (p *WorkerPool) purge() {
defer p.workersWaitGroup.Done()
idleTicker := time.NewTicker(p.idleTimeout)
defer idleTicker.Stop()
for {
select {
// Timed out waiting for any activity to happen, attempt to stop an idle worker
case <-idleTicker.C:
p.maybeStopIdleWorker()
// Pool context was cancelled, exit
case <-p.context.Done():
return
}
}
}
// maybeStopIdleWorker attempts to stop an idle worker by sending it a nil task
func (p *WorkerPool) maybeStopIdleWorker() bool {
if decremented := p.decrementWorkerCount(); !decremented {
return false
}
// Send a nil task to stop an idle worker
p.tasks <- nil
return true
}
// maybeStartWorker attempts to create a new worker goroutine to run the given task.
// If the worker pool has reached the maximum number of workers or there are idle workers,
// it will not create a new one.
func (p *WorkerPool) maybeStartWorker(firstTask func()) bool {
if incremented := p.incrementWorkerCount(); !incremented {
return false
}
if firstTask == nil {
// Worker starts idle
atomic.AddInt32(&p.idleWorkerCount, 1)
}
// Launch worker goroutine
go worker(p.context, &p.workersWaitGroup, firstTask, p.tasks, p.executeTask)
return true
}
// executeTask executes the given task and updates task-related counters
func (p *WorkerPool) executeTask(task func(), isFirstTask bool) {
defer func() {
if panic := recover(); panic != nil {
// Increment failed task count
atomic.AddUint64(&p.failedTaskCount, 1)
// Invoke panic handler
p.panicHandler(panic)
// Increment idle count
atomic.AddInt32(&p.idleWorkerCount, 1)
}
p.tasksWaitGroup.Done()
}()
// Decrement idle count
if !isFirstTask {
atomic.AddInt32(&p.idleWorkerCount, -1)
}
// Decrement waiting task count
atomic.AddUint64(&p.waitingTaskCount, ^uint64(0))
// Execute task
task()
// Increment successful task count
atomic.AddUint64(&p.successfulTaskCount, 1)
// Increment idle count
atomic.AddInt32(&p.idleWorkerCount, 1)
}
func (p *WorkerPool) incrementWorkerCount() bool {
p.mutex.Lock()
defer p.mutex.Unlock()
runningWorkerCount := p.RunningWorkers()
// Reached max workers, do not create a new one
if runningWorkerCount >= p.maxWorkers {
return false
}
// Idle workers available, do not create a new one
if runningWorkerCount >= p.minWorkers && runningWorkerCount > 0 && p.IdleWorkers() > 0 {
return false
}
// Execute the resizing strategy to determine if we should create more workers
if resize := p.strategy.Resize(runningWorkerCount, p.minWorkers, p.maxWorkers); !resize {
return false
}
// Increment worker count
atomic.AddInt32(&p.workerCount, 1)
// Increment wait group
p.workersWaitGroup.Add(1)
return true
}
func (p *WorkerPool) decrementWorkerCount() bool {
p.mutex.Lock()
defer p.mutex.Unlock()
if p.IdleWorkers() <= 0 || p.RunningWorkers() <= p.minWorkers || p.Stopped() {
return false
}
// Decrement worker count
atomic.AddInt32(&p.workerCount, -1)
// Decrement idle count
atomic.AddInt32(&p.idleWorkerCount, -1)
return true
}
func (p *WorkerPool) resetWorkerCount() {
p.mutex.Lock()
defer p.mutex.Unlock()
// Reset worker count
atomic.StoreInt32(&p.workerCount, 0)
// Reset idle count
atomic.StoreInt32(&p.idleWorkerCount, 0)
}
// Group creates a new task group
func (p *WorkerPool) Group() *TaskGroup {
return &TaskGroup{
pool: p,
}
}
// GroupContext creates a new task group and an associated Context derived from ctx.
//
// The derived Context is canceled the first time a function submitted to the group
// returns a non-nil error or the first time Wait returns, whichever occurs first.
func (p *WorkerPool) GroupContext(ctx context.Context) (*TaskGroupWithContext, context.Context) {
if ctx == nil {
panic("a non-nil context needs to be specified when using GroupContext")
}
ctx, cancel := context.WithCancel(ctx)
return &TaskGroupWithContext{
TaskGroup: TaskGroup{
pool: p,
},
ctx: ctx,
cancel: cancel,
}, ctx
}

55
vendor/github.com/alitto/pond/resizer.go generated vendored Normal file
View File

@@ -0,0 +1,55 @@
package pond
import (
"runtime"
)
var maxProcs = runtime.GOMAXPROCS(0)
// Preset pool resizing strategies
var (
// Eager maximizes responsiveness at the expense of higher resource usage,
// which can reduce throughput under certain conditions.
// This strategy is meant for worker pools that will operate at a small percentage of their capacity
// most of the time and may occasionally receive bursts of tasks. It's the default strategy.
Eager = func() ResizingStrategy { return RatedResizer(1) }
// Balanced tries to find a balance between responsiveness and throughput.
// It's suitable for general purpose worker pools or those
// that will operate close to 50% of their capacity most of the time.
Balanced = func() ResizingStrategy { return RatedResizer(maxProcs / 2) }
// Lazy maximizes throughput at the expense of responsiveness.
// This strategy is meant for worker pools that will operate close to their max. capacity most of the time.
Lazy = func() ResizingStrategy { return RatedResizer(maxProcs) }
)
// ratedResizer implements a rated resizing strategy
type ratedResizer struct {
rate uint64
hits uint64
}
// RatedResizer creates a resizing strategy which can be configured
// to create workers at a specific rate when the pool has no idle workers.
// rate: determines the number of tasks to receive before creating an extra worker.
// A value of 3 can be interpreted as: "Create a new worker every 3 tasks".
func RatedResizer(rate int) ResizingStrategy {
if rate < 1 {
rate = 1
}
return &ratedResizer{
rate: uint64(rate),
}
}
func (r *ratedResizer) Resize(runningWorkers, minWorkers, maxWorkers int) bool {
if r.rate == 1 || runningWorkers == 0 {
return true
}
r.hits++
return r.hits%r.rate == 1
}

35
vendor/github.com/alitto/pond/worker.go generated vendored Normal file
View File

@@ -0,0 +1,35 @@
package pond
import (
"context"
"sync"
)
// worker represents a worker goroutine
func worker(context context.Context, waitGroup *sync.WaitGroup, firstTask func(), tasks <-chan func(), taskExecutor func(func(), bool)) {
// If provided, execute the first task immediately, before listening to the tasks channel
if firstTask != nil {
taskExecutor(firstTask, true)
}
defer func() {
waitGroup.Done()
}()
for {
select {
case <-context.Done():
// Pool context was cancelled, exit
return
case task, ok := <-tasks:
if task == nil || !ok {
// We have received a signal to exit
return
}
// We have received a task, execute it
taskExecutor(task, false)
}
}
}

27
vendor/github.com/kardianos/osext/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
vendor/github.com/kardianos/osext/README.md generated vendored Normal file
View File

@@ -0,0 +1,21 @@
### Extensions to the "os" package.
[![GoDoc](https://godoc.org/github.com/kardianos/osext?status.svg)](https://godoc.org/github.com/kardianos/osext)
## Find the current Executable and ExecutableFolder.
As of go1.8 the Executable function may be found in `os`. The Executable function
in the std lib `os` package is used if available.
There is sometimes utility in finding the current executable file
that is running. This can be used for upgrading the current executable
or finding resources located relative to the executable file. Both
working directory and the os.Args[0] value are arbitrary and cannot
be relied on; os.Args[0] can be "faked".
Multi-platform and supports:
* Linux
* OS X
* Windows
* Plan 9
* BSDs.

33
vendor/github.com/kardianos/osext/osext.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Extensions to the standard "os" package.
package osext // import "github.com/kardianos/osext"
import "path/filepath"
var cx, ce = executableClean()
func executableClean() (string, error) {
p, err := executable()
return filepath.Clean(p), err
}
// Executable returns an absolute path that can be used to
// re-invoke the current program.
// It may not be valid after the current program exits.
func Executable() (string, error) {
return cx, ce
}
// Returns same path as Executable, returns just the folder
// path. Excludes the executable name and any trailing slash.
func ExecutableFolder() (string, error) {
p, err := Executable()
if err != nil {
return "", err
}
return filepath.Dir(p), nil
}

9
vendor/github.com/kardianos/osext/osext_go18.go generated vendored Normal file
View File

@@ -0,0 +1,9 @@
//+build go1.8,!openbsd
package osext
import "os"
func executable() (string, error) {
return os.Executable()
}

22
vendor/github.com/kardianos/osext/osext_plan9.go generated vendored Normal file
View File

@@ -0,0 +1,22 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//+build !go1.8
package osext
import (
"os"
"strconv"
"syscall"
)
func executable() (string, error) {
f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text")
if err != nil {
return "", err
}
defer f.Close()
return syscall.Fd2path(int(f.Fd()))
}

36
vendor/github.com/kardianos/osext/osext_procfs.go generated vendored Normal file
View File

@@ -0,0 +1,36 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8,android !go1.8,linux !go1.8,netbsd !go1.8,solaris !go1.8,dragonfly
package osext
import (
"errors"
"fmt"
"os"
"runtime"
"strings"
)
func executable() (string, error) {
switch runtime.GOOS {
case "linux", "android":
const deletedTag = " (deleted)"
execpath, err := os.Readlink("/proc/self/exe")
if err != nil {
return execpath, err
}
execpath = strings.TrimSuffix(execpath, deletedTag)
execpath = strings.TrimPrefix(execpath, deletedTag)
return execpath, nil
case "netbsd":
return os.Readlink("/proc/curproc/exe")
case "dragonfly":
return os.Readlink("/proc/curproc/file")
case "solaris":
return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid()))
}
return "", errors.New("ExecPath not implemented for " + runtime.GOOS)
}

126
vendor/github.com/kardianos/osext/osext_sysctl.go generated vendored Normal file
View File

@@ -0,0 +1,126 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8,darwin !go1.8,freebsd openbsd
package osext
import (
"os"
"os/exec"
"path/filepath"
"runtime"
"syscall"
"unsafe"
)
var initCwd, initCwdErr = os.Getwd()
func executable() (string, error) {
var mib [4]int32
switch runtime.GOOS {
case "freebsd":
mib = [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1}
case "darwin":
mib = [4]int32{1 /* CTL_KERN */, 38 /* KERN_PROCARGS */, int32(os.Getpid()), -1}
case "openbsd":
mib = [4]int32{1 /* CTL_KERN */, 55 /* KERN_PROC_ARGS */, int32(os.Getpid()), 1 /* KERN_PROC_ARGV */}
}
n := uintptr(0)
// Get length.
_, _, errNum := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
if errNum != 0 {
return "", errNum
}
if n == 0 { // This shouldn't happen.
return "", nil
}
buf := make([]byte, n)
_, _, errNum = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
if errNum != 0 {
return "", errNum
}
if n == 0 { // This shouldn't happen.
return "", nil
}
var execPath string
switch runtime.GOOS {
case "openbsd":
// buf now contains **argv, with pointers to each of the C-style
// NULL terminated arguments.
var args []string
argv := uintptr(unsafe.Pointer(&buf[0]))
Loop:
for {
argp := *(**[1 << 20]byte)(unsafe.Pointer(argv))
if argp == nil {
break
}
for i := 0; uintptr(i) < n; i++ {
// we don't want the full arguments list
if string(argp[i]) == " " {
break Loop
}
if argp[i] != 0 {
continue
}
args = append(args, string(argp[:i]))
n -= uintptr(i)
break
}
if n < unsafe.Sizeof(argv) {
break
}
argv += unsafe.Sizeof(argv)
n -= unsafe.Sizeof(argv)
}
execPath = args[0]
// There is no canonical way to get an executable path on
// OpenBSD, so check PATH in case we are called directly
if execPath[0] != '/' && execPath[0] != '.' {
execIsInPath, err := exec.LookPath(execPath)
if err == nil {
execPath = execIsInPath
}
}
default:
for i, v := range buf {
if v == 0 {
buf = buf[:i]
break
}
}
execPath = string(buf)
}
var err error
// execPath will not be empty due to above checks.
// Try to get the absolute path if the execPath is not rooted.
if execPath[0] != '/' {
execPath, err = getAbs(execPath)
if err != nil {
return execPath, err
}
}
// For darwin KERN_PROCARGS may return the path to a symlink rather than the
// actual executable.
if runtime.GOOS == "darwin" {
if execPath, err = filepath.EvalSymlinks(execPath); err != nil {
return execPath, err
}
}
return execPath, nil
}
func getAbs(execPath string) (string, error) {
if initCwdErr != nil {
return execPath, initCwdErr
}
// The execPath may begin with a "../" or a "./" so clean it first.
// Join the two paths, trailing and starting slashes undetermined, so use
// the generic Join function.
return filepath.Join(initCwd, filepath.Clean(execPath)), nil
}

36
vendor/github.com/kardianos/osext/osext_windows.go generated vendored Normal file
View File

@@ -0,0 +1,36 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//+build !go1.8
package osext
import (
"syscall"
"unicode/utf16"
"unsafe"
)
var (
kernel = syscall.MustLoadDLL("kernel32.dll")
getModuleFileNameProc = kernel.MustFindProc("GetModuleFileNameW")
)
// GetModuleFileName() with hModule = NULL
func executable() (exePath string, err error) {
return getModuleFileName()
}
func getModuleFileName() (string, error) {
var n uint32
b := make([]uint16, syscall.MAX_PATH)
size := uint32(len(b))
r0, _, e1 := getModuleFileNameProc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size))
n = uint32(r0)
if n == 0 {
return "", e1
}
return string(utf16.Decode(b[0:n])), nil
}

15
vendor/github.com/sevlyar/go-daemon/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,15 @@
language: go
go:
- 1.3
- 1.5
- tip
before_install:
- go get -t -v ./...
script:
- go test -v -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)

7
vendor/github.com/sevlyar/go-daemon/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,7 @@
Copyright (C) 2013 Sergey Yarmonov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

70
vendor/github.com/sevlyar/go-daemon/README.md generated vendored Normal file
View File

@@ -0,0 +1,70 @@
# go-daemon [![Build Status](https://travis-ci.org/sevlyar/go-daemon.svg?branch=master)](https://travis-ci.org/sevlyar/go-daemon) [![GoDoc](https://godoc.org/github.com/sevlyar/go-daemon?status.svg)](https://godoc.org/github.com/sevlyar/go-daemon)
Library for writing system daemons in Go.
Now supported only UNIX-based OS (Windows is not supported). But the library was tested only on Linux
and OSX, so that if you have an ability to test the library on other platforms, give me feedback, please (#26).
*Please, feel free to send me bug reports and fixes. Many thanks to all contributors.*
## Features
* Goroutine-safe daemonization;
* Out of box work with pid-files;
* Easy handling of system signals;
* The control of a daemon.
## Installation
go get github.com/sevlyar/go-daemon
You can use [gopkg.in](http://labix.org/gopkg.in):
go get gopkg.in/sevlyar/go-daemon.v0
If you want to use the library in production project, please use vendoring,
because i can not ensure backward compatibility before release v1.0.
## Examples
* [Simple](examples/cmd/gd-simple/)
* [Log rotation](examples/cmd/gd-log-rotation/)
* [Signal handling](examples/cmd/gd-signal-handling/)
## Documentation
[godoc.org/github.com/sevlyar/go-daemon](https://godoc.org/github.com/sevlyar/go-daemon)
## How it works
We can not use `fork` syscall in Golang's runtime, because child process doesn't inherit
threads and goroutines in that case. The library uses a simple trick: it runs its own copy with
a mark - a predefined environment variable. Availability of the variable for the process means
an execution in the child's copy. So that if the mark is not setted - the library executes
parent's operations and runs its own copy with mark, and if the mark is setted - the library
executes child's operations:
```go
import "log"
func main() {
Pre()
context := new(Context)
child, _ := context.Reborn()
if child != nil {
PostParent()
} else {
defer func() {
if err := context.Release(); err != nil {
log.Printf("Unable to release pid-file: %s", err.Error())
}
}()
PostChild()
}
}
```
![](img/idea.png)

99
vendor/github.com/sevlyar/go-daemon/command.go generated vendored Normal file
View File

@@ -0,0 +1,99 @@
package daemon
import (
"os"
)
// AddCommand is wrapper on AddFlag and SetSigHandler functions.
func AddCommand(f Flag, sig os.Signal, handler SignalHandlerFunc) {
if f != nil {
AddFlag(f, sig)
}
if handler != nil {
SetSigHandler(handler, sig)
}
}
// Flag is the interface implemented by an object that has two state:
// 'set' and 'unset'.
type Flag interface {
IsSet() bool
}
// BoolFlag returns new object that implements interface Flag and
// has state 'set' when var with the given address is true.
func BoolFlag(f *bool) Flag {
return &boolFlag{f}
}
// StringFlag returns new object that implements interface Flag and
// has state 'set' when var with the given address equals given value of v.
func StringFlag(f *string, v string) Flag {
return &stringFlag{f, v}
}
type boolFlag struct {
b *bool
}
func (f *boolFlag) IsSet() bool {
if f == nil {
return false
}
return *f.b
}
type stringFlag struct {
s *string
v string
}
func (f *stringFlag) IsSet() bool {
if f == nil {
return false
}
return *f.s == f.v
}
var flags = make(map[Flag]os.Signal)
// Flags returns flags that was added by the function AddFlag.
func Flags() map[Flag]os.Signal {
return flags
}
// AddFlag adds the flag and signal to the internal map.
func AddFlag(f Flag, sig os.Signal) {
flags[f] = sig
}
// SendCommands sends active signals to the given process.
func SendCommands(p *os.Process) (err error) {
for _, sig := range signals() {
if err = p.Signal(sig); err != nil {
return
}
}
return
}
// ActiveFlags returns flags that has the state 'set'.
func ActiveFlags() (ret []Flag) {
ret = make([]Flag, 0, 1)
for f := range flags {
if f.IsSet() {
ret = append(ret, f)
}
}
return
}
func signals() (ret []os.Signal) {
ret = make([]os.Signal, 0, 1)
for f, sig := range flags {
if f.IsSet() {
ret = append(ret, sig)
}
}
return
}

44
vendor/github.com/sevlyar/go-daemon/daemon.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
package daemon
import (
"errors"
"os"
)
var errNotSupported = errors.New("daemon: Non-POSIX OS is not supported")
// Mark of daemon process - system environment variable _GO_DAEMON=1
const (
MARK_NAME = "_GO_DAEMON"
MARK_VALUE = "1"
)
// Default file permissions for log and pid files.
const FILE_PERM = os.FileMode(0640)
// WasReborn returns true in child process (daemon) and false in parent process.
func WasReborn() bool {
return os.Getenv(MARK_NAME) == MARK_VALUE
}
// Reborn runs second copy of current process in the given context.
// function executes separate parts of code in child process and parent process
// and provides demonization of child process. It look similar as the
// fork-daemonization, but goroutine-safe.
// In success returns *os.Process in parent process and nil in child process.
// Otherwise returns error.
func (d *Context) Reborn() (child *os.Process, err error) {
return d.reborn()
}
// Search searches daemons process by given in context pid file name.
// If success returns pointer on daemons os.Process structure,
// else returns error. Returns nil if filename is empty.
func (d *Context) Search() (daemon *os.Process, err error) {
return d.search()
}
// Release provides correct pid-file release in daemon.
func (d *Context) Release() error {
return d.release()
}

53
vendor/github.com/sevlyar/go-daemon/daemon_stub.go generated vendored Normal file
View File

@@ -0,0 +1,53 @@
//go:build !darwin && !dragonfly && !freebsd && !linux &&!netbsd && !openbsd && !plan9 && !solaris
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!plan9,!solaris
package daemon
import (
"os"
)
// A Context describes daemon context.
type Context struct {
// If PidFileName is non-empty, parent process will try to create and lock
// pid file with given name. Child process writes process id to file.
PidFileName string
// Permissions for new pid file.
PidFilePerm os.FileMode
// If LogFileName is non-empty, parent process will create file with given name
// and will link to fd 2 (stderr) for child process.
LogFileName string
// Permissions for new log file.
LogFilePerm os.FileMode
// If WorkDir is non-empty, the child changes into the directory before
// creating the process.
WorkDir string
// If Chroot is non-empty, the child changes root directory
Chroot string
// If Env is non-nil, it gives the environment variables for the
// daemon-process in the form returned by os.Environ.
// If it is nil, the result of os.Environ will be used.
Env []string
// If Args is non-nil, it gives the command-line args for the
// daemon-process. If it is nil, the result of os.Args will be used
// (without program name).
Args []string
// If Umask is non-zero, the daemon-process call Umask() func with given value.
Umask int
}
func (d *Context) reborn() (child *os.Process, err error) {
return nil, errNotSupported
}
func (d *Context) search() (daemon *os.Process, err error) {
return nil, errNotSupported
}
func (d *Context) release() (err error) {
return errNotSupported
}

270
vendor/github.com/sevlyar/go-daemon/daemon_unix.go generated vendored Normal file
View File

@@ -0,0 +1,270 @@
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || plan9 || solaris
// +build darwin dragonfly freebsd linux netbsd openbsd plan9 solaris
package daemon
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"syscall"
)
// A Context describes daemon context.
type Context struct {
// If PidFileName is non-empty, parent process will try to create and lock
// pid file with given name. Child process writes process id to file.
PidFileName string
// Permissions for new pid file.
PidFilePerm os.FileMode
// If LogFileName is non-empty, parent process will create file with given name
// and will link to fd 2 (stderr) for child process.
LogFileName string
// Permissions for new log file.
LogFilePerm os.FileMode
// If WorkDir is non-empty, the child changes into the directory before
// creating the process.
WorkDir string
// If Chroot is non-empty, the child changes root directory
Chroot string
// If Env is non-nil, it gives the environment variables for the
// daemon-process in the form returned by os.Environ.
// If it is nil, the result of os.Environ will be used.
Env []string
// If Args is non-nil, it gives the command-line args for the
// daemon-process. If it is nil, the result of os.Args will be used.
Args []string
// Credential holds user and group identities to be assumed by a daemon-process.
Credential *syscall.Credential
// If Umask is non-zero, the daemon-process call Umask() func with given value.
Umask int
// Struct contains only serializable public fields (!!!)
abspath string
pidFile *LockFile
logFile *os.File
nullFile *os.File
rpipe, wpipe *os.File
}
func (d *Context) reborn() (child *os.Process, err error) {
if !WasReborn() {
child, err = d.parent()
} else {
err = d.child()
}
return
}
func (d *Context) search() (daemon *os.Process, err error) {
if len(d.PidFileName) > 0 {
var pid int
if pid, err = ReadPidFile(d.PidFileName); err != nil {
return
}
daemon, err = os.FindProcess(pid)
if err == nil && daemon != nil {
// Send a test signal to test if this daemon is actually alive or dead
// An error means it is dead
if daemon.Signal(syscall.Signal(0)) != nil {
daemon = nil
}
}
}
return
}
func (d *Context) parent() (child *os.Process, err error) {
if err = d.prepareEnv(); err != nil {
return
}
defer d.closeFiles()
if err = d.openFiles(); err != nil {
return
}
attr := &os.ProcAttr{
Dir: d.WorkDir,
Env: d.Env,
Files: d.files(),
Sys: &syscall.SysProcAttr{
//Chroot: d.Chroot,
Credential: d.Credential,
Setsid: true,
},
}
if child, err = os.StartProcess(d.abspath, d.Args, attr); err != nil {
if d.pidFile != nil {
d.pidFile.Remove()
}
return
}
d.rpipe.Close()
encoder := json.NewEncoder(d.wpipe)
if err = encoder.Encode(d); err != nil {
return
}
_, err = fmt.Fprint(d.wpipe, "\n\n")
return
}
func (d *Context) openFiles() (err error) {
if d.PidFilePerm == 0 {
d.PidFilePerm = FILE_PERM
}
if d.LogFilePerm == 0 {
d.LogFilePerm = FILE_PERM
}
if d.nullFile, err = os.Open(os.DevNull); err != nil {
return
}
if len(d.PidFileName) > 0 {
if d.PidFileName, err = filepath.Abs(d.PidFileName); err != nil {
return err
}
if d.pidFile, err = OpenLockFile(d.PidFileName, d.PidFilePerm); err != nil {
return
}
if err = d.pidFile.Lock(); err != nil {
return
}
if len(d.Chroot) > 0 {
// Calculate PID-file absolute path in child's environment
if d.PidFileName, err = filepath.Rel(d.Chroot, d.PidFileName); err != nil {
return err
}
d.PidFileName = "/" + d.PidFileName
}
}
if len(d.LogFileName) > 0 {
if d.LogFileName == "/dev/stdout" {
d.logFile = os.Stdout
} else if d.LogFileName == "/dev/stderr" {
d.logFile = os.Stderr
} else if d.logFile, err = os.OpenFile(d.LogFileName,
os.O_WRONLY|os.O_CREATE|os.O_APPEND, d.LogFilePerm); err != nil {
return
}
}
d.rpipe, d.wpipe, err = os.Pipe()
return
}
func (d *Context) closeFiles() (err error) {
cl := func(file **os.File) {
if *file != nil {
(*file).Close()
*file = nil
}
}
cl(&d.rpipe)
cl(&d.wpipe)
cl(&d.logFile)
cl(&d.nullFile)
if d.pidFile != nil {
d.pidFile.Close()
d.pidFile = nil
}
return
}
func (d *Context) prepareEnv() (err error) {
if d.abspath, err = osExecutable(); err != nil {
return
}
if len(d.Args) == 0 {
d.Args = os.Args
}
mark := fmt.Sprintf("%s=%s", MARK_NAME, MARK_VALUE)
if len(d.Env) == 0 {
d.Env = os.Environ()
}
d.Env = append(d.Env, mark)
return
}
func (d *Context) files() (f []*os.File) {
log := d.nullFile
if d.logFile != nil {
log = d.logFile
}
f = []*os.File{
d.rpipe, // (0) stdin
log, // (1) stdout
log, // (2) stderr
d.nullFile, // (3) dup on fd 0 after initialization
}
if d.pidFile != nil {
f = append(f, d.pidFile.File) // (4) pid file
}
return
}
var initialized = false
func (d *Context) child() (err error) {
if initialized {
return os.ErrInvalid
}
initialized = true
decoder := json.NewDecoder(os.Stdin)
if err = decoder.Decode(d); err != nil {
return
}
// create PID file after context decoding to know PID file full path.
if len(d.PidFileName) > 0 {
d.pidFile = NewLockFile(os.NewFile(4, d.PidFileName))
if err = d.pidFile.WritePid(); err != nil {
return
}
defer func() {
if err != nil {
d.pidFile.Remove()
}
}()
}
if err = syscallDup(3, 0); err != nil {
return
}
if d.Umask != 0 {
syscall.Umask(int(d.Umask))
}
if len(d.Chroot) > 0 {
err = syscall.Chroot(d.Chroot)
if err != nil {
return
}
}
return
}
func (d *Context) release() error {
if !initialized || d.pidFile == nil {
return nil
}
return d.pidFile.Remove()
}

109
vendor/github.com/sevlyar/go-daemon/lock_file.go generated vendored Normal file
View File

@@ -0,0 +1,109 @@
package daemon
import (
"errors"
"fmt"
"os"
)
var (
// ErrWouldBlock indicates on locking pid-file by another process.
ErrWouldBlock = errors.New("daemon: Resource temporarily unavailable")
)
// LockFile wraps *os.File and provide functions for locking of files.
type LockFile struct {
*os.File
}
// NewLockFile returns a new LockFile with the given File.
func NewLockFile(file *os.File) *LockFile {
return &LockFile{file}
}
// CreatePidFile opens the named file, applies exclusive lock and writes
// current process id to file.
func CreatePidFile(name string, perm os.FileMode) (lock *LockFile, err error) {
if lock, err = OpenLockFile(name, perm); err != nil {
return
}
if err = lock.Lock(); err != nil {
lock.Remove()
return
}
if err = lock.WritePid(); err != nil {
lock.Remove()
}
return
}
// OpenLockFile opens the named file with flags os.O_RDWR|os.O_CREATE and specified perm.
// If successful, function returns LockFile for opened file.
func OpenLockFile(name string, perm os.FileMode) (lock *LockFile, err error) {
var file *os.File
if file, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE, perm); err == nil {
lock = &LockFile{file}
}
return
}
// Lock apply exclusive lock on an open file. If file already locked, returns error.
func (file *LockFile) Lock() error {
return lockFile(file.Fd())
}
// Unlock remove exclusive lock on an open file.
func (file *LockFile) Unlock() error {
return unlockFile(file.Fd())
}
// ReadPidFile reads process id from file with give name and returns pid.
// If unable read from a file, returns error.
func ReadPidFile(name string) (pid int, err error) {
var file *os.File
if file, err = os.OpenFile(name, os.O_RDONLY, 0640); err != nil {
return
}
defer file.Close()
lock := &LockFile{file}
pid, err = lock.ReadPid()
return
}
// WritePid writes current process id to an open file.
func (file *LockFile) WritePid() (err error) {
if _, err = file.Seek(0, os.SEEK_SET); err != nil {
return
}
var fileLen int
if fileLen, err = fmt.Fprint(file, os.Getpid()); err != nil {
return
}
if err = file.Truncate(int64(fileLen)); err != nil {
return
}
err = file.Sync()
return
}
// ReadPid reads process id from file and returns pid.
// If unable read from a file, returns error.
func (file *LockFile) ReadPid() (pid int, err error) {
if _, err = file.Seek(0, os.SEEK_SET); err != nil {
return
}
_, err = fmt.Fscan(file, &pid)
return
}
// Remove removes lock, closes and removes an open file.
func (file *LockFile) Remove() error {
defer file.Close()
if err := file.Unlock(); err != nil {
return err
}
return os.Remove(file.Name())
}

View File

@@ -0,0 +1,41 @@
//go:build solaris
// +build solaris
package daemon
import (
"io"
"syscall"
)
func lockFile(fd uintptr) error {
lockInfo := syscall.Flock_t{
Type: syscall.F_WRLCK,
Whence: io.SeekStart,
Start: 0,
Len: 0,
}
if err := syscall.FcntlFlock(fd, syscall.F_SETLK, &lockInfo); err != nil {
if err == syscall.EAGAIN {
err = ErrWouldBlock
}
return err
}
return nil
}
func unlockFile(fd uintptr) error {
lockInfo := syscall.Flock_t{
Type: syscall.F_UNLCK,
Whence: io.SeekStart,
Start: 0,
Len: 0,
}
if err := syscall.FcntlFlock(fd, syscall.F_GETLK, &lockInfo); err != nil {
if err == syscall.EAGAIN {
err = ErrWouldBlock
}
return err
}
return nil
}

12
vendor/github.com/sevlyar/go-daemon/lock_file_stub.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !plan9 && !solaris
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!plan9,!solaris
package daemon
func lockFile(fd uintptr) error {
return errNotSupported
}
func unlockFile(fd uintptr) error {
return errNotSupported
}

24
vendor/github.com/sevlyar/go-daemon/lock_file_unix.go generated vendored Normal file
View File

@@ -0,0 +1,24 @@
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || plan9
// +build darwin dragonfly freebsd linux netbsd openbsd plan9
package daemon
import (
"syscall"
)
func lockFile(fd uintptr) error {
err := syscall.Flock(int(fd), syscall.LOCK_EX|syscall.LOCK_NB)
if err == syscall.EWOULDBLOCK {
err = ErrWouldBlock
}
return err
}
func unlockFile(fd uintptr) error {
err := syscall.Flock(int(fd), syscall.LOCK_UN)
if err == syscall.EWOULDBLOCK {
err = ErrWouldBlock
}
return err
}

12
vendor/github.com/sevlyar/go-daemon/os_executable.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
//go:build go1.8
// +build go1.8
package daemon
import (
"os"
)
func osExecutable() (string, error) {
return os.Executable()
}

View File

@@ -0,0 +1,12 @@
//go:build !go1.8
// +build !go1.8
package daemon
import (
"github.com/kardianos/osext"
)
func osExecutable() (string, error) {
return osext.Executable()
}

59
vendor/github.com/sevlyar/go-daemon/signal.go generated vendored Normal file
View File

@@ -0,0 +1,59 @@
package daemon
import (
"errors"
"os"
"os/signal"
"syscall"
)
// ErrStop should be returned signal handler function
// for termination of handling signals.
var ErrStop = errors.New("stop serve signals")
// SignalHandlerFunc is the interface for signal handler functions.
type SignalHandlerFunc func(sig os.Signal) (err error)
// SetSigHandler sets handler for the given signals.
// SIGTERM has the default handler, he returns ErrStop.
func SetSigHandler(handler SignalHandlerFunc, signals ...os.Signal) {
for _, sig := range signals {
handlers[sig] = handler
}
}
// ServeSignals calls handlers for system signals.
func ServeSignals() (err error) {
signals := make([]os.Signal, 0, len(handlers))
for sig := range handlers {
signals = append(signals, sig)
}
ch := make(chan os.Signal, 8)
signal.Notify(ch, signals...)
for sig := range ch {
err = handlers[sig](sig)
if err != nil {
break
}
}
signal.Stop(ch)
if err == ErrStop {
err = nil
}
return
}
var handlers = make(map[os.Signal]SignalHandlerFunc)
func init() {
handlers[syscall.SIGTERM] = sigtermDefaultHandler
}
func sigtermDefaultHandler(sig os.Signal) error {
return ErrStop
}

13
vendor/github.com/sevlyar/go-daemon/syscall_dup.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
//go:build (!linux || !arm64) && (!linux || !riscv64) && !windows && go1.7
// +build !linux !arm64
// +build !linux !riscv64
// +build !windows
// +build go1.7
package daemon
import "golang.org/x/sys/unix"
func syscallDup(oldfd int, newfd int) (err error) {
return unix.Dup2(oldfd, newfd)
}

13
vendor/github.com/sevlyar/go-daemon/syscall_dup3.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
//go:build linux && (riscv64 || arm64)
// +build linux
// +build riscv64 arm64
package daemon
import "syscall"
func syscallDup(oldfd int, newfd int) (err error) {
// linux_arm64 platform doesn't have syscall.Dup2
// so use the nearly identical syscall.Dup3 instead.
return syscall.Dup3(oldfd, newfd, 0)
}

View File

@@ -0,0 +1,14 @@
//go:build (!linux || !arm64) && !windows && !go1.7
// +build !linux !arm64
// +build !windows
// +build !go1.7
package daemon
import (
"syscall"
)
func syscallDup(oldfd int, newfd int) (err error) {
return syscall.Dup2(oldfd, newfd)
}

View File

@@ -1,8 +1,7 @@
//go:build freebsd && arm64
// +build freebsd,arm64
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs process/types_freebsd.go
// cgo -godefs types_freebsd.go
package process
@@ -82,14 +81,14 @@ type Rlimit struct {
type KinfoProc struct {
Structsize int32
Layout int32
Args *int64 /* pargs */
Paddr *int64 /* proc */
Addr *int64 /* user */
Tracep *int64 /* vnode */
Textvp *int64 /* vnode */
Fd *int64 /* filedesc */
Vmspace *int64 /* vmspace */
Wchan *byte
Args int64 /* pargs */
Paddr int64 /* proc */
Addr int64 /* user */
Tracep int64 /* vnode */
Textvp int64 /* vnode */
Fd int64 /* filedesc */
Vmspace int64 /* vmspace */
Wchan int64
Pid int32
Ppid int32
Pgid int32
@@ -140,7 +139,7 @@ type KinfoProc struct {
Wmesg [9]uint8
Login [18]uint8
Lockname [9]uint8
Comm [20]int8
Comm [20]int8 // changed from uint8 by hand
Emul [17]uint8
Loginclass [18]uint8
Moretdname [4]uint8
@@ -159,11 +158,12 @@ type KinfoProc struct {
Pri Priority
Rusage Rusage
Rusage_ch Rusage
Pcb *int64 /* pcb */
Kstack *byte
Udata *byte
Tdaddr *int64 /* thread */
Spareptrs [6]*byte
Pcb int64 /* pcb */
Kstack int64
Udata int64
Tdaddr int64 /* thread */
Pd int64 /* pwddesc, not accurate */
Spareptrs [5]int64
Sparelongs [12]int64
Sflag int64
Tdflags int64
@@ -195,7 +195,7 @@ type KinfoVmentry struct {
Vn_rdev_freebsd11 uint32
Vn_mode uint16
Status uint16
Vn_fsid uint64
Type_spec [8]byte
Vn_rdev uint64
X_kve_ispare [8]int32
Path [1024]uint8

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build aix || darwin || dragonfly || freebsd || openbsd || solaris
//go:build aix || darwin || dragonfly || freebsd || openbsd || solaris || zos
package unix

View File

@@ -1520,6 +1520,14 @@ func (m *mmapper) Munmap(data []byte) (err error) {
return nil
}
func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) {
return mapper.Mmap(fd, offset, length, prot, flags)
}
func Munmap(b []byte) (err error) {
return mapper.Munmap(b)
}
func Read(fd int, p []byte) (n int, err error) {
n, err = read(fd, p)
if raceenabled {

View File

@@ -165,6 +165,7 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys CreateFile(name *uint16, access uint32, mode uint32, sa *SecurityAttributes, createmode uint32, attrs uint32, templatefile Handle) (handle Handle, err error) [failretval==InvalidHandle] = CreateFileW
//sys CreateNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *SecurityAttributes) (handle Handle, err error) [failretval==InvalidHandle] = CreateNamedPipeW
//sys ConnectNamedPipe(pipe Handle, overlapped *Overlapped) (err error)
//sys DisconnectNamedPipe(pipe Handle) (err error)
//sys GetNamedPipeInfo(pipe Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error)
//sys GetNamedPipeHandleState(pipe Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
//sys SetNamedPipeHandleState(pipe Handle, state *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32) (err error) = SetNamedPipeHandleState
@@ -348,8 +349,19 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys SetProcessPriorityBoost(process Handle, disable bool) (err error) = kernel32.SetProcessPriorityBoost
//sys GetProcessWorkingSetSizeEx(hProcess Handle, lpMinimumWorkingSetSize *uintptr, lpMaximumWorkingSetSize *uintptr, flags *uint32)
//sys SetProcessWorkingSetSizeEx(hProcess Handle, dwMinimumWorkingSetSize uintptr, dwMaximumWorkingSetSize uintptr, flags uint32) (err error)
//sys ClearCommBreak(handle Handle) (err error)
//sys ClearCommError(handle Handle, lpErrors *uint32, lpStat *ComStat) (err error)
//sys EscapeCommFunction(handle Handle, dwFunc uint32) (err error)
//sys GetCommState(handle Handle, lpDCB *DCB) (err error)
//sys GetCommModemStatus(handle Handle, lpModemStat *uint32) (err error)
//sys GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error)
//sys PurgeComm(handle Handle, dwFlags uint32) (err error)
//sys SetCommBreak(handle Handle) (err error)
//sys SetCommMask(handle Handle, dwEvtMask uint32) (err error)
//sys SetCommState(handle Handle, lpDCB *DCB) (err error)
//sys SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error)
//sys SetupComm(handle Handle, dwInQueue uint32, dwOutQueue uint32) (err error)
//sys WaitCommEvent(handle Handle, lpEvtMask *uint32, lpOverlapped *Overlapped) (err error)
//sys GetActiveProcessorCount(groupNumber uint16) (ret uint32)
//sys GetMaximumProcessorCount(groupNumber uint16) (ret uint32)
//sys EnumWindows(enumFunc uintptr, param unsafe.Pointer) (err error) = user32.EnumWindows
@@ -1834,3 +1846,73 @@ func ResizePseudoConsole(pconsole Handle, size Coord) error {
// accept arguments that can be casted to uintptr, and Coord can't.
return resizePseudoConsole(pconsole, *((*uint32)(unsafe.Pointer(&size))))
}
// DCB constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-dcb.
const (
CBR_110 = 110
CBR_300 = 300
CBR_600 = 600
CBR_1200 = 1200
CBR_2400 = 2400
CBR_4800 = 4800
CBR_9600 = 9600
CBR_14400 = 14400
CBR_19200 = 19200
CBR_38400 = 38400
CBR_57600 = 57600
CBR_115200 = 115200
CBR_128000 = 128000
CBR_256000 = 256000
DTR_CONTROL_DISABLE = 0x00000000
DTR_CONTROL_ENABLE = 0x00000010
DTR_CONTROL_HANDSHAKE = 0x00000020
RTS_CONTROL_DISABLE = 0x00000000
RTS_CONTROL_ENABLE = 0x00001000
RTS_CONTROL_HANDSHAKE = 0x00002000
RTS_CONTROL_TOGGLE = 0x00003000
NOPARITY = 0
ODDPARITY = 1
EVENPARITY = 2
MARKPARITY = 3
SPACEPARITY = 4
ONESTOPBIT = 0
ONE5STOPBITS = 1
TWOSTOPBITS = 2
)
// EscapeCommFunction constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-escapecommfunction.
const (
SETXOFF = 1
SETXON = 2
SETRTS = 3
CLRRTS = 4
SETDTR = 5
CLRDTR = 6
SETBREAK = 8
CLRBREAK = 9
)
// PurgeComm constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-purgecomm.
const (
PURGE_TXABORT = 0x0001
PURGE_RXABORT = 0x0002
PURGE_TXCLEAR = 0x0004
PURGE_RXCLEAR = 0x0008
)
// SetCommMask constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setcommmask.
const (
EV_RXCHAR = 0x0001
EV_RXFLAG = 0x0002
EV_TXEMPTY = 0x0004
EV_CTS = 0x0008
EV_DSR = 0x0010
EV_RLSD = 0x0020
EV_BREAK = 0x0040
EV_ERR = 0x0080
EV_RING = 0x0100
)

View File

@@ -3380,3 +3380,27 @@ type BLOB struct {
Size uint32
BlobData *byte
}
type ComStat struct {
Flags uint32
CBInQue uint32
CBOutQue uint32
}
type DCB struct {
DCBlength uint32
BaudRate uint32
Flags uint32
wReserved uint16
XonLim uint16
XoffLim uint16
ByteSize uint8
Parity uint8
StopBits uint8
XonChar byte
XoffChar byte
ErrorChar byte
EofChar byte
EvtChar byte
wReserved1 uint16
}

View File

@@ -188,6 +188,8 @@ var (
procAssignProcessToJobObject = modkernel32.NewProc("AssignProcessToJobObject")
procCancelIo = modkernel32.NewProc("CancelIo")
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
procClearCommBreak = modkernel32.NewProc("ClearCommBreak")
procClearCommError = modkernel32.NewProc("ClearCommError")
procCloseHandle = modkernel32.NewProc("CloseHandle")
procClosePseudoConsole = modkernel32.NewProc("ClosePseudoConsole")
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
@@ -212,7 +214,9 @@ var (
procDeleteProcThreadAttributeList = modkernel32.NewProc("DeleteProcThreadAttributeList")
procDeleteVolumeMountPointW = modkernel32.NewProc("DeleteVolumeMountPointW")
procDeviceIoControl = modkernel32.NewProc("DeviceIoControl")
procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe")
procDuplicateHandle = modkernel32.NewProc("DuplicateHandle")
procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction")
procExitProcess = modkernel32.NewProc("ExitProcess")
procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW")
procFindClose = modkernel32.NewProc("FindClose")
@@ -236,6 +240,8 @@ var (
procGenerateConsoleCtrlEvent = modkernel32.NewProc("GenerateConsoleCtrlEvent")
procGetACP = modkernel32.NewProc("GetACP")
procGetActiveProcessorCount = modkernel32.NewProc("GetActiveProcessorCount")
procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus")
procGetCommState = modkernel32.NewProc("GetCommState")
procGetCommTimeouts = modkernel32.NewProc("GetCommTimeouts")
procGetCommandLineW = modkernel32.NewProc("GetCommandLineW")
procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW")
@@ -322,6 +328,7 @@ var (
procProcess32NextW = modkernel32.NewProc("Process32NextW")
procProcessIdToSessionId = modkernel32.NewProc("ProcessIdToSessionId")
procPulseEvent = modkernel32.NewProc("PulseEvent")
procPurgeComm = modkernel32.NewProc("PurgeComm")
procQueryDosDeviceW = modkernel32.NewProc("QueryDosDeviceW")
procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW")
procQueryInformationJobObject = modkernel32.NewProc("QueryInformationJobObject")
@@ -335,6 +342,9 @@ var (
procResetEvent = modkernel32.NewProc("ResetEvent")
procResizePseudoConsole = modkernel32.NewProc("ResizePseudoConsole")
procResumeThread = modkernel32.NewProc("ResumeThread")
procSetCommBreak = modkernel32.NewProc("SetCommBreak")
procSetCommMask = modkernel32.NewProc("SetCommMask")
procSetCommState = modkernel32.NewProc("SetCommState")
procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts")
procSetConsoleCursorPosition = modkernel32.NewProc("SetConsoleCursorPosition")
procSetConsoleMode = modkernel32.NewProc("SetConsoleMode")
@@ -342,7 +352,6 @@ var (
procSetDefaultDllDirectories = modkernel32.NewProc("SetDefaultDllDirectories")
procSetDllDirectoryW = modkernel32.NewProc("SetDllDirectoryW")
procSetEndOfFile = modkernel32.NewProc("SetEndOfFile")
procSetFileValidData = modkernel32.NewProc("SetFileValidData")
procSetEnvironmentVariableW = modkernel32.NewProc("SetEnvironmentVariableW")
procSetErrorMode = modkernel32.NewProc("SetErrorMode")
procSetEvent = modkernel32.NewProc("SetEvent")
@@ -351,6 +360,7 @@ var (
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
procSetFilePointer = modkernel32.NewProc("SetFilePointer")
procSetFileTime = modkernel32.NewProc("SetFileTime")
procSetFileValidData = modkernel32.NewProc("SetFileValidData")
procSetHandleInformation = modkernel32.NewProc("SetHandleInformation")
procSetInformationJobObject = modkernel32.NewProc("SetInformationJobObject")
procSetNamedPipeHandleState = modkernel32.NewProc("SetNamedPipeHandleState")
@@ -361,6 +371,7 @@ var (
procSetStdHandle = modkernel32.NewProc("SetStdHandle")
procSetVolumeLabelW = modkernel32.NewProc("SetVolumeLabelW")
procSetVolumeMountPointW = modkernel32.NewProc("SetVolumeMountPointW")
procSetupComm = modkernel32.NewProc("SetupComm")
procSizeofResource = modkernel32.NewProc("SizeofResource")
procSleepEx = modkernel32.NewProc("SleepEx")
procTerminateJobObject = modkernel32.NewProc("TerminateJobObject")
@@ -379,6 +390,7 @@ var (
procVirtualQueryEx = modkernel32.NewProc("VirtualQueryEx")
procVirtualUnlock = modkernel32.NewProc("VirtualUnlock")
procWTSGetActiveConsoleSessionId = modkernel32.NewProc("WTSGetActiveConsoleSessionId")
procWaitCommEvent = modkernel32.NewProc("WaitCommEvent")
procWaitForMultipleObjects = modkernel32.NewProc("WaitForMultipleObjects")
procWaitForSingleObject = modkernel32.NewProc("WaitForSingleObject")
procWriteConsoleW = modkernel32.NewProc("WriteConsoleW")
@@ -1641,6 +1653,22 @@ func CancelIoEx(s Handle, o *Overlapped) (err error) {
return
}
func ClearCommBreak(handle Handle) (err error) {
r1, _, e1 := syscall.Syscall(procClearCommBreak.Addr(), 1, uintptr(handle), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func ClearCommError(handle Handle, lpErrors *uint32, lpStat *ComStat) (err error) {
r1, _, e1 := syscall.Syscall(procClearCommError.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(lpErrors)), uintptr(unsafe.Pointer(lpStat)))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func CloseHandle(handle Handle) (err error) {
r1, _, e1 := syscall.Syscall(procCloseHandle.Addr(), 1, uintptr(handle), 0, 0)
if r1 == 0 {
@@ -1845,6 +1873,14 @@ func DeviceIoControl(handle Handle, ioControlCode uint32, inBuffer *byte, inBuff
return
}
func DisconnectNamedPipe(pipe Handle) (err error) {
r1, _, e1 := syscall.Syscall(procDisconnectNamedPipe.Addr(), 1, uintptr(pipe), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetProcessHandle Handle, lpTargetHandle *Handle, dwDesiredAccess uint32, bInheritHandle bool, dwOptions uint32) (err error) {
var _p0 uint32
if bInheritHandle {
@@ -1857,6 +1893,14 @@ func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetP
return
}
func EscapeCommFunction(handle Handle, dwFunc uint32) (err error) {
r1, _, e1 := syscall.Syscall(procEscapeCommFunction.Addr(), 2, uintptr(handle), uintptr(dwFunc), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func ExitProcess(exitcode uint32) {
syscall.Syscall(procExitProcess.Addr(), 1, uintptr(exitcode), 0, 0)
return
@@ -2058,6 +2102,22 @@ func GetActiveProcessorCount(groupNumber uint16) (ret uint32) {
return
}
func GetCommModemStatus(handle Handle, lpModemStat *uint32) (err error) {
r1, _, e1 := syscall.Syscall(procGetCommModemStatus.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpModemStat)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func GetCommState(handle Handle, lpDCB *DCB) (err error) {
r1, _, e1 := syscall.Syscall(procGetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpDCB)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) {
r1, _, e1 := syscall.Syscall(procGetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0)
if r1 == 0 {
@@ -2810,6 +2870,14 @@ func PulseEvent(event Handle) (err error) {
return
}
func PurgeComm(handle Handle, dwFlags uint32) (err error) {
r1, _, e1 := syscall.Syscall(procPurgeComm.Addr(), 2, uintptr(handle), uintptr(dwFlags), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func QueryDosDevice(deviceName *uint16, targetPath *uint16, max uint32) (n uint32, err error) {
r0, _, e1 := syscall.Syscall(procQueryDosDeviceW.Addr(), 3, uintptr(unsafe.Pointer(deviceName)), uintptr(unsafe.Pointer(targetPath)), uintptr(max))
n = uint32(r0)
@@ -2924,6 +2992,30 @@ func ResumeThread(thread Handle) (ret uint32, err error) {
return
}
func SetCommBreak(handle Handle) (err error) {
r1, _, e1 := syscall.Syscall(procSetCommBreak.Addr(), 1, uintptr(handle), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SetCommMask(handle Handle, dwEvtMask uint32) (err error) {
r1, _, e1 := syscall.Syscall(procSetCommMask.Addr(), 2, uintptr(handle), uintptr(dwEvtMask), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SetCommState(handle Handle, lpDCB *DCB) (err error) {
r1, _, e1 := syscall.Syscall(procSetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpDCB)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) {
r1, _, e1 := syscall.Syscall(procSetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0)
if r1 == 0 {
@@ -2989,14 +3081,6 @@ func SetEndOfFile(handle Handle) (err error) {
return
}
func SetFileValidData(handle Handle, validDataLength int64) (err error) {
r1, _, e1 := syscall.Syscall(procSetFileValidData.Addr(), 2, uintptr(handle), uintptr(validDataLength), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SetEnvironmentVariable(name *uint16, value *uint16) (err error) {
r1, _, e1 := syscall.Syscall(procSetEnvironmentVariableW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), 0)
if r1 == 0 {
@@ -3060,6 +3144,14 @@ func SetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetim
return
}
func SetFileValidData(handle Handle, validDataLength int64) (err error) {
r1, _, e1 := syscall.Syscall(procSetFileValidData.Addr(), 2, uintptr(handle), uintptr(validDataLength), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SetHandleInformation(handle Handle, mask uint32, flags uint32) (err error) {
r1, _, e1 := syscall.Syscall(procSetHandleInformation.Addr(), 3, uintptr(handle), uintptr(mask), uintptr(flags))
if r1 == 0 {
@@ -3145,6 +3237,14 @@ func SetVolumeMountPoint(volumeMountPoint *uint16, volumeName *uint16) (err erro
return
}
func SetupComm(handle Handle, dwInQueue uint32, dwOutQueue uint32) (err error) {
r1, _, e1 := syscall.Syscall(procSetupComm.Addr(), 3, uintptr(handle), uintptr(dwInQueue), uintptr(dwOutQueue))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SizeofResource(module Handle, resInfo Handle) (size uint32, err error) {
r0, _, e1 := syscall.Syscall(procSizeofResource.Addr(), 2, uintptr(module), uintptr(resInfo), 0)
size = uint32(r0)
@@ -3291,6 +3391,14 @@ func WTSGetActiveConsoleSessionId() (sessionID uint32) {
return
}
func WaitCommEvent(handle Handle, lpEvtMask *uint32, lpOverlapped *Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procWaitCommEvent.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(lpEvtMask)), uintptr(unsafe.Pointer(lpOverlapped)))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func waitForMultipleObjects(count uint32, handles uintptr, waitAll bool, waitMilliseconds uint32) (event uint32, err error) {
var _p0 uint32
if waitAll {

13
vendor/modules.txt vendored
View File

@@ -1,14 +1,23 @@
# github.com/alitto/pond v1.8.3
## explicit; go 1.19
github.com/alitto/pond
# github.com/go-ole/go-ole v1.3.0
## explicit; go 1.12
github.com/go-ole/go-ole
github.com/go-ole/go-ole/oleutil
# github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
## explicit
github.com/kardianos/osext
# github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a
## explicit; go 1.16
github.com/lufia/plan9stats
# github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55
## explicit; go 1.14
github.com/power-devops/perfstat
# github.com/shirou/gopsutil/v3 v3.24.2
# github.com/sevlyar/go-daemon v0.1.6
## explicit
github.com/sevlyar/go-daemon
# github.com/shirou/gopsutil/v3 v3.24.3
## explicit; go 1.15
github.com/shirou/gopsutil/v3/common
github.com/shirou/gopsutil/v3/cpu
@@ -28,7 +37,7 @@ github.com/tklauser/numcpus
# github.com/yusufpapurcu/wmi v1.2.4
## explicit; go 1.16
github.com/yusufpapurcu/wmi
# golang.org/x/sys v0.18.0
# golang.org/x/sys v0.19.0
## explicit; go 1.18
golang.org/x/sys/unix
golang.org/x/sys/windows