package storage
import (
+ "errors"
"fmt"
"io"
"io/fs"
type fileMmap struct {
m mmap.MMap
- writable bool
+ f *os.File
refs atomic.Int32
+ writable bool
}
func (me *fileMmap) dec() error {
if me.refs.Add(-1) == 0 {
- return me.m.Unmap()
+ return me.close()
}
return nil
}
+func (me *fileMmap) close() (err error) {
+ return errors.Join(me.m.Unmap(), me.f.Close())
+}
+
func (me *fileMmap) inc() {
panicif.LessThanOrEqual(me.refs.Add(1), 0)
}
if err != nil {
return
}
- defer f.Close()
mm, err := mmap.Map(f, mmap.RDONLY, 0)
if err != nil {
+ f.Close()
err = fmt.Errorf("mapping file: %w", err)
return
}
- v = m.addNewMmap(name, mm, false)
+ v = m.addNewMmap(name, mm, false, f)
return newMmapFile(v), nil
}
g.MustDelete(m.paths, name)
}
}
+ // TODO: A bunch of this can be done without holding the lock.
f, err := openFileExtra(name, os.O_RDWR)
if err != nil {
return
}
- defer f.Close()
+ closeFile := true
+ defer func() {
+ if closeFile {
+ f.Close()
+ }
+ }()
err = f.Truncate(size)
if err != nil {
err = fmt.Errorf("error truncating file: %w", err)
mm.Unmap()
return
}
- return newMmapFile(m.addNewMmap(name, mm, true)), nil
+ closeFile = false
+ return newMmapFile(m.addNewMmap(name, mm, true, f)), nil
}
func newMmapFile(f *fileMmap) *mmapSharedFileHandle {
return ret
}
-func (me *mmapFileIo) addNewMmap(name string, mm mmap.MMap, writable bool) *fileMmap {
+func (me *mmapFileIo) addNewMmap(name string, mm mmap.MMap, writable bool, f *os.File) *fileMmap {
v := &fileMmap{
m: mm,
+ f: f,
writable: writable,
}
// One for the store, one for the caller.
return
}
-func (me *mmapFileHandle) seekData(offset int64) (ret int64, err error) {
- me.pos = offset
- ret = offset
+func (me *mmapFileHandle) seekDataOrEof(offset int64) (ret int64, err error) {
+ // This should be fine as it's an atomic operation, on a shared file handle, so nobody will be
+ // relying non-atomic operations on the file. TODO: Does this require msync first so we don't
+ // skip our own writes.
+ ret, err = seekData(me.shared.f.f, offset)
+ if err == nil {
+ me.pos = ret
+ } else if err == io.EOF {
+ err = nil
+ ret = int64(len(me.shared.f.m))
+ me.pos = ret
+ } else {
+ ret = me.pos
+ }
return
}
panicif.GreaterThan(extent.End(), file.FileInfo.Length)
extentRemaining := extent.Length
var dataOffset int64
- dataOffset, err = f.seekData(extent.Start)
- if err == io.EOF {
+ dataOffset, err = f.seekDataOrEof(extent.Start)
+ if err != nil {
+ err = fmt.Errorf("seeking to start of extent: %w", err)
+ return
+ }
+ if dataOffset < extent.Start {
+ // File is too short.
return
}
- panicif.Err(err)
- panicif.LessThan(dataOffset, extent.Start)
if dataOffset > extent.Start {
// Write zeroes until the end of the hole we're in.
var n1 int64