122 lines
3.5 KiB
Go
122 lines
3.5 KiB
Go
|
// Copyright 2013, Google Inc. All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package sync2
|
||
|
|
||
|
import (
|
||
|
"sync"
|
||
|
)
|
||
|
|
||
|
// These are the three predefined states of a service.
|
||
|
const (
|
||
|
SERVICE_STOPPED = iota
|
||
|
SERVICE_RUNNING
|
||
|
SERVICE_SHUTTING_DOWN
|
||
|
)
|
||
|
|
||
|
var stateNames = []string{
|
||
|
"Stopped",
|
||
|
"Running",
|
||
|
"ShuttingDown",
|
||
|
}
|
||
|
|
||
|
// ServiceManager manages the state of a service through its lifecycle.
|
||
|
type ServiceManager struct {
|
||
|
mu sync.Mutex
|
||
|
wg sync.WaitGroup
|
||
|
err error // err is the error returned from the service function.
|
||
|
state AtomicInt64
|
||
|
// shutdown is created when the service starts and is closed when the service
|
||
|
// enters the SERVICE_SHUTTING_DOWN state.
|
||
|
shutdown chan struct{}
|
||
|
}
|
||
|
|
||
|
// Go tries to change the state from SERVICE_STOPPED to SERVICE_RUNNING.
|
||
|
//
|
||
|
// If the current state is not SERVICE_STOPPED (already running), it returns
|
||
|
// false immediately.
|
||
|
//
|
||
|
// On successful transition, it launches the service as a goroutine and returns
|
||
|
// true. The service function is responsible for returning on its own when
|
||
|
// requested, either by regularly checking svc.IsRunning(), or by waiting for
|
||
|
// the svc.ShuttingDown channel to be closed.
|
||
|
//
|
||
|
// When the service func returns, the state is reverted to SERVICE_STOPPED.
|
||
|
func (svm *ServiceManager) Go(service func(svc *ServiceContext) error) bool {
|
||
|
svm.mu.Lock()
|
||
|
defer svm.mu.Unlock()
|
||
|
if !svm.state.CompareAndSwap(SERVICE_STOPPED, SERVICE_RUNNING) {
|
||
|
return false
|
||
|
}
|
||
|
svm.wg.Add(1)
|
||
|
svm.err = nil
|
||
|
svm.shutdown = make(chan struct{})
|
||
|
go func() {
|
||
|
svm.err = service(&ServiceContext{ShuttingDown: svm.shutdown})
|
||
|
svm.state.Set(SERVICE_STOPPED)
|
||
|
svm.wg.Done()
|
||
|
}()
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// Stop tries to change the state from SERVICE_RUNNING to SERVICE_SHUTTING_DOWN.
|
||
|
// If the current state is not SERVICE_RUNNING, it returns false immediately.
|
||
|
// On successul transition, it waits for the service to finish, and returns true.
|
||
|
// You are allowed to Go() again after a Stop().
|
||
|
func (svm *ServiceManager) Stop() bool {
|
||
|
svm.mu.Lock()
|
||
|
defer svm.mu.Unlock()
|
||
|
if !svm.state.CompareAndSwap(SERVICE_RUNNING, SERVICE_SHUTTING_DOWN) {
|
||
|
return false
|
||
|
}
|
||
|
// Signal the service that we've transitioned to SERVICE_SHUTTING_DOWN.
|
||
|
close(svm.shutdown)
|
||
|
svm.shutdown = nil
|
||
|
svm.wg.Wait()
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// Wait waits for the service to terminate if it's currently running.
|
||
|
func (svm *ServiceManager) Wait() {
|
||
|
svm.wg.Wait()
|
||
|
}
|
||
|
|
||
|
// Join waits for the service to terminate and returns the value returned by the
|
||
|
// service function.
|
||
|
func (svm *ServiceManager) Join() error {
|
||
|
svm.wg.Wait()
|
||
|
return svm.err
|
||
|
}
|
||
|
|
||
|
// State returns the current state of the service.
|
||
|
// This should only be used to report the current state.
|
||
|
func (svm *ServiceManager) State() int64 {
|
||
|
return svm.state.Get()
|
||
|
}
|
||
|
|
||
|
// StateName returns the name of the current state.
|
||
|
func (svm *ServiceManager) StateName() string {
|
||
|
return stateNames[svm.State()]
|
||
|
}
|
||
|
|
||
|
// ServiceContext is passed into the service function to give it access to
|
||
|
// information about the running service.
|
||
|
type ServiceContext struct {
|
||
|
// ShuttingDown is a channel that the service can select on to be notified
|
||
|
// when it should shut down. The channel is closed when the state transitions
|
||
|
// from SERVICE_RUNNING to SERVICE_SHUTTING_DOWN.
|
||
|
ShuttingDown chan struct{}
|
||
|
}
|
||
|
|
||
|
// IsRunning returns true if the ServiceContext.ShuttingDown channel has not
|
||
|
// been closed yet.
|
||
|
func (svc *ServiceContext) IsRunning() bool {
|
||
|
select {
|
||
|
case <-svc.ShuttingDown:
|
||
|
return false
|
||
|
default:
|
||
|
return true
|
||
|
}
|
||
|
}
|