Skip to content

Commit ab18dd3

Browse files
committed
improves exec child process handling
1 parent 89349d6 commit ab18dd3

File tree

4 files changed

+49
-26
lines changed

4 files changed

+49
-26
lines changed

pkg/agent/spy/spy.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var autoDetectionMapping = map[string]string{
2525
"ruby": "rbspy",
2626
"bundle": "rbspy",
2727
"rails": "rbspy",
28+
"rake": "rbspy",
2829
}
2930

3031
func init() {
@@ -46,3 +47,14 @@ func SpyFromName(name string, pid int) (Spy, error) {
4647
func ResolveAutoName(s string) string {
4748
return autoDetectionMapping[s]
4849
}
50+
51+
func SupportedExecSpies() []string {
52+
supportedSpies := []string{}
53+
for _, s := range SupportedSpies {
54+
if s != "gospy" {
55+
supportedSpies = append(supportedSpies, s)
56+
}
57+
}
58+
59+
return supportedSpies
60+
}

pkg/agent/upstream/remote/remote.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/http"
77
"net/url"
88
"strconv"
9+
"sync"
910
"time"
1011

1112
log "github.com/sirupsen/logrus"
@@ -25,7 +26,7 @@ type uploadJob struct {
2526
type Remote struct {
2627
cfg RemoteConfig
2728
todo chan *uploadJob
28-
done chan struct{}
29+
done chan *sync.WaitGroup
2930
client *http.Client
3031
}
3132

@@ -39,7 +40,7 @@ func New(cfg RemoteConfig) *Remote {
3940
r := &Remote{
4041
cfg: cfg,
4142
todo: make(chan *uploadJob, 100),
42-
done: make(chan struct{}, cfg.UpstreamThreads),
43+
done: make(chan *sync.WaitGroup, cfg.UpstreamThreads),
4344
client: &http.Client{
4445
Transport: &http.Transport{
4546
MaxConnsPerHost: cfg.UpstreamThreads,
@@ -58,9 +59,12 @@ func (u *Remote) start() {
5859
}
5960

6061
func (u *Remote) Stop() {
62+
wg := sync.WaitGroup{}
63+
wg.Add(u.cfg.UpstreamThreads)
6164
for i := 0; i < u.cfg.UpstreamThreads; i++ {
62-
u.done <- struct{}{}
65+
u.done <- &wg
6366
}
67+
wg.Wait()
6468
}
6569

6670
// TODO: this metadata class should be unified
@@ -112,7 +116,8 @@ func (u *Remote) uploadLoop() {
112116
select {
113117
case j := <-u.todo:
114118
u.uploadProfile(j)
115-
case <-u.done:
119+
case wg := <-u.done:
120+
wg.Done()
116121
return
117122
}
118123
}

pkg/exec/cli.go

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/pyroscope-io/pyroscope/pkg/agent/spy"
2020
"github.com/pyroscope-io/pyroscope/pkg/agent/upstream/remote"
2121
"github.com/pyroscope-io/pyroscope/pkg/config"
22+
"github.com/pyroscope-io/pyroscope/pkg/util/atexit"
2223
"github.com/sirupsen/logrus"
2324
)
2425

@@ -32,7 +33,7 @@ func Cli(cfg *config.Config, args []string) error {
3233
baseName := path.Base(args[0])
3334
spyName = spy.ResolveAutoName(baseName)
3435
if spyName == "" {
35-
supportedSpies := supportedSpiesWithoutGospy()
36+
supportedSpies := spy.SupportedExecSpies()
3637
suggestedCommand := fmt.Sprintf("pyroscope exec -spy-name %s %s", supportedSpies[0], strings.Join(args, " "))
3738
return fmt.Errorf(
3839
"could not automatically find a spy for program \"%s\". Pass spy name via %s argument, for example: \n %s\n\nAvailable spies are: %s\n%s\nIf you believe this is a mistake, please submit an issue at %s",
@@ -69,36 +70,42 @@ func Cli(cfg *config.Config, args []string) error {
6970
UpstreamThreads: cfg.Exec.UpstreamThreads,
7071
UpstreamRequestTimeout: cfg.Exec.UpstreamRequestTimeout,
7172
})
73+
defer u.Stop()
7274

73-
// TODO: make configurable?
75+
// TODO: improve this logic, basically we need a smart way of detecting that an app successfully loaded.
76+
// Maybe do this on some ticker (every 100 ms) with a timeout (20 s). Make this configurable too
7477
time.Sleep(5 * time.Second)
75-
// TODO: add sample rate
78+
// TODO: add sample rate, make it configurable
7679
sess := agent.NewSession(u, cfg.Exec.ApplicationName, spyName, 100, cmd.Process.Pid, cfg.Exec.DetectSubprocesses)
7780
sess.Start()
7881
defer sess.Stop()
7982

80-
// TODO: very hacky, at some point we'll need to make wait work
81-
// cmd.Wait()
82-
83-
for {
84-
time.Sleep(time.Second)
85-
p, err := ps.FindProcess(cmd.Process.Pid)
86-
if p == nil || err != nil {
87-
break
88-
}
89-
}
83+
waitForProcessToExit(cmd)
9084
return nil
9185
}
9286

93-
func supportedSpiesWithoutGospy() []string {
94-
supportedSpies := []string{}
95-
for _, s := range spy.SupportedSpies {
96-
if s != "gospy" {
97-
supportedSpies = append(supportedSpies, s)
87+
// TODO: very hacky, at some point we'll need to make `cmd.Wait()` work
88+
// Currently the issue is that on Linux it often thinks the process exited when it did not.
89+
func waitForProcessToExit(cmd *exec.Cmd) {
90+
sigc := make(chan struct{})
91+
92+
atexit.Register(func() {
93+
sigc <- struct{}{}
94+
})
95+
96+
t := time.NewTicker(time.Second)
97+
for {
98+
select {
99+
case <-sigc:
100+
cmd.Process.Kill()
101+
return
102+
case <-t.C:
103+
p, err := ps.FindProcess(cmd.Process.Pid)
104+
if p == nil || err != nil {
105+
return
106+
}
98107
}
99108
}
100-
101-
return supportedSpies
102109
}
103110

104111
func performChecks(spyName string) error {
@@ -113,7 +120,7 @@ func performChecks(spyName string) error {
113120
}
114121

115122
if !stringsContains(spy.SupportedSpies, spyName) {
116-
supportedSpies := supportedSpiesWithoutGospy()
123+
supportedSpies := spy.SupportedExecSpies()
117124
return fmt.Errorf(
118125
"Spy \"%s\" is not supported. Available spies are: %s\n%s",
119126
color.BlueString(spyName),

pkg/util/atexit/atexit.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ func init() {
1616
for _, cb := range callbacks {
1717
cb()
1818
}
19-
os.Exit(0)
2019
}()
2120
}
2221

0 commit comments

Comments
 (0)