Skip to content

Commit

Permalink
Add handling for outdated PID files (#43)
Browse files Browse the repository at this point in the history
* Add handling for outdated PID files

* goimports

* Fix goreleaser config

* Bump Goreleaser & Goreleaser workflow
  • Loading branch information
martin-helmich authored Nov 23, 2022
1 parent c95c449 commit 5b8502c
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 51 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
go-version: 1.16

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
uses: goreleaser/goreleaser-action@v3
with:
version: latest
version: v1.13.0
args: release --rm-dist --snapshot --skip-publish
4 changes: 2 additions & 2 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ jobs:
go-version: 1.16

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
uses: goreleaser/goreleaser-action@v3
with:
version: latest
version: v1.13.0
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_USER_TOKEN }}
5 changes: 3 additions & 2 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ nfpms:
- deb
- rpm
bindir: /usr/bin
empty_folders:
- /etc/mittnite.d
contents:
- dst: /etc/mittnite.d
type: dir
checksum:
name_template: 'checksums.txt'
snapshot:
Expand Down
50 changes: 6 additions & 44 deletions cmd/up.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package cmd

import (
"bytes"
"context"
"fmt"
"os"
"os/signal"
"path/filepath"
"syscall"

"github.com/mittwald/mittnite/internal/config"
"github.com/mittwald/mittnite/pkg/files"
"github.com/mittwald/mittnite/pkg/pidfile"
"github.com/mittwald/mittnite/pkg/probe"
"github.com/mittwald/mittnite/pkg/proc"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -45,11 +44,14 @@ var up = &cobra.Command{
Jobs: nil,
}

if err := writePidFile(); err != nil {
pidFileHandle := pidfile.New(pidFile)

if err := pidFileHandle.Acquire(); err != nil {
log.Fatalf("failed to write pid file to %q: %s", pidFile, err)
}

defer func() {
if err := deletePidFile(); err != nil {
if err := pidFileHandle.Release(); err != nil {
log.Errorf("error while cleaning up the pid file: %s", err)
}
}()
Expand Down Expand Up @@ -121,43 +123,3 @@ var up = &cobra.Command{
}
},
}

func writePidFile() error {
if pidFile == "" {
return nil
}

if err := os.MkdirAll(filepath.Dir(pidFile), 0o755); err != nil {
return err
}
if stats, err := os.Stat(pidFile); err == nil {
if stats.Size() > 0 {
return fmt.Errorf("pidFile %q already exists", pidFile)
}
}

return os.WriteFile(pidFile, pidToByteString(), 0644)

}

func deletePidFile() error {
if pidFile == "" {
return nil
}

pid := pidToByteString()
content, err := os.ReadFile(pidFile)
if err != nil {
return err
}

if bytes.Compare(pid, content) != 0 {
return fmt.Errorf("won't delete pid file %q because it does not contain the expected content", pidFile)
}

return os.Remove(pidFile)
}

func pidToByteString() []byte {
return []byte(fmt.Sprintf("%d", os.Getpid()))
}
104 changes: 104 additions & 0 deletions pkg/pidfile/pidfile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package pidfile

import (
"fmt"
"os"
"path/filepath"
"strconv"
"syscall"

"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)

type PIDFile struct {
path string
fd int
}

func New(path string) PIDFile {
return PIDFile{
path: path,
}
}

func (f PIDFile) Acquire() error {
if f.path == "" {
return nil
}

if err := os.MkdirAll(filepath.Dir(f.path), 0o755); err != nil {
return errors.Wrapf(err, "failed to create pid file directory %q", filepath.Dir(f.path))
}

fd, err := syscall.Open(f.path, syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY, 0o644)
switch err {
case syscall.EEXIST:
if err := f.removePIDFileIfOutdated(); err != nil {
return err
}

return f.Acquire()
case nil:
if _, err := syscall.Write(fd, pidToByteString()); err != nil {
return errors.Wrapf(err, "failed to write pid to pid file %q", f.path)
}

log.Info("acquired pid file ", f.path)
default:
return errors.Wrapf(err, "failed to open pid file %q", f.path)
}

f.fd = fd
return nil
}

func (f PIDFile) removePIDFileIfOutdated() error {
pidStr, err := os.ReadFile(f.path)
if err != nil {
return errors.Wrapf(err, "failed to read pid file '%s'", f.path)
}

pid, err := strconv.Atoi(string(pidStr))
if err != nil {
return errors.Wrapf(err, "failed to parse pid file '%s'", f.path)
}

process, err := os.FindProcess(pid)
if err != nil {
return errors.Wrapf(err, "failed to find process with pid %d", pid)
}

if err := process.Signal(syscall.Signal(0)); err == nil {
return fmt.Errorf("pid file %q already exists and contains the PID of a running process", f.path)
}

log.Info("existing pid file contains the PID of a non-running process; removing it")

if err := os.Remove(f.path); err != nil {
return errors.Wrapf(err, "failed to remove pid file %q", f.path)
}

return nil
}

func (f PIDFile) Release() error {
if f.path == "" {
return nil
}

if err := syscall.Close(f.fd); err != nil {
return errors.Wrapf(err, "failed to close pid file %q", f.path)
}

if err := os.Remove(f.path); err != nil {
return errors.Wrapf(err, "failed to remove pid file %q", f.path)
}

log.Info("released pid file ", f.path)
return nil
}

func pidToByteString() []byte {
return []byte(fmt.Sprintf("%d", os.Getpid()))
}
53 changes: 53 additions & 0 deletions pkg/pidfile/pidfile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package pidfile_test

import (
"os"
"testing"

"github.com/mittwald/mittnite/pkg/pidfile"
"github.com/stretchr/testify/require"
)

func TestPidFileCanBeAcquiredAndReleased(t *testing.T) {
f := pidfile.New("./test1.pid")

require.NoError(t, f.Acquire())
require.NoError(t, f.Release())

_, err := os.Stat("./test1.pid")
require.True(t, os.IsNotExist(err))
}

func TestPidFileCanBeAcquiredWhenOutdatedFileExists(t *testing.T) {
f := pidfile.New("./test3.pid")

require.NoError(t, os.WriteFile("./test3.pid", []byte("12345"), 0o644))
require.NoError(t, f.Acquire())
require.NoError(t, f.Release())

_, err := os.Stat("./test3.pid")
require.True(t, os.IsNotExist(err))
}

func TestPidFileCannotBeAcquiredWhileAlreadyHeld(t *testing.T) {
f1 := pidfile.New("./test2.pid")
f2 := pidfile.New("./test2.pid")

closeF1 := make(chan struct{})
f1Acquired := make(chan struct{})
f1Closed := make(chan struct{})

go func() {
require.NoError(t, f1.Acquire())
close(f1Acquired)
<-closeF1
require.NoError(t, f1.Release())
close(f1Closed)
}()

<-f1Acquired

require.Error(t, f2.Acquire())
close(closeF1)
<-f1Closed
}
2 changes: 1 addition & 1 deletion pkg/proc/job_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import (
"context"
"errors"
"fmt"
"github.com/mittwald/mittnite/internal/config"
"os"
"os/exec"
"path/filepath"
"syscall"
"time"

"github.com/mittwald/mittnite/internal/config"
log "github.com/sirupsen/logrus"
)

Expand Down

0 comments on commit 5b8502c

Please sign in to comment.