X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/522d7fd117285e4e4ce3ea5ec9b5a1722521b681..9301f77a5709fe7daea3a71f8dd072c6cd7df4fe:/syncer.go diff --git a/syncer.go b/syncer.go index 3da6d7a..297405b 100644 --- a/syncer.go +++ b/syncer.go @@ -10,22 +10,6 @@ import ( "golang.org/x/sys/unix" ) -// openSyncer returns the syncer of the file system that holds dir. It syncs -// with syncfs, which writes back only that file system. -func openSyncer(dir string) (*Syncer, error) { - file, err := os.Open(dir) - if err != nil { - return nil, fmt.Errorf("open chat directory: %w", err) - } - - s := newSyncer(func() error { - return unix.Syncfs(int(file.Fd())) - }) - s.file = file - - return s, nil -} - // Syncer makes the writes of a chat durable by group commit. Each write goes to // the operating system at once, so a crash of the process loses nothing. After // each write, Written starts a background sync of the file system of the chat, @@ -48,59 +32,39 @@ type Syncer struct { workers sync.WaitGroup } -// Written tells the syncer that a write went to the operating system, and -// starts a sync when none runs. -func (s *Syncer) Written() { - s.mutex.Lock() - defer s.mutex.Unlock() - - s.written++ - s.start() -} - -// Flush returns when every write before the call is durable. -func (s *Syncer) Flush(ctx context.Context) error { - s.mutex.Lock() - - target := s.written - s.mutex.Unlock() - - for { - s.mutex.Lock() +// openSyncer returns the syncer of the file system that holds dir. It syncs +// with syncfs, which writes back only that file system. +func openSyncer(dir string) (*Syncer, error) { + file, err := os.Open(dir) + if err != nil { + return nil, fmt.Errorf("open chat directory: %w", err) + } - reached := s.durable >= target - failure := s.failure - done := s.done - s.mutex.Unlock() + s := newSyncer(func() error { + return unix.Syncfs(int(file.Fd())) + }) - if reached { - return nil - } + s.file = file - if failure != nil { - return failure - } + return s, nil +} - select { - case <-done: - case <-ctx.Done(): - return ctx.Err() - } +// newSyncer returns a syncer that makes the writes durable with sync. +func newSyncer(sync func() error) *Syncer { + return &Syncer{ + sync: sync, + done: make(chan struct{}), } } -// Close makes every write durable, waits for the syncs, and closes the -// directory. -func (s *Syncer) Close() error { - err := s.Flush(context.Background()) - s.workers.Wait() - - if s.file != nil { - err = errors.Join(err, s.file.Close()) - s.file = nil - } +// Written tells the syncer that a write went to the operating system, and +// starts a sync when none runs. +func (s *Syncer) Written() { + s.mutex.Lock() + defer s.mutex.Unlock() - return err + s.written++ + s.start() } // start starts a sync of the writes so far when none runs and a write is not @@ -139,10 +103,47 @@ func (s *Syncer) run(target uint64) { s.start() } -// newSyncer returns a syncer that makes the writes durable with sync. -func newSyncer(sync func() error) *Syncer { - return &Syncer{ - sync: sync, - done: make(chan struct{}), +// Close makes every write durable, waits for the syncs, and closes the +// directory. +func (s *Syncer) Close() error { + err := s.Flush(context.Background()) + s.workers.Wait() + + if s.file != nil { + err = errors.Join(err, s.file.Close()) + s.file = nil + } + + return err +} + +// Flush returns when every write before the call is durable. +func (s *Syncer) Flush(ctx context.Context) error { + s.mutex.Lock() + + target := s.written + s.mutex.Unlock() + + for { + s.mutex.Lock() + + reached := s.durable >= target + failure := s.failure + done := s.done + s.mutex.Unlock() + + if reached { + return nil + } + + if failure != nil { + return fmt.Errorf("flush durable writes: %w", failure) + } + + select { + case <-done: + case <-ctx.Done(): + return ctx.Err() + } } }