github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/utils/waitproc.go (about) 1 /* 2 Copyright 2017 Mirantis 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package utils 18 19 import ( 20 "fmt" 21 "io/ioutil" 22 "strconv" 23 "strings" 24 "time" 25 26 "github.com/golang/glog" 27 "github.com/jonboulle/clockwork" 28 ) 29 30 const ( 31 waitProcRetryPeriod = 200 * time.Millisecond 32 waitProcTimeout = 30 * time.Second 33 ) 34 35 func readProcFile(procFile string) (int, uint64, error) { 36 content, err := ioutil.ReadFile(procFile) 37 if err != nil { 38 return 0, 0, fmt.Errorf("reading procfile %q: %v", procFile, err) 39 } 40 parts := strings.Split(strings.TrimSpace(string(content)), " ") 41 if len(parts) != 2 { 42 return 0, 0, fmt.Errorf("procfile %q is malformed: wrong number of fields", procFile) 43 } 44 pid, err := strconv.Atoi(parts[0]) 45 if err != nil { 46 return 0, 0, fmt.Errorf("procfile %q is malformed: bad pid field", procFile) 47 } 48 startTime, err := strconv.ParseUint(parts[1], 10, 64) 49 if err != nil { 50 return 0, 0, fmt.Errorf("procfile %q is malformed: bad start time field", procFile) 51 } 52 return pid, startTime, nil 53 } 54 55 func getProcessStartTime(pid int) (uint64, error) { 56 statPath := fmt.Sprintf("/proc/%d/stat", pid) 57 content, err := ioutil.ReadFile(statPath) 58 if err != nil { 59 return 0, fmt.Errorf("error reading %q: %v", statPath, err) 60 } 61 text := string(content) 62 // avoid problems due to spaces and parens in the executable name 63 parenPos := strings.LastIndex(text, ")") 64 if parenPos == -1 { 65 return 0, fmt.Errorf("can't parse %q: no closing paren after executable filename", statPath) 66 } 67 parts := strings.Split(text[parenPos:], " ") 68 if len(parts) < 21 { 69 return 0, fmt.Errorf("can't parse %q: insufficient number of fields", statPath) 70 } 71 startTime, err := strconv.ParseUint(parts[20], 10, 64) 72 if err != nil { 73 return 0, fmt.Errorf("can't parse %q: bad start time field", statPath) 74 } 75 return startTime, nil 76 } 77 78 // WaitForProcess waits for the following conditions to be true 79 // at the same time: 80 // * the specified procFile is readable and contains two numeric values separated by space, 81 // PID and start time in jiffies (field 22 in /proc/PID/stat, starting from 1) 82 // * the process with the PID read from procFile exists and has start time 83 // equal to the start time read from procFile 84 // This avoids possible problems with stale procFile that could happen 85 // if only PID was stored there. 86 // The command can be used in shell script to generate the "procfile" 87 // for the current shell: 88 // /bin/sh -c 'echo "$$ `cut -d" " -f22 /proc/$$/stat`"' 89 func WaitForProcess(procFile string) (int, error) { 90 var pid int 91 err := WaitLoop(func() (bool, error) { 92 var expectedStartTime uint64 93 var err error 94 pid, expectedStartTime, err = readProcFile(procFile) 95 if err != nil { 96 glog.V(3).Infof("procfile %q not ready yet: %v", procFile, err) 97 return false, nil 98 } 99 startTime, err := getProcessStartTime(pid) 100 if err != nil { 101 glog.V(3).Infof("procfile %q: can't get process start time for pid %d yet: %v", procFile, pid, err) 102 return false, nil 103 } 104 if startTime != expectedStartTime { 105 glog.V(3).Infof("procfile %q is stale: start time for pid %d is %d instead of %d", procFile, pid, startTime, expectedStartTime) 106 return false, nil 107 } 108 return true, nil 109 }, waitProcRetryPeriod, waitProcTimeout, clockwork.NewRealClock()) 110 if err != nil { 111 return 0, err 112 } 113 return pid, nil 114 }