diff --git a/config.go b/config.go index 5440610..ab0395e 100644 --- a/config.go +++ b/config.go @@ -6,11 +6,9 @@ import ( ) type Config struct { - Server struct { - Timeout int `yaml:"timeout"` - StartCommand string `yaml:"start_command"` - Address string `yaml:"address"` - } `yaml:"server"` + Timeout int `yaml:"timeout"` // minutes + StartCommand string `yaml:"start_command"` + Port string `yaml:"port"` } // this is a global constant since it's shared diff --git a/daemon.go b/daemon.go new file mode 100644 index 0000000..e69de29 diff --git a/ds.go b/ds.go new file mode 100644 index 0000000..bfcaf16 --- /dev/null +++ b/ds.go @@ -0,0 +1,35 @@ +package main + +// this DS does not provide thread security +func NewStack[T any]() *Stack[T] { + return &Stack[T]{ + items: make([]T, 0), + } +} + +type Stack[T any] struct { + items []T +} + +func (s *Stack[T]) Push(item T) { + s.items = append(s.items, item) +} +func (s *Stack[T]) Pop() T { + l := len(s.items) + if l == 0 { + panic("Stack: pop") + } + ret := s.items[l] + s.items = s.items[:l-1] + return ret +} +func (s Stack[T]) Peek() T { + l := len(s.items) + if l == 0 { + panic("Stack: peek") + } + return s.items[l] +} +func (s Stack[T]) IsEmpty() bool { + return len(s.items) == 0 +} diff --git a/fsm.go b/fsm.go new file mode 100644 index 0000000..96ba396 --- /dev/null +++ b/fsm.go @@ -0,0 +1,160 @@ +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() {} +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, SIZE) + 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), + //} + addChan := func(Chan QueryChan) { + logger.Debug("handleState():adding new chanchan") + nelem := reflect.SelectCase{ + Dir: reflect.SelectDefault, + Chan: reflect.ValueOf(Chan), + } + if unused.IsEmpty() { + selectCases = append(selectCases, nelem) + } else { + selectCases[unused.Pop()] = nelem + } + } + logger.Debug("handleState(): ready") + for { + id, recv, ok := reflect.Select(selectCases) + if !ok { + unused.Push(id) + continue + } + switch id { + case CHANCHAN: + nchan := *(*QueryChan)(recv.UnsafePointer()) + addChan(nchan) + case EVENTCHAN: + // wait until transformation finishes + event := globalConnEvent(recv.Int()) + switch event { + case INCOMING: + switch state { + case STOPPED: + // case STOPPING: // it shouldn't be there, too, should be handled with conn.go + case WAITING: + handleWaitingToRunning() + default: + panic("handleState():written wrong!") + } + case EMPTY: + switch state { + case RUNNING: + handleRunningToWaiting() + default: + panic("handleState():written wrong!") + } + } + default: + } + } + +} diff --git a/state.go b/state.go index 885992f..dfe1774 100644 --- a/state.go +++ b/state.go @@ -1,8 +1,10 @@ package main -import ( - "reflect" - "runtime/debug" +type CntEvent int + +const ( + INCREASE CntEvent = 1 + DECREASE CntEvent = -1 ) type MCState int @@ -14,63 +16,3 @@ const ( WAITING STOPPING ) -const QUERY = 114514 - -type CntEvent int - -const ( - INCREASE CntEvent = 1 - DECREASE CntEvent = -1 -) - -type StateEvent int - -const ( - INCOMING StateEvent = iota - EMPTY -) - -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 StateEvent) - // the channel used to pass channel to build directional communication - ChanChan = make(chan QueryChan) -) -var cnt int - -func InitState() { - go handleCnt() - go handleEvent() -} -func handleCnt() { - for { - cntEvent := <-CntChan - switch cntEvent { - case INCREASE: - cnt++ - if cnt == 1 { //which means it is 0->1 - eventChan <- INCOMING - } - case DECREASE: - cnt-- - if cnt < 0 { - debug.Stack() - panic("cnt processor was written wrong!") - } else if cnt == 0 { - eventChan <- EMPTY - } - } - } -} -func handleEvent() { //handles both write and read - var chanList []QueryChan - // TODO: create chanList vector - for { - - } - -}