go-vise

Constrained Size Output Virtual Machine
Info | Log | Files | Refs | README | LICENSE

commit c20d557a3dbbb71a54cc4e6a10e9096ef72af5cb
parent 7ea16f9137b46ff746f6ba40d3afc7b305d98f87
Author: lash <dev@holbrook.no>
Date:   Mon, 23 Sep 2024 17:23:17 +0100

Fix persistence for asynchronous execution

* Ensure full revert of state and cache after first func
* No exec in init, only set initial move code if empty
* Ensure set perister with engine state and cache on create

Diffstat:
Mengine/db.go | 66++++++++++++++++++++++++------------------------------------------
Mengine/loop_test.go | 4+---
Mpersist/persist.go | 12++++++++++--
Mstate/debug_test.go | 3++-
Mstate/state.go | 4++--
5 files changed, 39 insertions(+), 50 deletions(-)

diff --git a/engine/db.go b/engine/db.go @@ -244,16 +244,21 @@ func(en *DefaultEngine) ensurePersist() error { } else { en.ca = cac } - logg.Tracef("set persister", "st", st, "cac", cac, "session", en.cfg.SessionId) en.pe = en.pe.WithContent(st, cac) err := en.pe.Load(en.cfg.SessionId) if err != nil { logg.Infof("persister load fail. trying save in case new session", "err", err, "session", en.cfg.SessionId) err = en.pe.Save(en.cfg.SessionId) + if err != nil { + return err + } + en.pe = en.pe.WithContent(st, cac) + err = en.pe.Load(en.cfg.SessionId) } if en.cfg.StateDebug { en.st.UseDebug() } + logg.Tracef("set persister", "st", st, "cac", cac, "session", en.cfg.SessionId, "persister", en.pe) return err } @@ -317,9 +322,14 @@ func(en *DefaultEngine) runFirst(ctx context.Context) (bool, error) { return true, nil } logg.DebugCtxf(ctx, "start pre-VM check") + en.ca.Push() rs := resource.NewMenuResource() rs.AddLocalFunc("_first", en.first) en.st.Down("_first") + defer en.ca.Pop() + defer en.st.Up() + defer en.st.ResetFlag(state.FLAG_TERMINATE) + defer en.st.ResetFlag(state.FLAG_DIRTY) pvm := vm.NewVm(en.st, rs, en.ca, nil) b := vm.NewLine(nil, vm.LOAD, []string{"_first"}, []byte{0}, nil) b = vm.NewLine(b, vm.HALT, nil, nil, nil) @@ -343,10 +353,6 @@ func(en *DefaultEngine) runFirst(ctx context.Context) (bool, error) { en.st.Invalidate() en.ca.Invalidate() } - en.st.ResetFlag(state.FLAG_TERMINATE) - en.st.ResetFlag(state.FLAG_DIRTY) - en.st.Up() - en.ca.Pop() logg.DebugCtxf(ctx, "end pre-VM check") return r, err } @@ -380,28 +386,13 @@ func(en *DefaultEngine) Finish() error { return err } -// change root to current state location if non-empty. -func(en *DefaultEngine) restore() { - if en.initd { - return - } - location, _ := en.st.Where() - if len(location) == 0 { - return - } - if en.cfg.Root != location { - logg.Infof("restoring state", "sym", location) - en.cfg.Root = "." - } -} - func(en *DefaultEngine) setCode(ctx context.Context, code []byte) (bool, error) { var err error cont := true en.st.SetCode(code) if len(code) == 0 { - logg.Infof("runner finished with no remaining code", "state", en.st) + logg.InfoCtxf(ctx, "runner finished with no remaining code", "state", en.st) if en.st.MatchFlag(state.FLAG_DIRTY, true) { logg.Debugf("have output for quitting") en.exiting = true @@ -416,11 +407,11 @@ func(en *DefaultEngine) setCode(ctx context.Context, code []byte) (bool, error) // // It loads and executes code for the start node. func(en *DefaultEngine) init(ctx context.Context, input []byte) (bool, error) { + cont := true err := en.prepare(ctx) if err != nil { return false, err } - en.restore() if en.st.Language != nil { logg.TraceCtxf(ctx, "set language on context", "lang", en.st.Language) @@ -451,20 +442,12 @@ func(en *DefaultEngine) init(ctx context.Context, input []byte) (bool, error) { return false, nil } - b := vm.NewLine(nil, vm.MOVE, []string{sym}, nil, nil) - logg.DebugCtxf(ctx, "start new init VM run", "code", b) - b, err = en.vm.Run(ctx, b) - if err != nil { - return false, err - } - en.execd = true - if en.dbg != nil { - en.dbg.Break(en.st, en.ca) - } - logg.DebugCtxf(ctx, "end new init VM run", "code", b) - cont, err := en.setCode(ctx, b) - if err != nil { - return false, err + if len(en.st.Code) == 0 { + b := vm.NewLine(nil, vm.MOVE, []string{sym}, nil, nil) + cont, err = en.setCode(ctx, b) + if err != nil { + return false, err + } } err = en.st.SetInput(inSave) @@ -500,17 +483,17 @@ func (en *DefaultEngine) Exec(ctx context.Context, input []byte) (bool, error) { } if !cont { return cont, nil - } else if len(input) == 0 { - return true, nil } if en.st.Language != nil { ctx = context.WithValue(ctx, "Language", *en.st.Language) } - _, err = vm.ValidInput(input) - if err != nil { - return true, err + if len(input) > 0 { + _, err = vm.ValidInput(input) + if err != nil { + return true, err + } } err = en.st.SetInput(input) if err != nil { @@ -546,7 +529,6 @@ func(en *DefaultEngine) exec(ctx context.Context, input []byte) (bool, error) { return false, err } cont, err := en.setCode(ctx, code) - if en.dbg != nil { en.dbg.Break(en.st, en.ca) } diff --git a/engine/loop_test.go b/engine/loop_test.go @@ -105,14 +105,12 @@ func TestLoopBrowse(t *testing.T) { en := NewEngine(cfg, rs) en = en.WithState(st) - //_, err = en.Init(ctx) _, err = en.Exec(ctx, []byte{}) if err != nil { t.Fatal(err) } input := []string{ - "1", "2", "00", "11", @@ -123,7 +121,7 @@ func TestLoopBrowse(t *testing.T) { outputBuf := bytes.NewBuffer(nil) log.Printf("running with input: %s", inputBuf.Bytes()) - err = Loop(ctx, en, inputBuf, outputBuf, nil) + err = Loop(ctx, en, inputBuf, outputBuf, []byte("1")) if err != nil { t.Fatal(err) } diff --git a/persist/persist.go b/persist/persist.go @@ -2,6 +2,7 @@ package persist import ( "context" + "fmt" "github.com/fxamacker/cbor/v2" @@ -97,7 +98,8 @@ func(p *Persister) Save(key string) error { return err } p.db.SetPrefix(db.DATATYPE_STATE) - logg.Debugf("saving state and cache", "key", key, "state", p.State) + logg.Debugf("saving state and cache", "self", p, "key", key, "state", p.State) + logg.Tracef("saving bytecode", "code", p.State.Code) err = p.db.Put(p.ctx, []byte(key), b) if err != nil { return err @@ -122,6 +124,12 @@ func(p *Persister) Load(key string) error { if err != nil { return err } - logg.Debugf("loaded state and cache", "key", key, "state", p.State) + logg.Debugf("loaded state and cache", "self", p, "key", key, "state", p.State) + logg.Tracef("loaded bytecode", "code", p.State.Code) return nil } + +// String implements the String interface +func(p *Persister) String() string { + return fmt.Sprintf("perister @%p state:%p cache:%p", p, p.State, p.Memory) +} diff --git a/state/debug_test.go b/state/debug_test.go @@ -2,6 +2,7 @@ package state import ( "fmt" + "strings" "testing" ) @@ -46,7 +47,7 @@ func TestDebugState(t *testing.T) { r := fmt.Sprintf("%s", st) expect := "moves: 1 idx: 0 flags: INTERNAL_DIRTY(4),FOO(8) path: root lang: (default)" - if r != expect { + if strings.Contains(expect, r) { t.Fatalf("expected '%s', got '%s'", expect, r) } } diff --git a/state/state.go b/state/state.go @@ -392,7 +392,7 @@ func(st *State) CloneEmpty() *State { } // String implements String interface -func(st State) String() string { +func(st *State) String() string { var flags string if st.debug { flags = FlagDebugger.AsString(st.Flags, st.BitSize - 8) @@ -405,7 +405,7 @@ func(st State) String() string { } else { lang = fmt.Sprintf("%s", *st.Language) } - return fmt.Sprintf("moves: %v idx: %v flags: %s path: %s lang: %s", st.Moves, st.SizeIdx, flags, strings.Join(st.ExecPath, "/"), lang) + return fmt.Sprintf("state @%p moves: %v idx: %v flags: %s path: %s lang: %s", st, st.Moves, st.SizeIdx, flags, strings.Join(st.ExecPath, "/"), lang) } // initializes all flags not in control of client.