Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add timeout and retry logic for finding NGINX PID file #676

Merged
merged 3 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 40 additions & 9 deletions internal/nginx/runtime/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,27 @@ package runtime

import (
"context"
"errors"
"fmt"
"io/fs"
"os"
"strconv"
"strings"
"syscall"
"time"

"k8s.io/apimachinery/pkg/util/wait"
)

const pidFile = "/etc/nginx/nginx.pid"
const (
pidFile = "/etc/nginx/nginx.pid"
pidFileTimeout = 5 * time.Second
)

type readFileFunc func(string) ([]byte, error)
type (
readFileFunc func(string) ([]byte, error)
checkFileFunc func(string) (fs.FileInfo, error)
)

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . Manager

Expand All @@ -31,13 +41,8 @@ func NewManagerImpl() *ManagerImpl {
}

func (m *ManagerImpl) Reload(ctx context.Context) error {
// FIXME(pleshakov): Before reload attempt, make sure NGINX is running.
// If the gateway container starts before NGINX container (which is possible),
// then it is possible that a reload can be attempted when NGINX is not running yet.
// Make sure to prevent this case, so we don't get an error.

// We find the main NGINX PID on every reload because it will change if the NGINX container is restarted.
pid, err := findMainProcess(os.ReadFile)
pid, err := findMainProcess(ctx, os.Stat, os.ReadFile, pidFileTimeout)
if err != nil {
return fmt.Errorf("failed to find NGINX main process: %w", err)
}
Expand Down Expand Up @@ -65,7 +70,33 @@ func (m *ManagerImpl) Reload(ctx context.Context) error {
return nil
}

func findMainProcess(readFile readFileFunc) (int, error) {
func findMainProcess(
ctx context.Context,
checkFile checkFileFunc,
readFile readFileFunc,
timeout time.Duration,
) (int, error) {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

err := wait.PollUntilContextCancel(
ctx,
1*time.Second,
true, /* poll immediately */
func(ctx context.Context) (bool, error) {
_, err := checkFile(pidFile)
if err == nil {
return true, nil
}
if !errors.Is(err, fs.ErrNotExist) {
return false, err
}
return false, nil
})
if err != nil {
return 0, err
}

content, err := readFile(pidFile)
if err != nil {
return 0, err
Expand Down
47 changes: 46 additions & 1 deletion internal/nginx/runtime/manager_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package runtime

import (
"context"
"errors"
"io/fs"
"testing"
"time"
)

func TestFindMainProcess(t *testing.T) {
Expand All @@ -18,40 +21,82 @@ func TestFindMainProcess(t *testing.T) {
return nil, errors.New("error")
}

checkFileFuncGen := func(content fs.FileInfo) checkFileFunc {
return func(name string) (fs.FileInfo, error) {
if name != pidFile {
return nil, errors.New("error")
}
return content, nil
}
}
checkFileError := func(string) (fs.FileInfo, error) {
return nil, errors.New("error")
}
var testFileInfo fs.FileInfo
ctx := context.Background()
cancellingCtx, cancel := context.WithCancel(ctx)
time.AfterFunc(1*time.Millisecond, cancel)

tests := []struct {
ctx context.Context
readFile readFileFunc
checkFile checkFileFunc
msg string
expected int
expectError bool
}{
{
ctx: ctx,
readFile: readFileFuncGen([]byte("1\n")),
checkFile: checkFileFuncGen(testFileInfo),
expected: 1,
expectError: false,
msg: "normal case",
},
{
ctx: ctx,
readFile: readFileFuncGen([]byte("")),
checkFile: checkFileFuncGen(testFileInfo),
expected: 0,
expectError: true,
msg: "empty file content",
},
{
ctx: ctx,
readFile: readFileFuncGen([]byte("not a number")),
checkFile: checkFileFuncGen(testFileInfo),
expected: 0,
expectError: true,
msg: "bad file content",
},
{
ctx: ctx,
readFile: readFileError,
checkFile: checkFileFuncGen(testFileInfo),
expected: 0,
expectError: true,
msg: "cannot read file",
},
{
ctx: ctx,
readFile: readFileFuncGen([]byte("1\n")),
checkFile: checkFileError,
expected: 0,
expectError: true,
msg: "cannot find pid file",
},
{
ctx: cancellingCtx,
readFile: readFileFuncGen([]byte("1\n")),
checkFile: checkFileError,
expected: 0,
expectError: true,
msg: "context canceled",
},
}
ciarams87 marked this conversation as resolved.
Show resolved Hide resolved

for _, test := range tests {
result, err := findMainProcess(test.readFile)
result, err := findMainProcess(test.ctx, test.checkFile, test.readFile, 2*time.Millisecond)

if result != test.expected {
t.Errorf("findMainProcess() returned %d but expected %d for case %q", result, test.expected, test.msg)
Expand Down