222 lines
4.9 KiB
Go
222 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"runtime/debug"
|
|
"time"
|
|
)
|
|
|
|
type globalConnEvent int
|
|
|
|
const (
|
|
INCOMING globalConnEvent = iota
|
|
EMPTY
|
|
)
|
|
|
|
// luckily I use int for them all
|
|
type GenericChan chan int
|
|
type QueryChan chan MCState
|
|
|
|
var (
|
|
state MCState = STOPPED
|
|
// we don't need mutex for cnt because with channel it's guaranteed to be processed sequently
|
|
CntChan = make(chan CntEvent)
|
|
eventChan = make(chan globalConnEvent)
|
|
// the channel used to pass channel to build directional communication
|
|
ChanChan = make(chan QueryChan)
|
|
)
|
|
var cnt int
|
|
|
|
func InitState() {
|
|
go handleCnt()
|
|
go handleState()
|
|
}
|
|
func handleCnt() { //goroutine only, handles cnt related message
|
|
for {
|
|
cntEvent := <-CntChan
|
|
switch cntEvent {
|
|
case INCREASE:
|
|
GetLogger().Debug("handleCnt(): connection cnt increased")
|
|
cnt++
|
|
if cnt == 1 { //which means it is 0->1
|
|
eventChan <- INCOMING
|
|
}
|
|
case DECREASE:
|
|
GetLogger().Debug("handleCnt(): connection cnt decreased")
|
|
cnt--
|
|
if cnt < 0 {
|
|
debug.Stack()
|
|
panic("cnt processor was written wrong!")
|
|
} else if cnt == 0 {
|
|
eventChan <- EMPTY
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// STOPPED->BOOTING->RUNNING<->WAITING->STOPPING->STOPPED
|
|
var waitChan chan struct{}
|
|
|
|
// no we don't need cmdChan, we just make it work immediately
|
|
func handleWaitingToRunning() {
|
|
waitChan <- struct{}{}
|
|
_, ok := <-waitChan
|
|
if ok {
|
|
state = RUNNING //happens immediately
|
|
} //else it's too late
|
|
}
|
|
func handleRunningToWaiting() {
|
|
waitChan = make(chan struct{})
|
|
go waitingThread()
|
|
state = WAITING
|
|
}
|
|
|
|
// TODO: work with daemon
|
|
func handleWaitingToStopping() {
|
|
go stoppingThread()
|
|
state = STOPPING
|
|
}
|
|
func stoppingThread() {
|
|
DaemonChanTX <- struct{}{}
|
|
<-DaemonChanRX
|
|
handleStoppingToStopped()
|
|
}
|
|
func handleStoppingToStopped() {
|
|
state = STOPPED
|
|
}
|
|
func bootingThread() {
|
|
DaemonChanTX <- struct{}{}
|
|
<-DaemonChanRX
|
|
handleBootingToRunning()
|
|
}
|
|
|
|
var RunningChan = make(chan struct{})
|
|
|
|
func handleBootingToRunning() {
|
|
state = RUNNING
|
|
RunningChan <- struct{}{}
|
|
}
|
|
func handleStoppedToBooting() {
|
|
go bootingThread()
|
|
state = BOOTING
|
|
}
|
|
func waitingThread() {
|
|
defer close(waitChan)
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(config.Timeout)*time.Minute)
|
|
defer cancel()
|
|
select {
|
|
case <-ctx.Done():
|
|
handleWaitingToStopping()
|
|
return
|
|
case <-waitChan:
|
|
waitChan <- struct{}{}
|
|
return
|
|
}
|
|
}
|
|
func handleState() { //goroutine only, handles both write and read
|
|
unused := NewStack[int]()
|
|
const (
|
|
// CMD must come first
|
|
CHANCHAN = iota
|
|
EVENTCHAN
|
|
SIZE
|
|
)
|
|
|
|
selectCases := make([]reflect.SelectCase, 1)
|
|
//selectCases[CHANCHAN] = reflect.SelectCase{
|
|
// Dir: reflect.SelectRecv, // readonly
|
|
// Chan: reflect.ValueOf(ChanChan),
|
|
//}
|
|
//selectCases[EVENTCHAN] = reflect.SelectCase{
|
|
// Dir: reflect.SelectRecv, // readonly
|
|
// Chan: reflect.ValueOf(eventChan),
|
|
//}
|
|
//selectCases[CMDCHAN] = reflect.SelectCase{
|
|
// Dir: reflect.SelectRecv,
|
|
// Chan: reflect.ValueOf(cmdChan),
|
|
//}
|
|
packagedChan := make(QueryChan)
|
|
selectCases[0] = reflect.SelectCase{
|
|
Dir: reflect.SelectRecv,
|
|
Chan: reflect.ValueOf(packagedChan),
|
|
}
|
|
addChan := func(Chan QueryChan) {
|
|
logger.Debug("addChan(): adding new chan from chanchan")
|
|
nelem := reflect.SelectCase{
|
|
Dir: reflect.SelectRecv,
|
|
Chan: reflect.ValueOf(Chan),
|
|
}
|
|
logger.Debug("addChan(): done")
|
|
if unused.IsEmpty() {
|
|
selectCases = append(selectCases, nelem)
|
|
} else {
|
|
selectCases[unused.Pop()] = nelem
|
|
}
|
|
}
|
|
const SIGNAL = 114514
|
|
queryThread := func() {
|
|
prevId := -1
|
|
for {
|
|
id, recv, ok := reflect.Select(selectCases)
|
|
logger.Debugf("queryThread(): recv message from No.%d", id)
|
|
if prevId != -1 {
|
|
logger.Debug("queryThread(): clearing previous")
|
|
selectCases[prevId].Dir = reflect.SelectRecv
|
|
selectCases[prevId].Send = reflect.Value{}
|
|
prevId = -1
|
|
}
|
|
if !ok {
|
|
unused.Push(id)
|
|
continue
|
|
}
|
|
if id != 0 {
|
|
packagedChan <- MCState(recv.Int())
|
|
state, _ := <-packagedChan
|
|
selectCases[id].Dir = reflect.SelectSend
|
|
selectCases[id].Send = reflect.ValueOf(state)
|
|
prevId = id
|
|
}
|
|
|
|
}
|
|
}
|
|
go queryThread()
|
|
logger.Debug("handleState(): ready")
|
|
for {
|
|
select {
|
|
case nchan, _ := <-ChanChan:
|
|
addChan(nchan)
|
|
packagedChan <- SIGNAL
|
|
//selectCases[id].Dir = reflect.SelectSend
|
|
//selectCases[id].Send = reflect.ValueOf(make(QueryChan))
|
|
//prevId = id
|
|
case event, _ := <-eventChan:
|
|
// wait until transformation finishes
|
|
switch event {
|
|
case INCOMING:
|
|
switch state {
|
|
case STOPPED:
|
|
handleStoppedToBooting()
|
|
// case STOPPING: // it shouldn't be there, too, should be handled with conn.go
|
|
// case BOOTING: //shouldn't be there too
|
|
case WAITING:
|
|
handleWaitingToRunning()
|
|
default:
|
|
panic("handleState():written wrong!" + stateToStr[state])
|
|
}
|
|
case EMPTY:
|
|
switch state {
|
|
case RUNNING:
|
|
handleRunningToWaiting()
|
|
case BOOTING:
|
|
default:
|
|
panic("handleState():written wrong!" + stateToStr[state])
|
|
}
|
|
}
|
|
case <-packagedChan:
|
|
packagedChan <- state
|
|
}
|
|
}
|
|
|
|
}
|