X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/2532718d5288e1a2586fe5716cc41bb2a0210e16..bd7da3557dc7a17020931f5a49ed5007fa81a645:/syncer.go diff --git a/syncer.go b/syncer.go index 6b69f86..297405b 100644 --- a/syncer.go +++ b/syncer.go @@ -10,17 +10,17 @@ import ( "golang.org/x/sys/unix" ) -// syncer makes the writes of a chat durable by group commit. Each write goes to +// 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, // unless one runs already. Then the next sync starts when the running one ends. // One sync makes every write before its start durable, so a sync covers many -// writes, and there is never more than one sync at a time. A power loss loses at -// most the writes after the start of the last finished sync. +// writes, and there is never more than one sync at a time. A power loss loses +// at most the writes after the start of the last finished sync. // // A failed sync can mean that the kernel dropped written data, so the error // stays, and each later Flush returns it. -type syncer struct { +type Syncer struct { mutex sync.Mutex sync func() error file *os.File @@ -34,7 +34,7 @@ type syncer struct { // 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) { +func openSyncer(dir string) (*Syncer, error) { file, err := os.Open(dir) if err != nil { return nil, fmt.Errorf("open chat directory: %w", err) @@ -43,14 +43,15 @@ func openSyncer(dir string) (*syncer, error) { s := newSyncer(func() error { return unix.Syncfs(int(file.Fd())) }) + s.file = file return s, nil } // newSyncer returns a syncer that makes the writes durable with sync. -func newSyncer(sync func() error) *syncer { - return &syncer{ +func newSyncer(sync func() error) *Syncer { + return &Syncer{ sync: sync, done: make(chan struct{}), } @@ -58,7 +59,7 @@ func newSyncer(sync func() error) *syncer { // Written tells the syncer that a write went to the operating system, and // starts a sync when none runs. -func (s *syncer) Written() { +func (s *Syncer) Written() { s.mutex.Lock() defer s.mutex.Unlock() @@ -68,19 +69,20 @@ func (s *syncer) Written() { // start starts a sync of the writes so far when none runs and a write is not // durable. The caller holds the mutex. -func (s *syncer) start() { +func (s *Syncer) start() { if s.running || s.durable == s.written || s.failure != nil { return } s.running = true s.workers.Add(1) + go s.run(s.written) } // run syncs once, so the writes up to target become durable, and starts the // next sync when writes came during this one. -func (s *syncer) run(target uint64) { +func (s *Syncer) run(target uint64) { defer s.workers.Done() err := s.sync() @@ -96,18 +98,35 @@ func (s *syncer) run(target uint64) { } close(s.done) + s.done = make(chan struct{}) s.start() } +// 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 { +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 @@ -118,7 +137,7 @@ func (s *syncer) Flush(ctx context.Context) error { } if failure != nil { - return failure + return fmt.Errorf("flush durable writes: %w", failure) } select { @@ -128,16 +147,3 @@ func (s *syncer) Flush(ctx context.Context) error { } } } - -// 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 -}