|
1 | | -package mkill |
| 1 | +// Copyright 2020 The golang.design Initiative authors. |
| 2 | +// All rights reserved. Use of this source code is governed |
| 3 | +// by a MIT license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +// Package mkill limits the number of threads in a Go program, without crash the whole program. |
| 6 | +package mkill // import "golang.design/x/mkill" |
2 | 7 |
|
3 | 8 | import ( |
| 9 | + "context" |
4 | 10 | "fmt" |
5 | 11 | "os" |
6 | 12 | "os/exec" |
7 | 13 | "runtime" |
8 | 14 | "strconv" |
9 | 15 | "strings" |
| 16 | + "sync" |
10 | 17 | "sync/atomic" |
11 | 18 | "time" |
12 | 19 | ) |
13 | 20 |
|
14 | 21 | var ( |
15 | 22 | pid = os.Getpid() |
16 | | - maxThread = int32(runtime.NumCPU()) |
| 23 | + maxThread = int32(runtime.NumCPU()) + 2 // 2 meaning runtime sysmon thread + template thread |
17 | 24 | interval = time.Second |
18 | 25 | debug = false |
19 | 26 | ) |
20 | 27 |
|
| 28 | +// NumM returns the number of running threads. |
| 29 | +func NumM() int { |
| 30 | + out, err := exec.Command("bash", "-c", cmdThreads).Output() |
| 31 | + if err != nil && debug { |
| 32 | + fmt.Printf("mkill: failed to fetch #threads: %v\n", err) |
| 33 | + return 0 |
| 34 | + } |
| 35 | + n, err := strconv.Atoi(strings.TrimSpace(string(out))) |
| 36 | + if err != nil && debug { |
| 37 | + fmt.Printf("mkill: failed to parse #threads: %v\n", err) |
| 38 | + return 0 |
| 39 | + } |
| 40 | + return n |
| 41 | +} |
| 42 | + |
| 43 | +// GOMAXTHREADS sets the maximum number of system threads that allowed in a Go program |
| 44 | +// and returns the previous setting. If n < 1, it does not change the current setting. |
| 45 | +// The default allowed number of threads of a program is runtime.NumCPU() + 2. |
| 46 | +func GOMAXTHREADS(n int) int { |
| 47 | + if n < 1 { |
| 48 | + return int(atomic.LoadInt32(&maxThread)) |
| 49 | + } |
| 50 | + |
| 51 | + return int(atomic.SwapInt32(&maxThread, int32(n))) |
| 52 | +} |
| 53 | + |
| 54 | +// Wait waits until the number of threads meet the GOMAXTHREADS settings. |
| 55 | +// The function always returns true if the ctx is not canceled. |
| 56 | +// Otherwise returns true only if the Wait is successed in the last check. |
| 57 | +func Wait(ctx context.Context) (ok bool) { |
| 58 | + for { |
| 59 | + select { |
| 60 | + case <-ctx.Done(): |
| 61 | + if NumM() <= GOMAXTHREADS(0) { |
| 62 | + ok = true |
| 63 | + } |
| 64 | + return |
| 65 | + default: |
| 66 | + if NumM() > GOMAXTHREADS(0) { |
| 67 | + continue |
| 68 | + } |
| 69 | + ok = true |
| 70 | + return |
| 71 | + } |
| 72 | + } |
| 73 | +} |
| 74 | + |
21 | 75 | func checkwork() { |
22 | | - _, err := getThreads() |
| 76 | + _, err := exec.Command("bash", "-c", cmdThreads).Output() |
23 | 77 | if err != nil { |
24 | | - panic(fmt.Sprintf("mkill: failed to use the library: %v", err)) |
| 78 | + panic(fmt.Sprintf("mkill: failed to use the package: %v", err)) |
25 | 79 | } |
26 | 80 | } |
27 | 81 |
|
28 | 82 | func init() { |
29 | 83 | checkwork() |
30 | | - |
31 | 84 | if debug { |
32 | 85 | fmt.Printf("mkill: pid %v, maxThread %v, interval %v\n", pid, maxThread, interval) |
33 | 86 | } |
| 87 | + |
| 88 | + wg := sync.WaitGroup{} |
34 | 89 | go func() { |
35 | 90 | t := time.NewTicker(interval) |
36 | 91 | for { |
37 | 92 | select { |
38 | 93 | case <-t.C: |
39 | | - n, _ := getThreads() |
| 94 | + n := NumM() |
40 | 95 | nkill := int32(n) - atomic.LoadInt32(&maxThread) |
41 | 96 | if nkill <= 0 { |
42 | 97 | if debug { |
43 | 98 | fmt.Printf("mkill: checked #threads total %v / max %v\n", n, maxThread) |
44 | 99 | } |
45 | 100 | continue |
46 | 101 | } |
| 102 | + wg.Add(int(nkill)) |
47 | 103 | for i := int32(0); i < nkill; i++ { |
48 | 104 | go func() { |
49 | 105 | runtime.LockOSThread() |
| 106 | + wg.Done() |
50 | 107 | }() |
51 | 108 | } |
| 109 | + wg.Wait() |
52 | 110 | if debug { |
53 | 111 | fmt.Printf("mkill: killing #threads, remaining: %v\n", n) |
54 | 112 | } |
55 | 113 | } |
56 | 114 | } |
57 | 115 | }() |
58 | 116 | } |
59 | | - |
60 | | -// GOMAXTHREADS change the limits of the maximum threads in runtime |
61 | | -// and returns the previous number of threads limit |
62 | | -func GOMAXTHREADS(n int) int { |
63 | | - return int(atomic.SwapInt32(&maxThread, int32(n))) |
64 | | -} |
65 | | - |
66 | | -// getThreads returns the number of running threads |
67 | | -// Linux: |
68 | | -func getThreads() (int, error) { |
69 | | - out, err := exec.Command("bash", "-c", cmdThreads).Output() |
70 | | - if err != nil { |
71 | | - return 0, fmt.Errorf("mkill: failed to fetch #threads: %v", err) |
72 | | - } |
73 | | - n, err := strconv.Atoi(strings.TrimSpace(string(out))) |
74 | | - if err != nil { |
75 | | - return 0, fmt.Errorf("mkill: failed to parse #threads: %v", err) |
76 | | - } |
77 | | - return n, nil |
78 | | -} |
|
0 commit comments