Add engine and state restart on empty termination node
This commit is contained in:
parent
957d59bb1a
commit
bf1d634474
31
README.md
31
README.md
@ -56,6 +56,8 @@ The signal flag arguments should only set a single flag to be tested. If more th
|
||||
|
||||
First 8 flags are reserved and used for internal VM operations.
|
||||
|
||||
When a signal is caught, the *bytecode buffer is flushed* before the target symbol code is loaded.
|
||||
|
||||
|
||||
### Avoid duplicate menu items
|
||||
|
||||
@ -144,17 +146,17 @@ Currently the following rules apply for encoding in version `0`:
|
||||
|
||||
This repository provides a `golang` reference implementation for the `vise` concept.
|
||||
|
||||
In this reference implementation some constraints apply
|
||||
|
||||
|
||||
### Structure
|
||||
|
||||
- `vm`: Defines instructions, and applies transformations according to the instructions.
|
||||
- `state`: Holds the bytecode buffer, error states and navigation states.
|
||||
- `asm`: Assembly parser and compiler.
|
||||
- `cache`: Holds and manages all loaded content.
|
||||
- `resource`: Retrieves data and bytecode from external symbols, and retrieves templates.
|
||||
- `render`: Renders menu and templates, and enforces output size constraints.
|
||||
- `engine`: Outermost interface. Orchestrates execution of bytecode against input.
|
||||
- `persist`: Interface and reference implementation of `state` and `cache` persistence across asynchronous vm executions.
|
||||
- `render`: Renders menu and templates, and enforces output size constraints.
|
||||
- `resource`: Retrieves data and bytecode from external symbols, and retrieves templates.
|
||||
- `state`: Holds the bytecode buffer, error states and navigation states.
|
||||
- `vm`: Defines instructions, and applies transformations according to the instructions.
|
||||
|
||||
|
||||
### Template rendering
|
||||
@ -164,6 +166,21 @@ Template rendering is done using the `text/template` faciilty in the `golang` st
|
||||
It expects all replacement symbols to be available at time of rendering, and has no tolerance for missing ones.
|
||||
|
||||
|
||||
### Runtime engine
|
||||
|
||||
The runtime engine:
|
||||
|
||||
* Validates client input
|
||||
* Runs VM with client input
|
||||
* Renders result
|
||||
* Restarts execution from top if the vm has nothing more to do.
|
||||
|
||||
There are two flavors of the engine:
|
||||
|
||||
* `engine.Loop` - class used for continuous, in-memory interaction with the vm (e.g. terminal).
|
||||
* `engine.RunPersisted` - method which combines single vm executions with persisted state (e.g. http).
|
||||
|
||||
|
||||
## Bytecode examples
|
||||
|
||||
(Minimal, WIP)
|
||||
@ -182,7 +199,7 @@ It expects all replacement symbols to be available at time of rendering, and has
|
||||
|
||||
## Assembly examples
|
||||
|
||||
See `testdata/*.fst`
|
||||
See `testdata/*.vis`
|
||||
|
||||
|
||||
## Development tools
|
||||
|
@ -28,6 +28,7 @@ type Engine struct {
|
||||
rs resource.Resource
|
||||
ca cache.Memory
|
||||
vm *vm.Vm
|
||||
root string
|
||||
initd bool
|
||||
}
|
||||
|
||||
@ -44,11 +45,8 @@ func NewEngine(cfg Config, st *state.State, rs resource.Resource, ca cache.Memor
|
||||
ca: ca,
|
||||
vm: vm.NewVm(st, rs, ca, szr),
|
||||
}
|
||||
var err error
|
||||
if st.Moves == 0 {
|
||||
err = engine.Init(cfg.Root, ctx)
|
||||
}
|
||||
return engine, err
|
||||
engine.root = cfg.Root
|
||||
return engine, nil
|
||||
}
|
||||
|
||||
// Init must be explicitly called before using the Engine instance.
|
||||
@ -62,6 +60,7 @@ func(en *Engine) Init(sym string, ctx context.Context) error {
|
||||
if sym == "" {
|
||||
return fmt.Errorf("start sym empty")
|
||||
}
|
||||
inSave, _ := en.st.GetInput()
|
||||
err := en.st.SetInput([]byte{})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -77,6 +76,10 @@ func(en *Engine) Init(sym string, ctx context.Context) error {
|
||||
}
|
||||
log.Printf("ended init VM run with code %x", b)
|
||||
en.st.SetCode(b)
|
||||
err = en.st.SetInput(inSave)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
en.initd = true
|
||||
return nil
|
||||
}
|
||||
@ -92,7 +95,17 @@ func(en *Engine) Init(sym string, ctx context.Context) error {
|
||||
// - no current bytecode is available
|
||||
// - input processing against bytcode failed
|
||||
func (en *Engine) Exec(input []byte, ctx context.Context) (bool, error) {
|
||||
err := vm.ValidInput(input)
|
||||
var err error
|
||||
if en.st.Moves == 0 {
|
||||
err = en.Init(en.root, ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(input) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
err = vm.ValidInput(input)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
@ -109,6 +122,7 @@ func (en *Engine) Exec(input []byte, ctx context.Context) (bool, error) {
|
||||
if len(code) == 0 {
|
||||
return false, fmt.Errorf("no code to execute")
|
||||
}
|
||||
|
||||
log.Printf("start new VM run with code %x", code)
|
||||
code, err = en.vm.Run(code, ctx)
|
||||
if err != nil {
|
||||
@ -124,13 +138,14 @@ func (en *Engine) Exec(input []byte, ctx context.Context) (bool, error) {
|
||||
if len(code) > 0 {
|
||||
log.Printf("terminated with code remaining: %x", code)
|
||||
}
|
||||
return false, nil
|
||||
return false, err
|
||||
}
|
||||
|
||||
en.st.SetCode(code)
|
||||
if len(code) == 0 {
|
||||
log.Printf("runner finished with no remaining code")
|
||||
return false, nil
|
||||
err = en.reset(ctx)
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
@ -149,3 +164,22 @@ func(en *Engine) WriteResult(w io.Writer, ctx context.Context) (int, error) {
|
||||
}
|
||||
return io.WriteString(w, r)
|
||||
}
|
||||
|
||||
func(en *Engine) reset(ctx context.Context) error {
|
||||
var err error
|
||||
var isTop bool
|
||||
for !isTop {
|
||||
isTop, err = en.st.Top()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = en.st.Up()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
en.ca.Pop()
|
||||
}
|
||||
en.st.Restart()
|
||||
en.initd = false
|
||||
return en.Init(en.root, ctx)
|
||||
}
|
||||
|
@ -87,7 +87,11 @@ func TestEngineInit(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
//
|
||||
|
||||
err = en.Init("root", ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w := bytes.NewBuffer(nil)
|
||||
_, err = en.WriteResult(w, ctx)
|
||||
if err != nil {
|
||||
@ -152,3 +156,40 @@ func TestEngineExecInvalidInput(t *testing.T) {
|
||||
t.Fatalf("expected fail on invalid input")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineResumeTerminated(t *testing.T) {
|
||||
generateTestData(t)
|
||||
ctx := context.TODO()
|
||||
st := state.NewState(17)
|
||||
rs := NewFsWrapper(dataDir, &st)
|
||||
ca := cache.NewCache().WithCacheSize(1024)
|
||||
|
||||
en, err := NewEngine(Config{
|
||||
Root: "root",
|
||||
}, &st, &rs, ca, ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = en.Init("root", ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = en.Exec([]byte("1"), ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = en.Exec([]byte("1"), ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
location, idx := st.Where()
|
||||
if location != "root" {
|
||||
t.Fatalf("expected 'root', got %s", location)
|
||||
}
|
||||
if idx != 0 {
|
||||
t.Fatalf("expected idx '0', got %v", idx)
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,6 @@ const (
|
||||
FLAG_TERMINATE = 3
|
||||
FLAG_DIRTY = 4
|
||||
FLAG_LOADFAIL = 5
|
||||
FLAG_USERSTART = 9
|
||||
//FLAG_WRITEABLE = FLAG_LOADFAIL
|
||||
)
|
||||
|
||||
func IsWriteableFlag(flag uint32) bool {
|
||||
|
@ -327,10 +327,22 @@ func(st *State) SetInput(input []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func(st *State) Reset() error {
|
||||
// Reset re-initializes the state to run from top node with accumulated client state.
|
||||
func(st *State) Restart() error {
|
||||
st.resetBaseFlags()
|
||||
st.Moves = 0
|
||||
st.SizeIdx = 0
|
||||
st.input = []byte{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements String interface
|
||||
func(st State) String() string {
|
||||
return fmt.Sprintf("moves %v idx %v path: %s", st.Moves, st.SizeIdx, strings.Join(st.ExecPath, "/"))
|
||||
}
|
||||
|
||||
// initializes all flags not in control of client.
|
||||
func(st *State) resetBaseFlags() {
|
||||
st.Flags[0] = 0
|
||||
}
|
||||
|
||||
|
@ -195,7 +195,6 @@ func(vm *Vm) RunCatch(b []byte, ctx context.Context) ([]byte, error) {
|
||||
b = bh
|
||||
vm.st.Down(sym)
|
||||
vm.ca.Push()
|
||||
vm.Reset()
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
@ -213,7 +212,6 @@ func(vm *Vm) RunCroak(b []byte, ctx context.Context) ([]byte, error) {
|
||||
if r {
|
||||
log.Printf("croak at flag %v, purging and moving to top", sig)
|
||||
vm.Reset()
|
||||
vm.st.Reset()
|
||||
vm.pg.Reset()
|
||||
vm.ca.Reset()
|
||||
b = []byte{}
|
||||
|
Loading…
Reference in New Issue
Block a user