Skip to content

Commit 151d837

Browse files
committed
all: rework entire package
1 parent 9d8234c commit 151d837

File tree

9 files changed

+177
-49
lines changed

9 files changed

+177
-49
lines changed

.github/workflows/mkill.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: mkill
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
jobs:
10+
11+
build:
12+
name: Build
13+
runs-on: ubuntu-latest
14+
steps:
15+
16+
- name: Set up Go 1.x
17+
uses: actions/setup-go@v2
18+
with:
19+
go-version: ^1.13
20+
id: go
21+
22+
- name: Check out code into the Go module directory
23+
uses: actions/checkout@v2
24+
25+
- name: Get dependencies
26+
run: |
27+
go get -v -t -d ./...
28+
29+
- name: Test
30+
run: |
31+
go test -v -coverprofile=coverage.txt -covermode=atomic ./...
32+
33+
- name: Upload coverage profile
34+
uses: codecov/codecov-action@v1
35+
with:
36+
token: ${{secrets.CODECOV_TOKEN}}
37+
file: coverage.txt

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2019 Ou Changkun
3+
Copyright (c) 2020 The golang.design Authors
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
# mkill
22

3-
mkill limits the number of threads in a Go program
3+
[![PkgGoDev](https://pkg.go.dev/badge/golang.design/x/mkill)](https://pkg.go.dev/golang.design/x/mkill) [![Go Report Card](https://goreportcard.com/badge/golang.design/x/mkill)](https://goreportcard.com/report/golang.design/x/mkill)
4+
![mkill](https://github.com/golang-design/mkill/workflows/mkill/badge.svg?branch=master)
45

5-
## Usage
6+
Package mkill limits the number of threads in a Go program, without crash the whole program.
67

78
```
8-
mkill.GOMAXTHREADS(50)
9+
import "golang.design/x/mkill"
10+
```
11+
12+
## Quick Start
13+
14+
```
15+
mkill.GOMAXTHREADS(10)
916
```
1017

1118
## License
1219

13-
MIT © Changkun Ou
20+
MIT © The [golang.design](https://golang.design) Authors

example/main.go

Lines changed: 0 additions & 17 deletions
This file was deleted.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
module github.com/changkun/mkill
1+
module golang.design/x/mkill
22

33
go 1.13

mkill.go

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,116 @@
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"
27

38
import (
9+
"context"
410
"fmt"
511
"os"
612
"os/exec"
713
"runtime"
814
"strconv"
915
"strings"
16+
"sync"
1017
"sync/atomic"
1118
"time"
1219
)
1320

1421
var (
1522
pid = os.Getpid()
16-
maxThread = int32(runtime.NumCPU())
23+
maxThread = int32(runtime.NumCPU()) + 2 // 2 meaning runtime sysmon thread + template thread
1724
interval = time.Second
1825
debug = false
1926
)
2027

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+
2175
func checkwork() {
22-
_, err := getThreads()
76+
_, err := exec.Command("bash", "-c", cmdThreads).Output()
2377
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))
2579
}
2680
}
2781

2882
func init() {
2983
checkwork()
30-
3184
if debug {
3285
fmt.Printf("mkill: pid %v, maxThread %v, interval %v\n", pid, maxThread, interval)
3386
}
87+
88+
wg := sync.WaitGroup{}
3489
go func() {
3590
t := time.NewTicker(interval)
3691
for {
3792
select {
3893
case <-t.C:
39-
n, _ := getThreads()
94+
n := NumM()
4095
nkill := int32(n) - atomic.LoadInt32(&maxThread)
4196
if nkill <= 0 {
4297
if debug {
4398
fmt.Printf("mkill: checked #threads total %v / max %v\n", n, maxThread)
4499
}
45100
continue
46101
}
102+
wg.Add(int(nkill))
47103
for i := int32(0); i < nkill; i++ {
48104
go func() {
49105
runtime.LockOSThread()
106+
wg.Done()
50107
}()
51108
}
109+
wg.Wait()
52110
if debug {
53111
fmt.Printf("mkill: killing #threads, remaining: %v\n", n)
54112
}
55113
}
56114
}
57115
}()
58116
}
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-
}

mkill_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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_test
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"testing"
11+
"time"
12+
13+
"golang.design/x/mkill"
14+
)
15+
16+
func TestMKill(t *testing.T) {
17+
mkill.GOMAXTHREADS(10)
18+
19+
// create a lot of threads by sleep gs
20+
for i := 0; i < 100000; i++ {
21+
go func() {
22+
time.Sleep(time.Second * 10)
23+
}()
24+
}
25+
26+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*100)
27+
defer cancel()
28+
ok := mkill.Wait(ctx)
29+
if !ok {
30+
t.Fatal("mkill failed in 100s")
31+
}
32+
}
33+
34+
func ExampleGOMAXTHREADS() {
35+
mkill.GOMAXTHREADS(10)
36+
// Output:
37+
}
38+
39+
func ExampleNumM() {
40+
mkill.GOMAXTHREADS(10)
41+
mkill.Wait(context.Background())
42+
fmt.Println(mkill.NumM() <= 10)
43+
// Output:
44+
// true
45+
}
46+
47+
func ExampleWait() {
48+
mkill.GOMAXTHREADS(10)
49+
50+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*100)
51+
defer cancel()
52+
fmt.Println(mkill.Wait(ctx))
53+
// Output:
54+
// true
55+
}

platform_darwin.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
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+
15
// +build darwin
26

37
package mkill

platform_linux.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
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+
15
// +build linux
26

37
package mkill

0 commit comments

Comments
 (0)