初步完成了状态机设计

This commit is contained in:
MahnoKropotkinvich 2024-12-23 18:16:38 +08:00
parent be7812ed68
commit 519c945eff
5 changed files with 203 additions and 68 deletions

View File

@ -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

0
daemon.go Normal file
View File

35
ds.go Normal file
View File

@ -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
}

160
fsm.go Normal file
View File

@ -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:
}
}
}

View File

@ -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 {
}
}