From f953ece6f25beac0eaa9526b97a43a37736e3a2c Mon Sep 17 00:00:00 2001 From: Mikkel Krautz Date: Sun, 18 Nov 2012 17:40:18 +0100 Subject: [PATCH] grumble, pkg/freezer: more stable freezing on Windows. --- freeze.go | 90 +++++++++++++++++-------------------- freeze_unix.go | 49 ++++++++++++++++++++ freeze_windows.go | 59 ++++++++++++++++++++++++ pkg/freezer/freezer_test.go | 27 ++++++++--- pkg/freezer/walker.go | 14 ------ 5 files changed, 171 insertions(+), 68 deletions(-) create mode 100644 freeze_unix.go create mode 100644 freeze_windows.go diff --git a/freeze.go b/freeze.go index 64a5942..fd08ef3 100644 --- a/freeze.go +++ b/freeze.go @@ -20,69 +20,48 @@ import ( "time" ) -// Freeze a server to disk, and re-open the log, if needed. +// Freeze a server to disk and closes the log file. // This must be called from within the Server's synchronous handler. -func (server *Server) FreezeToFile() (err error) { - // Close the log file, if it's open - if server.freezelog != nil { - err = server.freezelog.Close() +func (server *Server) FreezeToFile() error { + // See freeeze_{windows,unix}.go for real implementations. + err := server.freezeToFile() + if err != nil { + return err + } + + if server.running { + // Re-open the freeze log. + err = server.openFreezeLog() if err != nil { return err } } - // Make sure the whole server is synced to disk - fs, err := server.Freeze() - if err != nil { - return err - } - f, err := ioutil.TempFile(filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10)), ".main.fz_") - if err != nil { - return err - } - buf, err := proto.Marshal(fs) - if err != nil { - return err - } - _, err = f.Write(buf) - if err != nil { - return err - } - err = f.Sync() - if err != nil { - return err - } - err = f.Close() - if err != nil { - return err - } - err = os.Rename(f.Name(), filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10), "main.fz")) - if err != nil { - return err - } - - // Re-open a new log file - err = server.openFreezeLog() - if err != nil { - return err - } - return nil } -// Open a new freeze log -func (server *Server) openFreezeLog() (err error) { +// Open a new freeze log. +func (server *Server) openFreezeLog() error { + if server.freezelog != nil { + err := server.freezelog.Close() + if err != nil { + return err + } + } + logfn := filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10), "log.fz") - err = os.Remove(logfn) + err := os.Remove(logfn) if os.IsNotExist(err) { - // OK. File does not exist... + // fallthrough } else if err != nil { return err } + server.freezelog, err = freezer.NewLogFile(logfn) if err != nil { return err } + return nil } @@ -406,10 +385,20 @@ func NewServerFromFrozen(name string) (s *Server, err error) { path := filepath.Join(Args.DataDir, "servers", name) mainFile := filepath.Join(path, "main.fz") - logFile := filepath.Join(path, "log.fz") + backupFile := filepath.Join(path, "backup.fz") + logFn := filepath.Join(path, "log.fz") r, err := os.Open(mainFile) - if err != nil { + if os.IsNotExist(err) { + err = os.Rename(backupFile, mainFile) + if err != nil { + return nil, err + } + r, err = os.Open(mainFile) + if err != nil { + return nil, err + } + } else if err != nil { return nil, err } defer r.Close() @@ -504,7 +493,8 @@ func NewServerFromFrozen(name string) (s *Server, err error) { } // Attempt to walk the stored log file - walker, err := freezer.NewFileWalker(logFile) + logFile, err := os.Open(logFn) + walker, err := freezer.NewReaderWalker(logFile) if err != nil { return nil, err } @@ -512,6 +502,10 @@ func NewServerFromFrozen(name string) (s *Server, err error) { for { values, err := walker.Next() if err == io.EOF { + err = logFile.Close() + if err != nil { + return nil, err + } break } else if err != nil { return nil, err diff --git a/freeze_unix.go b/freeze_unix.go new file mode 100644 index 0000000..eaea0ec --- /dev/null +++ b/freeze_unix.go @@ -0,0 +1,49 @@ +// Copyright (c) 2012 The Grumble Authors +// The use of this source code is goverened by a BSD-style +// license that can be found in the LICENSE-file. + +// +build !windows + +package main + +func (server *Server) freezeToFile() (err error) { + // Close the log file, if it's open + if server.freezelog != nil { + err = server.freezelog.Close() + if err != nil { + return err + } + } + + // Make sure the whole server is synced to disk + fs, err := server.Freeze() + if err != nil { + return err + } + f, err := ioutil.TempFile(filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10)), ".main.fz_") + if err != nil { + return err + } + buf, err := proto.Marshal(fs) + if err != nil { + return err + } + _, err = f.Write(buf) + if err != nil { + return err + } + err = f.Sync() + if err != nil { + return err + } + err = f.Close() + if err != nil { + return err + } + err = os.Rename(f.Name(), filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10), "main.fz")) + if err != nil { + return err + } + + return nil +} \ No newline at end of file diff --git a/freeze_windows.go b/freeze_windows.go new file mode 100644 index 0000000..34b2902 --- /dev/null +++ b/freeze_windows.go @@ -0,0 +1,59 @@ +// Copyright (c) 2012 The Grumble Authors +// The use of this source code is goverened by a BSD-style +// license that can be found in the LICENSE-file. + +package main + +import ( + "code.google.com/p/goprotobuf/proto" + "mumbleapp.com/grumble/pkg/replacefile" + "io/ioutil" + "path/filepath" + "strconv" +) + +func (server *Server) freezeToFile() (err error) { + // Close the log file, if it's open + if server.freezelog != nil { + err = server.freezelog.Close() + if err != nil { + return err + } + } + + // Make sure the whole server is synced to disk + fs, err := server.Freeze() + if err != nil { + return err + } + f, err := ioutil.TempFile(filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10)), ".main.fz_") + if err != nil { + return err + } + buf, err := proto.Marshal(fs) + if err != nil { + return err + } + _, err = f.Write(buf) + if err != nil { + return err + } + err = f.Sync() + if err != nil { + return err + } + err = f.Close() + if err != nil { + return err + } + + src := f.Name() + dst := filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10), "main.fz") + backup := filepath.Join(Args.DataDir, "servers", strconv.FormatInt(server.Id, 10), "backup.fz") + err = replacefile.ReplaceFile(dst, src, backup, replacefile.Flag(0)) + if err != nil { + return err + } + + return nil +} \ No newline at end of file diff --git a/pkg/freezer/freezer_test.go b/pkg/freezer/freezer_test.go index cfcde1d..c156ddc 100644 --- a/pkg/freezer/freezer_test.go +++ b/pkg/freezer/freezer_test.go @@ -15,7 +15,7 @@ import ( "testing" ) -var testValues []interface{} = []interface{}{ +var testValues []proto.Message = []proto.Message{ &ConfigKeyValuePair{Key: proto.String("Foo")}, &BanList{Bans: []*Ban{&Ban{Mask: proto.Uint32(32)}}}, &User{Id: proto.Uint32(0), Name: proto.String("SuperUser")}, @@ -97,18 +97,26 @@ func TestLogging(t *testing.T) { t.Error(err) return } - defer l.Close() defer os.Remove("logging.log") for _, val := range testValues { err = l.Put(val) if err != nil { - t.Error(err) - return + t.Fatal(err) } } - walker, err := NewFileWalker("logging.log") + err = l.Close() + if err != nil { + t.Fatal(err) + } + + f, err := os.Open("logging.log") + if err != nil { + t.Fatal(err) + } + + walker, err := NewReaderWalker(f) if err != nil { t.Error(err) return @@ -118,6 +126,10 @@ func TestLogging(t *testing.T) { for { entries, err := walker.Next() if err == io.EOF { + err = f.Close() + if err != nil { + t.Fatal(err) + } break } else if err != nil { t.Error(err) @@ -127,7 +139,10 @@ func TestLogging(t *testing.T) { t.Error("> 1 entry in log tx") return } - val := entries[0] + val, ok := entries[0].(proto.Message) + if !ok { + t.Fatal("val does not implement proto.Message") + } if !proto.Equal(val, testValues[i]) { t.Error("proto message mismatch") } diff --git a/pkg/freezer/walker.go b/pkg/freezer/walker.go index 53fdc52..54c47dc 100644 --- a/pkg/freezer/walker.go +++ b/pkg/freezer/walker.go @@ -11,7 +11,6 @@ import ( "hash/crc32" "io" "math" - "os" ) // Checks whether the error err is an EOF @@ -79,19 +78,6 @@ func (txr *txReader) Consumed() int { return txr.consumed } -// Create a new Walker that iterates over the entries of the given log file. -func NewFileWalker(fn string) (walker *Walker, err error) { - f, err := os.Open(fn) - if err != nil { - return nil, err - } - - walker = new(Walker) - walker.r = f - - return walker, nil -} - // Create a new Walker that iterates over the log entries of a given Reader. func NewReaderWalker(r io.Reader) (walker *Walker, err error) { walker = new(Walker)