Files
cloudflared/token/lockfile_test.go
T
Evan Raw da81fb02ec
Check / check (1.22.x, macos-latest) (push) Has been cancelled
Check / check (1.22.x, ubuntu-latest) (push) Has been cancelled
Check / check (1.22.x, windows-latest) (push) Has been cancelled
Semgrep config / semgrep/ci (push) Has been cancelled
AUTH-4699, AUTH-8460, TUN-10179: Fix .lock file deletion race condition
Replace the lock file mechanism with PID+start-time based stale
detection so that no cleanup is required on process death.

When both org and app token locks were held, the first signal handler
to call os.Exit() would kill the process before the second handler
could delete its lock file. The orphaned lock file then caused the
next invocation to wait ~128 seconds in an exponential backoff loop
before forcibly deleting it. The same issue occurred on SIGKILL, OOM,
or any non-signal death.

Lock files now contain the holder's PID and process start time as
JSON. On acquisition, if a lock file already exists, the recorded
process is checked for liveness via gopsutil. Stale locks are
reclaimed immediately with no backoff. Atomic O_CREATE|O_EXCL
prevents races between concurrent acquirers.

Also adds a companion .url file so processes waiting on an active
lock can print the auth URL for the user.
2026-05-01 13:04:51 +00:00

115 lines
3.1 KiB
Go

package token
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIsLockFileStale_DeadProcess(t *testing.T) {
// write a lock file with a PID that cannot exist (e.g., max int32)
dir := t.TempDir()
path := filepath.Join(dir, "test.lock")
content := lockContent{PID: 2147483647, StartTime: 1000000000000}
data, err := json.Marshal(content)
require.NoError(t, err)
require.NoError(t, os.WriteFile(path, data, 0600))
stale, _, err := isLockFileStale(path)
require.NoError(t, err)
assert.True(t, stale)
}
func TestIsLockFileStale_LiveProcess(t *testing.T) {
// write a lock file with our own PID and start time
dir := t.TempDir()
path := filepath.Join(dir, "test.lock")
content, err := newSelfLockContent()
require.NoError(t, err)
data, err := json.Marshal(content)
require.NoError(t, err)
require.NoError(t, os.WriteFile(path, data, 0600))
stale, readBack, err := isLockFileStale(path)
require.NoError(t, err)
assert.False(t, stale)
assert.Equal(t, content.PID, readBack.PID)
assert.Equal(t, content.StartTime, readBack.StartTime)
}
func TestIsLockFileStale_EmptyFile(t *testing.T) {
// backward compat: old lock files are empty
dir := t.TempDir()
path := filepath.Join(dir, "test.lock")
require.NoError(t, os.WriteFile(path, []byte{}, 0600))
stale, _, err := isLockFileStale(path)
require.NoError(t, err)
assert.True(t, stale)
}
func TestIsLockFileStale_CorruptFile(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.lock")
require.NoError(t, os.WriteFile(path, []byte("not json"), 0600))
stale, _, err := isLockFileStale(path)
require.NoError(t, err)
assert.True(t, stale)
}
func TestReadAuthURL_Exists(t *testing.T) {
dir := t.TempDir()
tokenPath := filepath.Join(dir, "token")
url := "https://example.com/cdn-cgi/access/cli?token=abc123"
require.NoError(t, os.WriteFile(tokenPath+".url", []byte(url), 0600))
assert.Equal(t, url, readAuthURL(tokenPath))
}
func TestReadAuthURL_NotExists(t *testing.T) {
dir := t.TempDir()
tokenPath := filepath.Join(dir, "token")
assert.Empty(t, readAuthURL(tokenPath))
}
func TestTryCreateLockFile_Success(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.lock")
err := tryCreateLockFile(path)
require.NoError(t, err)
// verify the file contains valid JSON with our PID
data, err := os.ReadFile(path) // nolint: gosec
require.NoError(t, err)
var content lockContent
require.NoError(t, json.Unmarshal(data, &content))
assert.Equal(t, int32(os.Getpid()), content.PID) // nolint: gosec
assert.Positive(t, content.StartTime)
}
func TestTryCreateLockFile_AlreadyExists(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.lock")
require.NoError(t, tryCreateLockFile(path))
// second create should fail with "already exists"
err := tryCreateLockFile(path)
require.Error(t, err)
assert.True(t, os.IsExist(err))
}
func TestNewSelfLockContent(t *testing.T) {
content, err := newSelfLockContent()
require.NoError(t, err)
assert.Equal(t, int32(os.Getpid()), content.PID) // nolint: gosec
assert.Positive(t, content.StartTime)
}