diff --git a/os.go b/os.go index e8943c0c..a273bec4 100644 --- a/os.go +++ b/os.go @@ -7,12 +7,19 @@ import ( "os" "os/signal" "strings" + "sync" + "syscall" + "time" ) var ( GoPath = os.Getenv("GOPATH") ) +func init() { + initAFSIGHUPWatcher() +} + func TrapSignal(cb func()) { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) @@ -105,6 +112,153 @@ func WriteFileAtomic(filePath string, newBytes []byte, mode os.FileMode) error { //-------------------------------------------------------------------------------- +/* AutoFile usage + +// Create/Append to ./autofile_test +af, err := OpenAutoFile("autofile_test") +if err != nil { + panic(err) +} + +// Stream of writes. +// During this time, the file may be moved e.g. by logRotate. +for i := 0; i < 60; i++ { + af.Write([]byte(Fmt("LOOP(%v)", i))) + time.Sleep(time.Second) +} + +// Close the AutoFile +err = af.Close() +if err != nil { + panic(err) +} +*/ + +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 + file *os.File +} + +func OpenAutoFile(path string) (af *AutoFile, err error) { + af = &AutoFile{ + ID: RandStr(12) + ":" + path, + Path: path, + ticker: time.NewTicker(autoFileOpenDuration), + } + if err = af.openFile(); err != nil { + return + } + go af.processTicks() + autoFileWatchers.addAutoFile(af) + return +} + +func (af *AutoFile) Close() error { + af.ticker.Stop() + err := af.closeFile() + autoFileWatchers.removeAutoFile(af) + return err +} + +func (af *AutoFile) processTicks() { + for { + _, ok := <-af.ticker.C + if !ok { + return // Done. + } + af.closeFile() + } +} + +func (af *AutoFile) closeFile() (err error) { + af.mtx.Lock() + defer af.mtx.Unlock() + + file := af.file + if file == nil { + return nil + } + af.file = nil + return file.Close() +} + +func (af *AutoFile) Write(b []byte) (n int, err error) { + af.mtx.Lock() + defer af.mtx.Unlock() + if af.file == nil { + if err = af.openFile(); err != nil { + return + } + } + return af.file.Write(b) +} + +func (af *AutoFile) openFile() error { + file, err := os.OpenFile(af.Path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) + if err != nil { + return err + } + af.file = file + 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 { @@ -113,19 +267,6 @@ func Tempfile(prefix string) (*os.File, string) { return file, file.Name() } -func Tempdir(prefix string) (*os.File, string) { - tempDir := os.TempDir() + "/" + prefix + RandStr(12) - err := EnsureDir(tempDir, 0700) - if err != nil { - panic(Fmt("Error creating temp dir: %v", err)) - } - dir, err := os.Open(tempDir) - if err != nil { - panic(Fmt("Error opening temp dir: %v", err)) - } - return dir, tempDir -} - //-------------------------------------------------------------------------------- func Prompt(prompt string, defaultValue string) (string, error) { 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) + } + +} diff --git a/service.go b/service.go index e2d31925..86ef20ea 100644 --- a/service.go +++ b/service.go @@ -65,7 +65,6 @@ type BaseService struct { name string started uint32 // atomic stopped uint32 // atomic - Quit chan struct{} // The "subclass" of BaseService impl Service @@ -75,7 +74,6 @@ func NewBaseService(log log15.Logger, name string, impl Service) *BaseService { return &BaseService{ log: log, name: name, - Quit: make(chan struct{}), impl: impl, } } @@ -104,8 +102,6 @@ func (bs *BaseService) Start() (bool, error) { } // Implements Service -// NOTE: Do not put anything in here, -// that way users don't need to call BaseService.OnStart() func (bs *BaseService) OnStart() error { return nil } // Implements Service @@ -115,7 +111,6 @@ func (bs *BaseService) Stop() bool { bs.log.Info(Fmt("Stopping %v", bs.name), "impl", bs.impl) } bs.impl.OnStop() - close(bs.Quit) return true } else { if bs.log != nil { @@ -126,8 +121,6 @@ func (bs *BaseService) Stop() bool { } // Implements Service -// NOTE: Do not put anything in here, -// that way users don't need to call BaseService.OnStop() func (bs *BaseService) OnStop() {} // Implements Service @@ -158,10 +151,6 @@ func (bs *BaseService) IsRunning() bool { return atomic.LoadUint32(&bs.started) == 1 && atomic.LoadUint32(&bs.stopped) == 0 } -func (bs *BaseService) Wait() { - <-bs.Quit -} - // Implements Servce func (bs *BaseService) String() string { return bs.name @@ -171,13 +160,25 @@ func (bs *BaseService) String() string { type QuitService struct { BaseService + Quit chan struct{} } func NewQuitService(log log15.Logger, name string, impl Service) *QuitService { - if log != nil { - log.Warn("QuitService is deprecated, use BaseService instead") - } return &QuitService{ BaseService: *NewBaseService(log, name, impl), + Quit: nil, + } +} + +// NOTE: when overriding OnStart, must call .QuitService.OnStart(). +func (qs *QuitService) OnStart() error { + qs.Quit = make(chan struct{}) + return nil +} + +// NOTE: when overriding OnStop, must call .QuitService.OnStop(). +func (qs *QuitService) OnStop() { + if qs.Quit != nil { + close(qs.Quit) } } diff --git a/service_test.go b/service_test.go deleted file mode 100644 index 6e24dad6..00000000 --- a/service_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package common - -import ( - "testing" -) - -func TestBaseServiceWait(t *testing.T) { - - type TestService struct { - BaseService - } - ts := &TestService{} - ts.BaseService = *NewBaseService(nil, "TestService", ts) - ts.Start() - - go func() { - ts.Stop() - }() - - for i := 0; i < 10; i++ { - ts.Wait() - } - -}