forked from keybase/client
/
service_info.go
146 lines (125 loc) · 3.5 KB
/
service_info.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.
package libkb
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"time"
"github.com/keybase/client/go/logger"
)
// ServiceInfo describes runtime info for a service.
// This is primarily used to detect service updates.
type ServiceInfo struct {
Version string `json:"version,omitempty"`
Label string `json:"label,omitempty"`
Pid int `json:"pid,omitempty"`
}
// KeybaseServiceInfo is runtime info for the Keybase service.
func KeybaseServiceInfo(g *GlobalContext) ServiceInfo {
return ServiceInfo{
Version: VersionString(),
Label: g.Env.GetLabel(),
Pid: os.Getpid(),
}
}
// NewServiceInfo generates service info for other services (like KBFS).
func NewServiceInfo(version string, prerelease string, label string, pid int) ServiceInfo {
if prerelease != "" {
version = fmt.Sprintf("%s-%s", version, prerelease)
}
return ServiceInfo{
Version: version,
Label: label,
Pid: pid,
}
}
// WriteFile writes service info as JSON in runtimeDir.
func (s ServiceInfo) WriteFile(path string, log logger.Logger) error {
out, err := json.MarshalIndent(s, "", " ")
if err != nil {
return err
}
file := NewFile(path, []byte(out), 0644)
return file.Save(log)
}
// serviceLog is the log interface for ServiceInfo
type serviceLog interface {
Debug(s string, args ...interface{})
}
// WaitForServiceInfoFile tries to wait for a service info file, which should be
// written on successful service startup.
func WaitForServiceInfoFile(path string, label string, pid string, timeout time.Duration, log serviceLog) (*ServiceInfo, error) {
if pid == "" {
return nil, fmt.Errorf("No pid to wait for")
}
lookForServiceInfo := func() (*ServiceInfo, error) {
if _, ferr := os.Stat(path); os.IsNotExist(ferr) {
return nil, nil
}
dat, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
var serviceInfo ServiceInfo
err = json.Unmarshal(dat, &serviceInfo)
if err != nil {
return nil, err
}
// Make sure the info file is the pid we are waiting for, otherwise it is
// still starting up.
if pid != fmt.Sprintf("%d", serviceInfo.Pid) {
return nil, nil
}
// PIDs match, the service has started up
return &serviceInfo, nil
}
log.Debug("Looking for service info file (timeout=%s)", timeout)
serviceInfo, err := waitForServiceInfo(timeout, time.Millisecond*400, lookForServiceInfo)
// If no service info was found, let's return an error
if serviceInfo == nil {
if err == nil {
err = fmt.Errorf("%s isn't running (expecting pid=%s)", label, pid)
}
return nil, err
}
// We succeeded in finding service info
log.Debug("Found service info: %#v", *serviceInfo)
return serviceInfo, nil
}
type serviceInfoResult struct {
info *ServiceInfo
err error
}
type loadServiceInfoFn func() (*ServiceInfo, error)
func waitForServiceInfo(timeout time.Duration, delay time.Duration, fn loadServiceInfoFn) (*ServiceInfo, error) {
if timeout <= 0 {
return fn()
}
ticker := time.NewTicker(delay)
defer ticker.Stop()
resultChan := make(chan serviceInfoResult, 1)
go func() {
for {
select {
case <-ticker.C:
info, err := fn()
if err != nil {
resultChan <- serviceInfoResult{info: nil, err: err}
return
}
if info != nil {
resultChan <- serviceInfoResult{info: info, err: nil}
return
}
}
}
}()
select {
case res := <-resultChan:
return res.info, res.err
case <-time.After(timeout):
return nil, nil
}
}