Merge pull request #5 from tendermint/develop

Reset() and SIGHUP for AutoFiles
This commit is contained in:
Jae Kwon 2016-10-20 02:01:00 -07:00 committed by GitHub
commit 1c62bb6dad
3 changed files with 158 additions and 7 deletions

68
os.go
View File

@ -8,6 +8,7 @@ import (
"os/signal" "os/signal"
"strings" "strings"
"sync" "sync"
"syscall"
"time" "time"
) )
@ -15,6 +16,10 @@ var (
GoPath = os.Getenv("GOPATH") GoPath = os.Getenv("GOPATH")
) )
func init() {
initAFSIGHUPWatcher()
}
func TrapSignal(cb func()) { func TrapSignal(cb func()) {
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt) signal.Notify(c, os.Interrupt)
@ -134,6 +139,7 @@ const autoFileOpenDuration = 1000 * time.Millisecond
// Automatically closes and re-opens file for writing. // Automatically closes and re-opens file for writing.
// This is useful for using a log file with the logrotate tool. // This is useful for using a log file with the logrotate tool.
type AutoFile struct { type AutoFile struct {
ID string
Path string Path string
ticker *time.Ticker ticker *time.Ticker
mtx sync.Mutex mtx sync.Mutex
@ -142,6 +148,7 @@ type AutoFile struct {
func OpenAutoFile(path string) (af *AutoFile, err error) { func OpenAutoFile(path string) (af *AutoFile, err error) {
af = &AutoFile{ af = &AutoFile{
ID: RandStr(12) + ":" + path,
Path: path, Path: path,
ticker: time.NewTicker(autoFileOpenDuration), ticker: time.NewTicker(autoFileOpenDuration),
} }
@ -149,14 +156,14 @@ func OpenAutoFile(path string) (af *AutoFile, err error) {
return return
} }
go af.processTicks() go af.processTicks()
autoFileWatchers.addAutoFile(af)
return return
} }
func (af *AutoFile) Close() error { func (af *AutoFile) Close() error {
af.ticker.Stop() af.ticker.Stop()
af.mtx.Lock()
err := af.closeFile() err := af.closeFile()
af.mtx.Unlock() autoFileWatchers.removeAutoFile(af)
return err return err
} }
@ -166,13 +173,14 @@ func (af *AutoFile) processTicks() {
if !ok { if !ok {
return // Done. return // Done.
} }
af.mtx.Lock()
af.closeFile() af.closeFile()
af.mtx.Unlock()
} }
} }
func (af *AutoFile) closeFile() (err error) { func (af *AutoFile) closeFile() (err error) {
af.mtx.Lock()
defer af.mtx.Unlock()
file := af.file file := af.file
if file == nil { if file == nil {
return nil return nil
@ -201,6 +209,56 @@ func (af *AutoFile) openFile() error {
return nil 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) { func Tempfile(prefix string) (*os.File, string) {
file, err := ioutil.TempFile("", prefix) file, err := ioutil.TempFile("", prefix)
if err != nil { if err != nil {
@ -209,6 +267,8 @@ func Tempfile(prefix string) (*os.File, string) {
return file, file.Name() return file, file.Name()
} }
//--------------------------------------------------------------------------------
func Prompt(prompt string, defaultValue string) (string, error) { func Prompt(prompt string, defaultValue string) (string, error) {
fmt.Print(prompt) fmt.Print(prompt)
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)

64
os_test.go Normal file
View File

@ -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)
}
}

View File

@ -1,12 +1,13 @@
/* /*
Classical-inheritance-style service declarations. 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. 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. Caller must ensure that Start() and Stop() are not called concurrently.
It is ok to call Stop() without calling Start() first. 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: Typical usage:
@ -51,6 +52,9 @@ type Service interface {
Stop() bool Stop() bool
OnStop() OnStop()
Reset() (bool, error)
OnReset() error
IsRunning() bool IsRunning() bool
String() string String() string
@ -119,6 +123,29 @@ func (bs *BaseService) Stop() bool {
// Implements Service // Implements Service
func (bs *BaseService) OnStop() {} 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 // Implements Service
func (bs *BaseService) IsRunning() bool { func (bs *BaseService) IsRunning() bool {
return atomic.LoadUint32(&bs.started) == 1 && atomic.LoadUint32(&bs.stopped) == 0 return atomic.LoadUint32(&bs.started) == 1 && atomic.LoadUint32(&bs.stopped) == 0