Fix dbfs error handling (#36844)

Add tests for opening non-existing files.
This commit is contained in:
wxiaoguang
2026-03-07 00:28:46 +08:00
committed by GitHub
parent f3bdcc58af
commit 2ce71629c3
5 changed files with 91 additions and 49 deletions

View File

@@ -75,7 +75,7 @@ func (f *file) readAt(fileMeta *dbfsMeta, offset int64, p []byte) (n int, err er
}
func (f *file) Read(p []byte) (n int, err error) {
if f.metaID == 0 || !f.allowRead {
if !f.allowRead {
return 0, os.ErrInvalid
}
@@ -89,7 +89,7 @@ func (f *file) Read(p []byte) (n int, err error) {
}
func (f *file) Write(p []byte) (n int, err error) {
if f.metaID == 0 || !f.allowWrite {
if !f.allowWrite {
return 0, os.ErrInvalid
}
@@ -184,10 +184,6 @@ func (f *file) Close() error {
}
func (f *file) Stat() (os.FileInfo, error) {
if f.metaID == 0 {
return nil, os.ErrInvalid
}
fileMeta, err := findFileMetaByID(f.ctx, f.metaID)
if err != nil {
return nil, err
@@ -232,15 +228,17 @@ func (f *file) open(flag int) (err error) {
if f.metaID != 0 {
return os.ErrExist
}
} else {
// create a new file if none exists.
if f.metaID == 0 {
if err = f.createEmpty(); err != nil {
return err
}
}
// create a new file if not exists.
if f.metaID == 0 {
if err = f.createEmpty(); err != nil {
return err
}
}
}
if f.metaID == 0 {
return os.ErrNotExist
}
if flag&os.O_TRUNC != 0 {
if err = f.truncate(); err != nil {
return err
@@ -252,7 +250,7 @@ func (f *file) open(flag int) (err error) {
}
}
return nil
}
} // end if: allowWrite
// read only mode
if f.metaID == 0 {
@@ -322,9 +320,6 @@ func (f *file) delete() error {
}
func (f *file) size() (int64, error) {
if f.metaID == 0 {
return 0, os.ErrNotExist
}
fileMeta, err := findFileMetaByID(f.ctx, f.metaID)
if err != nil {
return 0, err
@@ -339,7 +334,7 @@ func findFileMetaByID(ctx context.Context, metaID int64) (*dbfsMeta, error) {
} else if ok {
return &fileMeta, nil
}
return nil, nil //nolint:nilnil // return nil to indicate that the object does not exist
return nil, os.ErrNotExist
}
func buildPath(path string) string {

View File

@@ -40,6 +40,9 @@ The DBFS solution:
* In the future, when Gitea action needs to limit the log size (other CI/CD services also do so), it's easier to calculate the log file size.
* Even sometimes the UI needs to render the tailing lines, the tailing lines can be found be counting the "\n" from the end of the file by seek.
The seeking and finding is not the fastest way, but it's still acceptable and won't affect the performance too much.
Limitations of the DBFS solution:
* Not fully POSIX-compliant, some behaviors may be different from the real filesystem, especially for concurrent read/write
*/
type dbfsMeta struct {

View File

@@ -9,19 +9,14 @@ import (
"os"
"testing"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func changeDefaultFileBlockSize(n int64) (restore func()) {
old := defaultFileBlockSize
defaultFileBlockSize = n
return func() {
defaultFileBlockSize = old
}
}
func TestDbfsBasic(t *testing.T) {
defer changeDefaultFileBlockSize(4)()
defer test.MockVariableValue(&defaultFileBlockSize, 4)()
// test basic write/read
f, err := OpenFile(t.Context(), "test.txt", os.O_RDWR|os.O_CREATE)
@@ -122,10 +117,55 @@ func TestDbfsBasic(t *testing.T) {
stat, err = f.Stat()
assert.NoError(t, err)
assert.EqualValues(t, 10, stat.Size())
t.Run("NonExisting", func(t *testing.T) {
f, err := OpenFile(t.Context(), "non-existing.txt", os.O_RDONLY)
assert.ErrorIs(t, err, os.ErrNotExist)
assert.Nil(t, f)
f, err = OpenFile(t.Context(), "non-existing.txt", os.O_WRONLY)
assert.ErrorIs(t, err, os.ErrNotExist)
assert.Nil(t, f)
f, err = OpenFile(t.Context(), "non-existing.txt", os.O_WRONLY|os.O_APPEND|os.O_TRUNC)
assert.ErrorIs(t, err, os.ErrNotExist)
assert.Nil(t, f)
})
t.Run("Existing", func(t *testing.T) {
assertFileContent := func(f File, expected string) {
_, err := f.Seek(0, io.SeekStart)
require.NoError(t, err)
buf, err := io.ReadAll(f)
require.NoError(t, err)
assert.Equal(t, expected, string(buf))
}
f, err := OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_CREATE)
require.NoError(t, err)
_, _ = f.Write([]byte("test"))
assertFileContent(f, "test")
assert.NoError(t, f.Close())
f, err = OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND)
require.NoError(t, err)
_, _ = f.Write([]byte("\nnew"))
assertFileContent(f, "test\nnew")
assert.NoError(t, f.Close())
f, err = OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_TRUNC)
require.NoError(t, err)
assertFileContent(f, "")
assert.NoError(t, f.Close())
f, err = OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_CREATE|os.O_EXCL)
assert.ErrorIs(t, err, os.ErrExist)
assert.Nil(t, f)
})
}
func TestDbfsReadWrite(t *testing.T) {
defer changeDefaultFileBlockSize(4)()
defer test.MockVariableValue(&defaultFileBlockSize, 4)()
f1, err := OpenFile(t.Context(), "test.log", os.O_RDWR|os.O_CREATE)
assert.NoError(t, err)
@@ -157,30 +197,32 @@ func TestDbfsReadWrite(t *testing.T) {
}
func TestDbfsSeekWrite(t *testing.T) {
defer changeDefaultFileBlockSize(4)()
defer test.MockVariableValue(&defaultFileBlockSize, 4)()
f, err := OpenFile(t.Context(), "test2.log", os.O_RDWR|os.O_CREATE)
assert.NoError(t, err)
defer f.Close()
// write something
fw, err := OpenFile(t.Context(), "test2.log", os.O_RDWR|os.O_CREATE)
require.NoError(t, err)
defer fw.Close()
n, err := f.Write([]byte("111"))
n, err := fw.Write([]byte("111"))
assert.NoError(t, err)
_, err = f.Seek(int64(n), io.SeekStart)
_, err = fw.Seek(int64(n), io.SeekStart)
assert.NoError(t, err)
_, err = f.Write([]byte("222"))
_, err = fw.Write([]byte("222"))
assert.NoError(t, err)
_, err = f.Seek(int64(n), io.SeekStart)
_, err = fw.Seek(int64(n), io.SeekStart)
assert.NoError(t, err)
_, err = f.Write([]byte("333"))
_, err = fw.Write([]byte("333"))
assert.NoError(t, err)
// then read it
fr, err := OpenFile(t.Context(), "test2.log", os.O_RDONLY)
assert.NoError(t, err)
defer f.Close()
require.NoError(t, err)
defer fr.Close()
buf, err := io.ReadAll(fr)
assert.NoError(t, err)