Merge pull request #5 from tendermint/develop
Reset() and SIGHUP for AutoFiles
This commit is contained in:
commit
1c62bb6dad
68
os.go
68
os.go
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
33
service.go
33
service.go
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue