From 2e424ee663147cc9e731413641d185aa81dbefa7 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 11 Aug 2016 00:04:07 -0400 Subject: [PATCH 1/2] service: Reset() for restarts --- service.go | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/service.go b/service.go index 0ea69e23..86ef20ea 100644 --- a/service.go +++ b/service.go @@ -1,12 +1,13 @@ /* Classical-inheritance-style service declarations. -Services can be started, then stopped. +Services can be started, then stopped, then optionally restarted. Users can override the OnStart/OnStop methods. -These methods are guaranteed to be called at most once. +By default, these methods are guaranteed to be called at most once. +A call to Reset will panic, unless OnReset is overwritten, allowing OnStart/OnStop to be called again. Caller must ensure that Start() and Stop() are not called concurrently. It is ok to call Stop() without calling Start() first. -Services cannot be re-started unless otherwise documented. +Services cannot be re-started unless OnReset is overwritten to allow it. Typical usage: @@ -51,6 +52,9 @@ type Service interface { Stop() bool OnStop() + Reset() (bool, error) + OnReset() error + IsRunning() bool String() string @@ -119,6 +123,29 @@ func (bs *BaseService) Stop() bool { // Implements Service func (bs *BaseService) OnStop() {} +// Implements Service +func (bs *BaseService) Reset() (bool, error) { + if atomic.CompareAndSwapUint32(&bs.stopped, 1, 0) { + // whether or not we've started, we can reset + atomic.CompareAndSwapUint32(&bs.started, 1, 0) + + return true, bs.impl.OnReset() + } else { + if bs.log != nil { + bs.log.Debug(Fmt("Can't reset %v. Not stopped", bs.name), "impl", bs.impl) + } + return false, nil + } + // never happens + return false, nil +} + +// Implements Service +func (bs *BaseService) OnReset() error { + PanicSanity("The service cannot be reset") + return nil +} + // Implements Service func (bs *BaseService) IsRunning() bool { return atomic.LoadUint32(&bs.started) == 1 && atomic.LoadUint32(&bs.stopped) == 0 From 1a24e6e237a28354459281fe6a96419ed9e3e4fe Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Mon, 17 Oct 2016 16:15:57 -0700 Subject: [PATCH 2/2] Let SIGHUP close AutoFiles --- os.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++---- os_test.go | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 os_test.go diff --git a/os.go b/os.go index 1d0b538d..a273bec4 100644 --- a/os.go +++ b/os.go @@ -8,6 +8,7 @@ import ( "os/signal" "strings" "sync" + "syscall" "time" ) @@ -15,6 +16,10 @@ var ( GoPath = os.Getenv("GOPATH") ) +func init() { + initAFSIGHUPWatcher() +} + func TrapSignal(cb func()) { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) @@ -134,6 +139,7 @@ const autoFileOpenDuration = 1000 * time.Millisecond // Automatically closes and re-opens file for writing. // This is useful for using a log file with the logrotate tool. type AutoFile struct { + ID string Path string ticker *time.Ticker mtx sync.Mutex @@ -142,6 +148,7 @@ type AutoFile struct { func OpenAutoFile(path string) (af *AutoFile, err error) { af = &AutoFile{ + ID: RandStr(12) + ":" + path, Path: path, ticker: time.NewTicker(autoFileOpenDuration), } @@ -149,14 +156,14 @@ func OpenAutoFile(path string) (af *AutoFile, err error) { return } go af.processTicks() + autoFileWatchers.addAutoFile(af) return } func (af *AutoFile) Close() error { af.ticker.Stop() - af.mtx.Lock() err := af.closeFile() - af.mtx.Unlock() + autoFileWatchers.removeAutoFile(af) return err } @@ -166,13 +173,14 @@ func (af *AutoFile) processTicks() { if !ok { return // Done. } - af.mtx.Lock() af.closeFile() - af.mtx.Unlock() } } func (af *AutoFile) closeFile() (err error) { + af.mtx.Lock() + defer af.mtx.Unlock() + file := af.file if file == nil { return nil @@ -201,6 +209,56 @@ func (af *AutoFile) openFile() error { return nil } +//-------------------------------------------------------------------------------- + +var autoFileWatchers *afSIGHUPWatcher + +func initAFSIGHUPWatcher() { + autoFileWatchers = newAFSIGHUPWatcher() + + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP) + + go func() { + for _ = range c { + autoFileWatchers.closeAll() + } + }() +} + +type afSIGHUPWatcher struct { + mtx sync.Mutex + autoFiles map[string]*AutoFile +} + +func newAFSIGHUPWatcher() *afSIGHUPWatcher { + return &afSIGHUPWatcher{ + autoFiles: make(map[string]*AutoFile, 10), + } +} + +func (afw *afSIGHUPWatcher) addAutoFile(af *AutoFile) { + afw.mtx.Lock() + afw.autoFiles[af.ID] = af + afw.mtx.Unlock() +} + +func (afw *afSIGHUPWatcher) removeAutoFile(af *AutoFile) { + afw.mtx.Lock() + delete(afw.autoFiles, af.ID) + afw.mtx.Unlock() +} + +func (afw *afSIGHUPWatcher) closeAll() { + afw.mtx.Lock() + for _, af := range afw.autoFiles { + af.closeFile() + } + afw.mtx.Unlock() +} + +//-------------------------------------------------------------------------------- + func Tempfile(prefix string) (*os.File, string) { file, err := ioutil.TempFile("", prefix) if err != nil { @@ -209,6 +267,8 @@ func Tempfile(prefix string) (*os.File, string) { return file, file.Name() } +//-------------------------------------------------------------------------------- + func Prompt(prompt string, defaultValue string) (string, error) { fmt.Print(prompt) reader := bufio.NewReader(os.Stdin) diff --git a/os_test.go b/os_test.go new file mode 100644 index 00000000..c0effdc2 --- /dev/null +++ b/os_test.go @@ -0,0 +1,64 @@ +package common + +import ( + "os" + "syscall" + "testing" +) + +func TestSIGHUP(t *testing.T) { + + // First, create an AutoFile writing to a tempfile dir + file, name := Tempfile("sighup_test") + err := file.Close() + if err != nil { + t.Fatalf("Error creating tempfile: %v", err) + } + // Here is the actual AutoFile + af, err := OpenAutoFile(name) + if err != nil { + t.Fatalf("Error creating autofile: %v", err) + } + + // Write to the file. + _, err = af.Write([]byte("Line 1\n")) + if err != nil { + t.Fatalf("Error writing to autofile: %v", err) + } + _, err = af.Write([]byte("Line 2\n")) + if err != nil { + t.Fatalf("Error writing to autofile: %v", err) + } + + // Send SIGHUP to self. + syscall.Kill(syscall.Getpid(), syscall.SIGHUP) + + // Move the file over + err = os.Rename(name, name+"_old") + if err != nil { + t.Fatalf("Error moving autofile: %v", err) + } + + // Write more to the file. + _, err = af.Write([]byte("Line 3\n")) + if err != nil { + t.Fatalf("Error writing to autofile: %v", err) + } + _, err = af.Write([]byte("Line 4\n")) + if err != nil { + t.Fatalf("Error writing to autofile: %v", err) + } + err = af.Close() + if err != nil { + t.Fatalf("Error closing autofile") + } + + // Both files should exist + if body := MustReadFile(name + "_old"); string(body) != "Line 1\nLine 2\n" { + t.Errorf("Unexpected body %s", body) + } + if body := MustReadFile(name); string(body) != "Line 3\nLine 4\n" { + t.Errorf("Unexpected body %s", body) + } + +}