diff --git a/ipn/store.go b/ipn/store.go index 2034ae09a..0370ab647 100644 --- a/ipn/store.go +++ b/ipn/store.go @@ -89,8 +89,9 @@ func CurrentProfileKey(userID string) StateKey { // StateStore persists state, and produces it back on request. // Implementations of StateStore are expected to be safe for concurrent use. type StateStore interface { - // ReadState returns the bytes associated with ID. Returns (nil, - // ErrStateNotExist) if the ID doesn't have associated state. + // ReadState returns the bytes associated with ID. + // It returns (nil, ErrStateNotExist) if the ID doesn't have associated state. + // The returned value must not be mutated. ReadState(id StateKey) ([]byte, error) // WriteState saves bs as the state associated with ID. // diff --git a/ipn/store/stores.go b/ipn/store/stores.go index bf175da41..b76303087 100644 --- a/ipn/store/stores.go +++ b/ipn/store/stores.go @@ -208,18 +208,24 @@ func (s *FileStore) ReadState(id ipn.StateKey) ([]byte, error) { } // WriteState implements the StateStore interface. -func (s *FileStore) WriteState(id ipn.StateKey, bs []byte) error { +func (s *FileStore) WriteState(id ipn.StateKey, bs []byte) (err error) { s.mu.Lock() defer s.mu.Unlock() - if bytes.Equal(s.cache[id], bs) { + bs0 := s.cache[id] + if bytes.Equal(bs0, bs) { return nil } + defer func() { + if err != nil { + s.cache[id] = bs0 + } + }() s.cache[id] = bytes.Clone(bs) - bs, err := json.MarshalIndent(s.cache, "", " ") + b, err := json.MarshalIndent(s.cache, "", " ") if err != nil { return err } - return atomicfile.WriteFile(s.path, bs, 0600) + return atomicfile.WriteFile(s.path, b, 0600) } func (s *FileStore) All() iter.Seq2[ipn.StateKey, []byte] {