build: ref-count policy FS lifecycle

Make memoized policy FS ref-counted so repeated get()/close() pairs don't
prematurely close shared handles.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2026-02-26 18:48:38 -08:00
parent f35e45c307
commit 9ef416b258
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 {
init func() (fs.StatFS, func() error, error)
once sync.Once
mu sync.Mutex
loaded bool
refs int
fs fs.StatFS
closeFn func() error
err error
}
func (m *memoizedPolicyFS) get() (fs.StatFS, error) {
m.once.Do(func() {
if m.init == nil {
return
m.mu.Lock()
defer m.mu.Unlock()
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 {
return nil, m.err
}
m.refs++
return m.fs, nil
}
func (m *memoizedPolicyFS) close() error {
if m.closeFn != nil {
return m.closeFn()
m.mu.Lock()
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
}
+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)
}