10 "golang.org/x/sys/unix"
13 // Syncer makes the writes of a chat durable by group commit. Each write goes to
14 // the operating system at once, so a crash of the process loses nothing. After
15 // each write, Written starts a background sync of the file system of the chat,
16 // unless one runs already. Then the next sync starts when the running one ends.
17 // One sync makes every write before its start durable, so a sync covers many
18 // writes, and there is never more than one sync at a time. A power loss loses
19 // at most the writes after the start of the last finished sync.
21 // A failed sync can mean that the kernel dropped written data, so the error
22 // stays, and each later Flush returns it.
32 workers sync.WaitGroup
35 // openSyncer returns the syncer of the file system that holds dir. It syncs
36 // with syncfs, which writes back only that file system.
37 func openSyncer(dir string) (*Syncer, error) {
38 file, err := os.Open(dir)
40 return nil, fmt.Errorf("open chat directory: %w", err)
43 s := newSyncer(func() error {
44 return unix.Syncfs(int(file.Fd()))
51 // newSyncer returns a syncer that makes the writes durable with sync.
52 func newSyncer(sync func() error) *Syncer {
55 done: make(chan struct{}),
59 // Written tells the syncer that a write went to the operating system, and
60 // starts a sync when none runs.
61 func (s *Syncer) Written() {
63 defer s.mutex.Unlock()
69 // Flush returns when every write before the call is durable.
70 func (s *Syncer) Flush(ctx context.Context) error {
79 reached := s.durable >= target
100 // Close makes every write durable, waits for the syncs, and closes the
102 func (s *Syncer) Close() error {
103 err := s.Flush(context.Background())
107 err = errors.Join(err, s.file.Close())
114 // start starts a sync of the writes so far when none runs and a write is not
115 // durable. The caller holds the mutex.
116 func (s *Syncer) start() {
117 if s.running || s.durable == s.written || s.failure != nil {
127 // run syncs once, so the writes up to target become durable, and starts the
128 // next sync when writes came during this one.
129 func (s *Syncer) run(target uint64) {
130 defer s.workers.Done()
135 defer s.mutex.Unlock()
139 s.failure = fmt.Errorf("sync chat files: %w", err)
146 s.done = make(chan struct{})