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