]> git.giacomolli.uk - onechat/blobdiff - syncer.go
Check forced-disconnect test setup failures
[onechat] / syncer.go
index 6b69f860c59c18cb615d40b0ed37e53f39b299b2..297405bd6d0eccdcfd9bb771050b3ff67eb7e0cd 100644 (file)
--- 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
-}