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  }