storage/filesystem: add R/W locking

This commit adds read/write locking for individual files, so that
concurrent requests (e.g. to read and write the same file) cannot
interfere with one another.

The locking is not very fine-grained at the moment, and can probably be
improved upon. But it does ensure consistency.
This commit is contained in:
Conrad Hoffmann 2024-11-07 12:14:45 +01:00
parent 7969df1a38
commit 6eeea854be
3 changed files with 88 additions and 2 deletions

View file

@ -69,6 +69,7 @@ func vcardPropFilter(card vcard.Card, props []string) vcard.Card {
}
func vcardFromFile(path string, propFilter []string) (vcard.Card, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
@ -103,6 +104,10 @@ func (b *filesystemBackend) loadAllAddressObjects(ctx context.Context, urlPath s
return nil
}
// TODO use single file read for data & etag
b.RLock(filename)
defer b.RUnlock(filename)
card, err := vcardFromFile(filename, propFilter)
if err != nil {
return err
@ -141,7 +146,12 @@ func (b *filesystemBackend) writeAddressBook(ctx context.Context, ab *carddav.Ad
if err != nil {
return err
}
return os.WriteFile(path.Join(localPath, addressBookFileName), blob, 0644)
filename := path.Join(localPath, addressBookFileName)
b.Lock(filename)
defer b.Unlock(filename)
err = os.WriteFile(filename, blob, 0644)
if err != nil {
return fmt.Errorf("error writing address book: %s", err.Error())
}
@ -208,6 +218,9 @@ func (b *filesystemBackend) ListAddressBooks(ctx context.Context) ([]carddav.Add
}
abPath := path.Join(filename, addressBookFileName)
b.RLock(abPath)
defer b.RUnlock(abPath)
data, err := os.ReadFile(abPath)
if err != nil {
if os.IsNotExist(err) {
@ -250,6 +263,9 @@ func (b *filesystemBackend) GetAddressBook(ctx context.Context, urlPath string)
log.Debug().Str("path", localPath).Msg("loading addressbook")
b.RLock(localPath)
defer b.RUnlock(localPath)
data, err := os.ReadFile(localPath)
if err != nil {
if os.IsNotExist(err) {
@ -303,6 +319,9 @@ func (b *filesystemBackend) GetAddressObject(ctx context.Context, objPath string
return nil, err
}
b.RLock(localPath)
defer b.RUnlock(localPath)
info, err := os.Stat(localPath)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
@ -381,6 +400,9 @@ func (b *filesystemBackend) PutAddressObject(ctx context.Context, objPath string
return nil, err
}
b.Lock(localPath)
defer b.Unlock(localPath)
flags := os.O_RDWR | os.O_CREATE | os.O_TRUNC
// TODO handle IfNoneMatch == ETag
if opts.IfNoneMatch.IsWildcard() {
@ -444,6 +466,10 @@ func (b *filesystemBackend) DeleteAddressObject(ctx context.Context, path string
if err != nil {
return err
}
b.Lock(localPath)
defer b.Unlock(localPath)
err = os.Remove(localPath)
if err != nil {
return err