From 2c30300756a2389acc06bb777cf46f6f57feff96 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Mon, 30 Mar 2026 08:30:22 -0300 Subject: [PATCH] mmm: use flock instead of file presence for locking the database. --- eventstore/mmm/mmmm.go | 48 ++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/eventstore/mmm/mmmm.go b/eventstore/mmm/mmmm.go index b34a6f0..132cb57 100644 --- a/eventstore/mmm/mmmm.go +++ b/eventstore/mmm/mmmm.go @@ -35,6 +35,7 @@ type MultiMmapManager struct { mmapfEnd uint64 writeMutex sync.Mutex + lockfile *os.File lmdbEnv *lmdb.Env stuff lmdb.DBI @@ -55,28 +56,38 @@ const ( maxuint32 = 4294967295 ) -func (b *MultiMmapManager) Init() error { +func (b *MultiMmapManager) Init() (err error) { if b.Logger == nil { nopLogger := zerolog.Nop() b.Logger = &nopLogger } + defer func() { + if err != nil { + b.releaseLock() + } + }() + // create directory if it doesn't exist dbpath := filepath.Join(b.Dir, "mmmm") if err := os.MkdirAll(dbpath, 0755); err != nil { return fmt.Errorf("failed to create directory %s: %w", dbpath, err) } - if !b.ReadOnly { - // create lockfile to prevent multiple instances - lockfilePath := filepath.Join(b.Dir, "mmmm.lock") - if _, err := os.OpenFile(lockfilePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644); err != nil { - if os.IsExist(err) { - return fmt.Errorf("database at %s is already in use by another instance", b.Dir) - } - return fmt.Errorf("failed to create lockfile %s: %w", lockfilePath, err) - } + // lock database directory to prevent multiple instances + lockfilePath := filepath.Join(b.Dir, "mmmm.lock") + lockfile, err := os.OpenFile(lockfilePath, os.O_CREATE|os.O_RDWR, 0644) + if err != nil { + return fmt.Errorf("failed to open lockfile %s: %w", lockfilePath, err) } + if err := syscall.Flock(int(lockfile.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil { + lockfile.Close() + if errors.Is(err, syscall.EWOULDBLOCK) || errors.Is(err, syscall.EAGAIN) { + return fmt.Errorf("database at %s is already in use by another instance", b.Dir) + } + return fmt.Errorf("failed to lock database at %s: %w", b.Dir, err) + } + b.lockfile = lockfile // open a huge mmapped file b.mmapfPath = filepath.Join(b.Dir, "events") @@ -365,6 +376,19 @@ func (b *MultiMmapManager) getNextAvailableLayerId(txn *lmdb.Txn) (uint16, error return id, nil } +func (b *MultiMmapManager) releaseLock() { + if b.lockfile == nil { + return + } + + _ = syscall.Flock(int(b.lockfile.Fd()), syscall.LOCK_UN) + _ = b.lockfile.Close() + b.lockfile = nil + + lockfilePath := filepath.Join(b.Dir, "mmmm.lock") + _ = os.Remove(lockfilePath) +} + func (b *MultiMmapManager) Close() { b.lmdbEnv.Close() for _, il := range b.layers { @@ -373,7 +397,5 @@ func (b *MultiMmapManager) Close() { syscall.Munmap(b.mmapf) - // remove lockfile - lockfilePath := filepath.Join(b.Dir, "mmmm.lock") - os.Remove(lockfilePath) + b.releaseLock() }