|  | package goscaffold | 
|  |  | 
|  | import ( | 
|  | "math" | 
|  | "sync/atomic" | 
|  | "time" | 
|  | ) | 
|  |  | 
|  | /* | 
|  | values for the command channel. | 
|  | */ | 
|  | const ( | 
|  | startRequest = iota | 
|  | endRequest | 
|  | shutdown | 
|  | ) | 
|  |  | 
|  | /* | 
|  | values for the shutdown state | 
|  | */ | 
|  | const ( | 
|  | running    int32 = iota | 
|  | markedDown int32 = iota | 
|  | shutDown   int32 = iota | 
|  | ) | 
|  |  | 
|  | /* | 
|  | The requestTracker keeps track of HTTP requests. In normal operations it | 
|  | just counts. Once the server has been marked for shutdown, however, it | 
|  | counts down to zero and returns a shutdown indication when that | 
|  | happens. | 
|  | */ | 
|  | type requestTracker struct { | 
|  | // A value will be delivered to this channel when the server can stop. | 
|  | // If "shutdown" is never called then this will never happen. | 
|  | C              chan error | 
|  | shutdownWait   time.Duration | 
|  | shutdownState  int32 | 
|  | shutdownReason *atomic.Value | 
|  | commandChan    chan int | 
|  | } | 
|  |  | 
|  | /* | 
|  | startRequestTracker creates a new tracker. shutdownWait defines the | 
|  | maximum amount of time that we should wait for shutdown in case some | 
|  | do not complete in a timely way. | 
|  | */ | 
|  | func startRequestTracker(shutdownWait time.Duration) *requestTracker { | 
|  | rt := &requestTracker{ | 
|  | C:              make(chan error, 1), | 
|  | commandChan:    make(chan int, 100), | 
|  | shutdownState:  running, | 
|  | shutdownWait:   shutdownWait, | 
|  | shutdownReason: &atomic.Value{}, | 
|  | } | 
|  | go rt.trackerLoop() | 
|  | return rt | 
|  | } | 
|  |  | 
|  | /* | 
|  | start indicates that a request started. It returns true if the request | 
|  | should proceed, and false if the request should fail because the server is | 
|  | shutting down. | 
|  | */ | 
|  | func (t *requestTracker) start() error { | 
|  | md := t.markedDown() | 
|  | if md == nil { | 
|  | t.commandChan <- startRequest | 
|  | } | 
|  | return md | 
|  | } | 
|  |  | 
|  | /* | 
|  | end indicates that a request ended. In order for this thing to work, the | 
|  | caller needs to ensure that start and end are always paired. | 
|  | */ | 
|  | func (t *requestTracker) end() { | 
|  | t.commandChan <- endRequest | 
|  | } | 
|  |  | 
|  | /* | 
|  | markedDown returns nil if everything is good, and an error if the server | 
|  | has been marked down. The error is the one that was sent to the | 
|  | "Shutdown" method. | 
|  | */ | 
|  | func (t *requestTracker) markedDown() error { | 
|  | ss := atomic.LoadInt32(&t.shutdownState) | 
|  | if ss != running { | 
|  | reason := t.shutdownReason.Load().(*error) | 
|  | if reason == nil { | 
|  | return nil | 
|  | } | 
|  | return *reason | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | /* | 
|  | shutdown indicates that the tracker should start counting down until | 
|  | the number of running requests reaches zero. The "reason" will be returned | 
|  | as the result of the "start" call. | 
|  | */ | 
|  | func (t *requestTracker) shutdown(reason error) { | 
|  | t.shutdownReason.Store(&reason) | 
|  | t.commandChan <- shutdown | 
|  | } | 
|  |  | 
|  | func (t *requestTracker) markDown() { | 
|  | t.shutdownReason.Store(&ErrMarkedDown) | 
|  | atomic.StoreInt32(&t.shutdownState, markedDown) | 
|  | } | 
|  |  | 
|  | func (t *requestTracker) sendStop(sent bool) bool { | 
|  | if !sent { | 
|  | reason := t.shutdownReason.Load().(*error) | 
|  | if reason == nil { | 
|  | return false | 
|  | } | 
|  | t.C <- *reason | 
|  | } | 
|  | return true | 
|  | } | 
|  |  | 
|  | /* | 
|  | trackerLoop runs all day and manages stuff. | 
|  | */ | 
|  | func (t *requestTracker) trackerLoop() { | 
|  | activeRequests := 0 | 
|  | stopping := false | 
|  | sentStop := false | 
|  | graceTimer := time.NewTimer(time.Duration(math.MaxInt64)) | 
|  |  | 
|  | for !sentStop { | 
|  | select { | 
|  | case cmd := <-t.commandChan: | 
|  | switch cmd { | 
|  | case startRequest: | 
|  | activeRequests++ | 
|  | case endRequest: | 
|  | activeRequests-- | 
|  | if stopping && activeRequests == 0 { | 
|  | sentStop = t.sendStop(sentStop) | 
|  | } | 
|  | case shutdown: | 
|  | stopping = true | 
|  | atomic.StoreInt32(&t.shutdownState, shutDown) | 
|  | if activeRequests <= 0 { | 
|  | sentStop = t.sendStop(sentStop) | 
|  | } else { | 
|  | graceTimer.Reset(t.shutdownWait) | 
|  | } | 
|  | } | 
|  | case <-graceTimer.C: | 
|  | sentStop = t.sendStop(sentStop) | 
|  | } | 
|  | } | 
|  | } |