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
2 changes: 2 additions & 0 deletions store/file/ods.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ func ValidateODSSize(path string, eds *rsmt2d.ExtendedDataSquare) error {
if err != nil {
return fmt.Errorf("opening file: %w", err)
}
defer ods.Close()

shares, err := filledSharesAmount(eds)
if err != nil {
Expand Down Expand Up @@ -173,6 +174,7 @@ func OpenODS(path string) (*ODS, error) {

h, err := readHeader(f)
if err != nil {
_ = f.Close()
return nil, err
}

Expand Down
54 changes: 54 additions & 0 deletions store/file/ods_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os"
"runtime"
"strconv"
"testing"
"time"
Expand Down Expand Up @@ -153,6 +154,59 @@ func TestValidateODSSize(t *testing.T) {
}
}

// openFDCount returns the number of file descriptors currently open by the
// process. It relies on Linux's /proc and skips the test elsewhere.
func openFDCount(t *testing.T) int {
t.Helper()
if runtime.GOOS != "linux" {
t.Skip("file descriptor counting via /proc is only available on Linux")
}
entries, err := os.ReadDir("/proc/self/fd")
require.NoError(t, err)
return len(entries)
}

// TestValidateODSSize_NoFDLeak ensures ValidateODSSize does not leak the file
// descriptor opened by OpenODS. It opened the file but never closed it on any
// path, leaking one fd per call during store startup validation.
func TestValidateODSSize_NoFDLeak(t *testing.T) {
eds := edstest.RandEDS(t, 8)
roots, err := share.NewAxisRoots(eds)
require.NoError(t, err)
path := t.TempDir() + "ods-fd-leak"
require.NoError(t, CreateODS(path, roots, eds))

// warm up one call so any one-off fds (not per-call leaks) are already open.
require.NoError(t, ValidateODSSize(path, eds))

before := openFDCount(t)
for range 50 {
require.NoError(t, ValidateODSSize(path, eds))
}
after := openFDCount(t)
require.LessOrEqual(t, after-before, 1, "ValidateODSSize leaked file descriptors")
}

// TestOpenODS_NoFDLeakOnHeaderError ensures OpenODS closes the underlying file
// when header parsing fails, instead of leaking the descriptor.
func TestOpenODS_NoFDLeakOnHeaderError(t *testing.T) {
// too short to contain a valid header, so readHeader fails.
path := t.TempDir() + "corrupt-ods"
require.NoError(t, os.WriteFile(path, []byte{0x00, 0x01, 0x02}, 0o600))

// warm up so the first-call allocations don't skew the count.
_, err := OpenODS(path)
require.Error(t, err)

before := openFDCount(t)
for range 50 {
_, err := OpenODS(path)
require.Error(t, err)
}
after := openFDCount(t)
require.LessOrEqual(t, after-before, 1, "OpenODS leaked a file descriptor on header error")
}

// BenchmarkAxisFromODSFile/Size:32/ProofType:row/squareHalf:0-16 382011 3104 ns/op
// BenchmarkAxisFromODSFile/Size:32/ProofType:row/squareHalf:1-16 9320 122408 ns/op
// BenchmarkAxisFromODSFile/Size:32/ProofType:col/squareHalf:0-16 4408911 266.5 ns/op
Expand Down
Loading