Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,32 @@ positions:
}, out)
}

// TestWritePositionFile_ReusesDentry verifies that writePositionFile uses a
// deterministic temp path (<target>.tmp) rather than a unique random name on
// every call, so the kernel dentry slab does not grow unboundedly under
// cgroup v2.
func TestWritePositionFile_ReusesDentry(t *testing.T) {
tmpDir := t.TempDir()
target := filepath.Join(tmpDir, "positions.yaml")

// Write multiple times and verify the temp file path stays the same.
for i := 0; i < 3; i++ {
err := writePositionFile(target, map[Entry]string{
{Path: "/tmp/test.log", Labels: ""}: "100",
})
require.NoError(t, err)

// The temp file should either not exist (already renamed) or be the
// deterministic <target>.tmp path.
entries, readErr := os.ReadDir(tmpDir)
require.NoError(t, readErr)
for _, entry := range entries {
require.NotContains(t, entry.Name(), "positions.yaml.",
"unexpected uniquely-named temp file (renameio-style)")
}
}
}

func TestReadEmptyLabels(t *testing.T) {
temp := tempFilename(t)
defer func() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,51 @@ import (
"os"
"path/filepath"

renameio "github.com/google/renameio/v2"
yaml "gopkg.in/yaml.v2"
)

const positionFileMode = 0600

// atomicWriteFile writes buf to filename using a deterministic temp file
// (<target>.tmp), fsyncs, renames over the target, then fsyncs the directory.
// This reuses the same dentry on every write instead of allocating a new one
// (as renameio.WriteFile does), which prevents unbounded kernel dentry slab
// growth under cgroup v2.
func atomicWriteFile(filename string, buf []byte) error {
target := filepath.Clean(filename)
tmp := target + ".tmp"

f, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(positionFileMode))
if err != nil {
return err
}

if _, err := f.Write(buf); err != nil {
f.Close()
return err
}

if err := f.Sync(); err != nil {
f.Close()
return err
}

if err := f.Close(); err != nil {
return err
}

if err := os.Rename(tmp, target); err != nil {
return err
}

dir, err := os.Open(filepath.Dir(target))
if err != nil {
return err
}
defer dir.Close()
return dir.Sync()
}

func writePositionFile(filename string, positions map[Entry]string) error {
buf, err := yaml.Marshal(File{
Positions: positions,
Expand All @@ -24,7 +63,5 @@ func writePositionFile(filename string, positions map[Entry]string) error {
return err
}

target := filepath.Clean(filename)

return renameio.WriteFile(target, buf, os.FileMode(positionFileMode))
return atomicWriteFile(filename, buf)
}