"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,
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
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 failure
+ }
+
+ select {
+ case <-done:
+ case <-ctx.Done():
+ return ctx.Err()
+ }
}
}