Merge pull request #3674 from tonistiigi/policy-fs-refs-fix

build: ref-count policy FS lifecycle
This commit is contained in:
CrazyMax
2026-02-27 15:52:36 +01:00
committed by GitHub
3 changed files with 118 additions and 11 deletions
+28 -8
View File
@@ -228,28 +228,48 @@ func normalizeLocalPolicyPath(name, contextDir string) string {
type memoizedPolicyFS struct { type memoizedPolicyFS struct {
init func() (fs.StatFS, func() error, error) init func() (fs.StatFS, func() error, error)
once sync.Once mu sync.Mutex
loaded bool
refs int
fs fs.StatFS fs fs.StatFS
closeFn func() error closeFn func() error
err error err error
} }
func (m *memoizedPolicyFS) get() (fs.StatFS, error) { func (m *memoizedPolicyFS) get() (fs.StatFS, error) {
m.once.Do(func() { m.mu.Lock()
if m.init == nil { defer m.mu.Unlock()
return if !m.loaded {
m.loaded = true
if m.init != nil {
m.fs, m.closeFn, m.err = m.init()
} }
m.fs, m.closeFn, m.err = m.init() }
})
if m.err != nil { if m.err != nil {
return nil, m.err return nil, m.err
} }
m.refs++
return m.fs, nil return m.fs, nil
} }
func (m *memoizedPolicyFS) close() error { func (m *memoizedPolicyFS) close() error {
if m.closeFn != nil { m.mu.Lock()
return m.closeFn() if m.refs > 0 {
m.refs--
}
if m.refs > 0 {
m.mu.Unlock()
return nil
}
closeFn := m.closeFn
m.fs = nil
m.closeFn = nil
m.err = nil
m.loaded = false
m.mu.Unlock()
if closeFn != nil {
return closeFn()
} }
return nil return nil
} }
+85
View File
@@ -0,0 +1,85 @@
package build
import (
"io/fs"
"testing"
"testing/fstest"
"github.com/stretchr/testify/require"
)
func TestMemoizedPolicyFSRefCountedClose(t *testing.T) {
var initCalls int
var closeCalls int
m := &memoizedPolicyFS{
init: func() (fs.StatFS, func() error, error) {
initCalls++
root := fstest.MapFS{
"policy.rego": &fstest.MapFile{Data: []byte("package docker\n")},
}
return root, func() error {
closeCalls++
return nil
}, nil
},
}
first, err := m.get()
require.NoError(t, err)
require.NotNil(t, first)
require.Equal(t, 1, initCalls)
second, err := m.get()
require.NoError(t, err)
require.NotNil(t, second)
require.Equal(t, 1, initCalls)
require.NoError(t, m.close())
require.Equal(t, 0, closeCalls)
third, err := m.get()
require.NoError(t, err)
require.NotNil(t, third)
require.Equal(t, 1, initCalls)
require.NoError(t, m.close())
require.Equal(t, 0, closeCalls)
require.NoError(t, m.close())
require.Equal(t, 1, closeCalls)
}
func TestMemoizedPolicyFSReinitializesAfterAllRefsClosed(t *testing.T) {
var initCalls int
var closeCalls int
m := &memoizedPolicyFS{
init: func() (fs.StatFS, func() error, error) {
initCalls++
root := fstest.MapFS{
"policy.rego": &fstest.MapFile{Data: []byte("package docker\n")},
}
return root, func() error {
closeCalls++
return nil
}, nil
},
}
first, err := m.get()
require.NoError(t, err)
require.NotNil(t, first)
require.Equal(t, 1, initCalls)
require.NoError(t, m.close())
require.Equal(t, 1, closeCalls)
second, err := m.get()
require.NoError(t, err)
require.NotNil(t, second)
require.Equal(t, 2, initCalls)
require.NoError(t, m.close())
require.Equal(t, 2, closeCalls)
}
+5 -3
View File
@@ -486,13 +486,15 @@ func (p *Policy) readFile(path string, limit int64) ([]byte, error) {
if p.opt.FS == nil { if p.opt.FS == nil {
return nil, errors.Errorf("no policy FS defined for reading context files") return nil, errors.Errorf("no policy FS defined for reading context files")
} }
fs, cf, err := p.opt.FS() root, closeFS, err := p.opt.FS()
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to get policy FS for reading context files") return nil, errors.Wrapf(err, "failed to get policy FS for reading context files")
} }
defer cf() if closeFS != nil {
defer closeFS()
}
f, err := fs.Open(path) f, err := root.Open(path)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed opening file %q", path) return nil, errors.Wrapf(err, "failed opening file %q", path)
} }