Skip to content

Commit 38c29ff

Browse files
authored
Merge pull request #756 from klihub/devel/write-pid-file
resmgr: write a PID file upon successful startup.
2 parents 430099e + 06b9f08 commit 38c29ff

File tree

5 files changed

+437
-1
lines changed

5 files changed

+437
-1
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ require (
1919
github.com/prometheus/common v0.30.0
2020
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
2121
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
22+
github.com/stretchr/testify v1.7.0 // indirect
2223
go.opencensus.io v0.22.4
2324
golang.org/x/net v0.0.0-20210525063256-abc453219eb5
2425
golang.org/x/sys v0.0.0-20210903071746-97244b99971b

pkg/cri/resource-manager/flags.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"time"
2020

2121
"github.com/intel/cri-resource-manager/pkg/cri/resource-manager/sockets"
22+
"github.com/intel/cri-resource-manager/pkg/pidfile"
2223
)
2324

2425
// Options captures our command line parameters.
@@ -29,6 +30,7 @@ type options struct {
2930
RelayDir string
3031
AgentSocket string
3132
ConfigSocket string
33+
PidFile string
3234
ResctrlPath string
3335
FallbackConfig string
3436
ForceConfig string
@@ -58,7 +60,8 @@ func init() {
5860
"local socket of the cri-resmgr agent to connect")
5961
flag.StringVar(&opt.ConfigSocket, "config-socket", sockets.ResourceManagerConfig,
6062
"Unix domain socket path where the resource manager listens for cri-resmgr-agent")
61-
63+
flag.StringVar(&opt.PidFile, "pid-file", pidfile.GetPath(),
64+
"PID file to write daemon PID to")
6265
flag.StringVar(&opt.FallbackConfig, "fallback-config", "",
6366
"Fallback configuration to use unless/until one is available from the cache or agent.")
6467
flag.StringVar(&opt.ForceConfig, "force-config", "",

pkg/cri/resource-manager/resource-manager.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
"github.com/intel/cri-resource-manager/pkg/cri/resource-manager/visualizer"
3737
"github.com/intel/cri-resource-manager/pkg/instrumentation"
3838
logger "github.com/intel/cri-resource-manager/pkg/log"
39+
"github.com/intel/cri-resource-manager/pkg/pidfile"
3940

4041
policyCollector "github.com/intel/cri-resource-manager/pkg/policycollector"
4142
"github.com/intel/cri-resource-manager/pkg/utils"
@@ -118,6 +119,10 @@ func NewResourceManager() (ResourceManager, error) {
118119
}
119120

120121
if err := m.setupRelay(); err != nil {
122+
pid, _ := pidfile.OwnerPid()
123+
if pid > 0 {
124+
m.Error("looks like we're already running as pid %d...", pid)
125+
}
121126
return nil, err
122127
}
123128

@@ -165,6 +170,13 @@ func (m *resmgr) Start() error {
165170
return resmgrError("failed to start CRI relay: %v", err)
166171
}
167172

173+
if err := pidfile.Remove(); err != nil {
174+
return resmgrError("failed to remove stale/old PID file: %v", err)
175+
}
176+
if err := pidfile.Write(); err != nil {
177+
return resmgrError("failed to write PID file: %v", err)
178+
}
179+
168180
if opt.ForceConfig == "" {
169181
if err := m.configServer.Start(opt.ConfigSocket); err != nil {
170182
return resmgrError("failed to start configuration server: %v", err)

pkg/pidfile/pidfile.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright 2022 Intel Corporation. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package pidfile
16+
17+
import (
18+
"fmt"
19+
"os"
20+
"path/filepath"
21+
"strconv"
22+
"strings"
23+
"syscall"
24+
25+
"github.com/pkg/errors"
26+
)
27+
28+
var (
29+
pidFilePath = defaultPath()
30+
pidFile *os.File
31+
)
32+
33+
// GetPath returns the current pidfile path.
34+
func GetPath() string {
35+
return pidFilePath
36+
}
37+
38+
// SetPath sets the pidfile path to the given one.
39+
func SetPath(path string) {
40+
close()
41+
pidFilePath = path
42+
}
43+
44+
// Write opens the PID file and writes os.Getpid() to it. If the PID file already
45+
// exists Write() fails with an error. On successful completion, Write keeps the
46+
// PID file open.
47+
func Write() error {
48+
if pidFile != nil {
49+
return nil
50+
}
51+
52+
err := os.MkdirAll(filepath.Dir(pidFilePath), 0755)
53+
if err != nil {
54+
return errors.Wrap(err, "failed to create PID file")
55+
}
56+
57+
pidFile, err = os.OpenFile(pidFilePath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
58+
if err != nil {
59+
return errors.Wrap(err, "failed to create PID file")
60+
}
61+
62+
_, err = pidFile.Write([]byte(fmt.Sprintf("%d\n", os.Getpid())))
63+
if err != nil {
64+
close()
65+
return errors.Wrap(err, "failed to write PID file")
66+
}
67+
68+
return nil
69+
}
70+
71+
// Read reads the content of the PID file. It returns the process ID found
72+
// in the file. If opening the file or reading an integer process ID fails
73+
// Read() returns -1 and an error.
74+
func Read() (int, error) {
75+
var (
76+
pid int
77+
buf []byte
78+
err error
79+
)
80+
81+
if buf, err = os.ReadFile(pidFilePath); err != nil {
82+
if os.IsNotExist(err) {
83+
return 0, nil
84+
}
85+
return -1, errors.Wrap(err, "failed to read PID file")
86+
}
87+
88+
if pid, err = strconv.Atoi(strings.TrimRight(string(buf), "\n")); err != nil {
89+
return -1, errors.Wrapf(err, "invalid PID (%q) in PID file", string(buf))
90+
}
91+
92+
return pid, nil
93+
}
94+
95+
// close closes the PID file and truncates it to zero length.
96+
func close() {
97+
if pidFile != nil {
98+
pidFile.Truncate(0)
99+
pidFile.Close()
100+
pidFile = nil
101+
}
102+
}
103+
104+
// Remove removes the PID file for the process unconditionally, regardless if
105+
// the current process had created the PID file or not.
106+
func Remove() error {
107+
close()
108+
err := os.Remove(pidFilePath)
109+
if err != nil {
110+
if os.IsNotExist(err) {
111+
return nil
112+
}
113+
}
114+
return err
115+
}
116+
117+
// OwnerPid returns the ID of the process owning the PID file. 0 is returned
118+
// if it is known that no process owns the file. -1 and an error is returned
119+
// if the owner or its existence could not be determined.
120+
func OwnerPid() (int, error) {
121+
var (
122+
pid int
123+
p *os.Process
124+
err error
125+
)
126+
127+
pid, err = Read()
128+
if err != nil {
129+
return -1, err
130+
}
131+
if pid == 0 {
132+
return 0, nil
133+
}
134+
135+
p, err = os.FindProcess(pid)
136+
if err != nil {
137+
return -1, errors.Wrapf(err, "FindProcess() failed for PID %d", pid)
138+
}
139+
140+
err = p.Signal(syscall.Signal(0))
141+
if err == os.ErrProcessDone {
142+
return 0, nil
143+
}
144+
if err == nil {
145+
return pid, nil
146+
}
147+
148+
return -1, errors.Wrapf(err, "failed to check process %d", pid)
149+
}
150+
151+
// defaultPath returns the default pidfile path.
152+
func defaultPath() string {
153+
var path string
154+
155+
if len(os.Args) > 0 {
156+
name := filepath.Base(os.Args[0])
157+
if euid := os.Geteuid(); euid > 0 {
158+
path = filepath.Join("/tmp", name+".pid")
159+
} else {
160+
path = filepath.Join("/", "var", "run", name+".pid")
161+
}
162+
}
163+
164+
return path
165+
}

0 commit comments

Comments
 (0)