diff --git a/store/store.go b/store/store.go index a7bb0084..905dcae0 100644 --- a/store/store.go +++ b/store/store.go @@ -36,7 +36,7 @@ type Store struct { raftDir string raftBind string - mu sync.Mutex + mu sync.RWMutex// Sync access between queries and snapshots. raft *raft.Raft // The consensus mechanism dbConf *sql.Config @@ -156,6 +156,10 @@ func (s *Store) Execute(queries []string, tx bool) ([]*sql.Result, error) { // Query execute queries that return rows. func (s *Store) Query(queries []string, tx bool) ([]*sql.Rows, error) { + // Allow concurrent queries. + s.mu.RLock() + defer s.mu.RUnlock() + r, err := s.db.Query(queries, tx) return r, err } @@ -193,11 +197,16 @@ func (f *fsm) Apply(l *raft.Log) interface{} { } // Snapshot returns a snapshot of the database. The caller must ensure that -// no transaction is taking place during this call. +// no transaction is taking place during this call. Hashsicorp Raft guarantees +// that this function will not be called concurrently with Apply. // // http://sqlite.org/howtocorrupt.html states it is safe to do this // as long as no transaction is in progress. func (f *fsm) Snapshot() (raft.FSMSnapshot, error) { + // Ensure only one snapshot can take place at once, and block all queries. + f.mu.Lock() + defer f.mu.Unlock() + b, err := ioutil.ReadFile(f.dbPath) if err != nil { log.Printf("Failed to generate snapshot: %s", err.Error()) @@ -208,10 +217,25 @@ func (f *fsm) Snapshot() (raft.FSMSnapshot, error) { // Restore restores the database to a previous state. func (f *fsm) Restore(rc io.ReadCloser) error { - // Need to write bytes to SQLite file. - // Open it with desired DSN params. - // Implies the Open call above, should always blow away existing database, since - // it will be restored completely from log, or from log and remaining log entries. + if err := os.Remove(f.dbPath); err != nil && !os.IsNotExist(err) { + return err + } + + b, err := ioutil.ReadAll(rc) + if err != nil { + return err + } + + if err := ioutil.WriteFile(f.dbPath, b, 0660); err != nil { + return err + } + + db, err := sql.OpenWithConfiguration(f.dbPath, f.dbConf) + if err != nil { + return err + } + f.db = db + return nil }