fix(fs): fd must be closed after deleting, renaming

This commit is contained in:
hexxa 2021-09-18 23:07:54 +08:00 committed by Hexxa
parent ccfb9deb7e
commit dc80778255
3 changed files with 82 additions and 7 deletions

View file

@ -15,7 +15,7 @@ import (
"github.com/ihexxa/quickshare/src/idgen" "github.com/ihexxa/quickshare/src/idgen"
) )
var ErrTooManyOpens = errors.New("too many opened files") var ErrTooManyOpens = errors.New("too many opened files and failed to clean")
type LocalFS struct { type LocalFS struct {
root string root string
@ -148,6 +148,35 @@ func (fs *LocalFS) Sync() error {
return nil return nil
} }
func (fs *LocalFS) CloseFDs(filePaths map[string]bool) error {
fs.opensMtx.Lock()
defer fs.opensMtx.Unlock()
return fs.closeFDs(filePaths)
}
func (fs *LocalFS) closeFDs(filePaths map[string]bool) error {
var err error
for filePath := range filePaths {
info, ok := fs.opens[filePath]
if ok {
err = fs.closeInfo(filePath, info)
if err != nil {
return err
}
}
info, ok = fs.readers[filePath]
if ok {
err = fs.closeInfo(filePath, info)
if err != nil {
return err
}
}
}
return nil
}
func (fs *LocalFS) Close() error { func (fs *LocalFS) Close() error {
fs.opensMtx.Lock() fs.opensMtx.Lock()
defer fs.opensMtx.Unlock() defer fs.opensMtx.Unlock()
@ -171,7 +200,7 @@ func (fs *LocalFS) Create(path string) error {
if fs.isTooManyOpens() { if fs.isTooManyOpens() {
closed, err := fs.closeOpens(false, false, map[string]bool{}) closed, err := fs.closeOpens(false, false, map[string]bool{})
if err != nil || closed == 0 { if err != nil || closed == 0 {
return fmt.Errorf("too many opens and fail to clean(%d): %w", closed, err) return ErrTooManyOpens
} }
} }
@ -209,7 +238,12 @@ func (fs *LocalFS) Remove(entryPath string) error {
if err != nil { if err != nil {
return err return err
} }
return os.RemoveAll(fullpath)
err = os.RemoveAll(fullpath)
if err != nil {
return err
}
return fs.CloseFDs(map[string]bool{fullpath: true})
} }
func (fs *LocalFS) Rename(oldpath, newpath string) error { func (fs *LocalFS) Rename(oldpath, newpath string) error {
@ -231,7 +265,14 @@ func (fs *LocalFS) Rename(oldpath, newpath string) error {
_, err = os.Stat(fullNewPath) _, err = os.Stat(fullNewPath)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return os.Rename(fullOldPath, fullNewPath) err = os.Rename(fullOldPath, fullNewPath)
if err != nil {
return err
}
err = fs.CloseFDs(map[string]bool{fullOldPath: true})
if err != nil {
return err
}
} }
return err return err
} }
@ -253,7 +294,7 @@ func (fs *LocalFS) ReadAt(path string, b []byte, off int64) (int, error) {
if fs.isTooManyOpens() { if fs.isTooManyOpens() {
closed, err := fs.closeOpens(false, false, map[string]bool{}) closed, err := fs.closeOpens(false, false, map[string]bool{})
if err != nil || closed == 0 { if err != nil || closed == 0 {
return nil, fmt.Errorf("too many opens and fail to clean (%d): %w", closed, err) return nil, ErrTooManyOpens
} }
} }
@ -304,7 +345,7 @@ func (fs *LocalFS) WriteAt(path string, b []byte, off int64) (int, error) {
closed, err := fs.closeOpens(false, false, map[string]bool{}) closed, err := fs.closeOpens(false, false, map[string]bool{})
if err != nil || closed == 0 { if err != nil || closed == 0 {
// TODO: return Eagain and make client retry later // TODO: return Eagain and make client retry later
return nil, fmt.Errorf("too many opens and fail to clean (%d): %w", closed, err) return nil, ErrTooManyOpens
} }
} }
@ -367,7 +408,7 @@ func (fs *LocalFS) GetFileReader(path string) (fs.ReadCloseSeeker, uint64, error
if fs.isTooManyOpens() { if fs.isTooManyOpens() {
closed, err := fs.closeOpens(false, false, map[string]bool{}) closed, err := fs.closeOpens(false, false, map[string]bool{})
if err != nil || closed == 0 { if err != nil || closed == 0 {
return nil, 0, fmt.Errorf("too many opens and fail to clean (%d): %w", closed, err) return nil, 0, ErrTooManyOpens
} }
} }

View file

@ -157,6 +157,7 @@ func (h *FileHandlers) Create(c *gin.Context) {
err = h.deps.FS().Create(tmpFilePath) err = h.deps.FS().Create(tmpFilePath)
if err != nil { if err != nil {
fmt.Println("\nexist,", err, tmpFilePath)
if os.IsExist(err) { if os.IsExist(err) {
// avoid adding file size more than once // avoid adding file size more than once
err = h.deps.Users().SetUsed(userIDInt, false, req.FileSize) err = h.deps.Users().SetUsed(userIDInt, false, req.FileSize)

View file

@ -155,6 +155,39 @@ func TestFileHandlers(t *testing.T) {
} }
}) })
t.Run("test: Upload-Delete-Upload: fd are closed correctly", func(t *testing.T) {
files := map[string]string{
"qs/files/close_Fd/dup_file1": "12345678",
}
for i := 0; i < 2; i++ {
for filePath, content := range files {
assertUploadOK(t, filePath, content, addr, token)
// file operation(deleting tmp file) may be async
// so creating tmp file in the 2nd time may conflict with the first time if it is not deleted yet
// checking file until it is deleted
// TODO: use fs.Stat() to avoid flaky testing...
time.Sleep(1000)
err = fs.Close()
if err != nil {
t.Fatal(err)
}
res, _, errs := cl.Delete(filePath)
if len(errs) > 0 {
t.Fatal(errs)
} else if res.StatusCode != 200 {
t.Fatal(res.StatusCode)
}
// tmpFile fd is closed, so follow must succeed
assertUploadOK(t, filePath, content, addr, token)
}
}
})
t.Run("test files APIs: Create-UploadChunk-UploadStatus-Metadata-Delete", func(t *testing.T) { t.Run("test files APIs: Create-UploadChunk-UploadStatus-Metadata-Delete", func(t *testing.T) {
for filePath, content := range map[string]string{ for filePath, content := range map[string]string{
"qs/files/path1/f1.md": "1111 1111 1111 1111", "qs/files/path1/f1.md": "1111 1111 1111 1111",