go-vise

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

commit accdb96d175cad33402d326f105f0d16bd409eb8
parent bd9758bd774b87abd39cf659e767d3c814af73c9
Author: lash <dev@holbrook.no>
Date:   Sun,  1 Sep 2024 22:47:59 +0100

Force db base for implementeds, testresource manual lock

Diffstat:
Mdb/db.go | 76+++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mdb/fs/fs.go | 3++-
Mdb/gdbm/gdbm.go | 3++-
Mdb/mem/mem.go | 3++-
Mdb/postgres/pg.go | 1+
Minternal/resourcetest/util.go | 4++++
Mrender/page_test.go | 1+
Mrender/size_test.go | 6++++++
Mresource/db.go | 10++++++++++
Mvm/runner_test.go | 20++++++++++++++++++++
10 files changed, 91 insertions(+), 36 deletions(-)

diff --git a/db/db.go b/db/db.go @@ -34,13 +34,23 @@ const ( // Db abstracts all data storage and retrieval as a key-value store type Db interface { - // Connect prepares the storage backend for use. May panic or error if called more than once. + // Connect prepares the storage backend for use. + // + // If called more than once, consecutive calls should be ignored. Connect(ctx context.Context, connStr string) error - // Close implements io.Closer. MUST be called before termination after a Connect(). + // Close implements io.Closer. + // + // MUST be called before termination after a Connect(). Close() error - // Get retrieves the value belonging to a key. Errors if the key does not exist, or if the retrieval otherwise fails. + // Get retrieves the value belonging to a key. + // + // Errors if the key does not exist, or if the retrieval otherwise fails. Get(ctx context.Context, key []byte) ([]byte, error) - // Put stores a value under a key. Any existing value will be replaced. Errors if the value could not be stored. + // Put stores a value under a key. + // + // Any existing value will be replaced. + // + // Errors if the value could not be stored. Put(ctx context.Context, key []byte, val []byte) error // SetPrefix sets the storage context prefix to use for consecutive Get and Put operations. SetPrefix(pfx uint8) @@ -51,6 +61,7 @@ type Db interface { // * DATATYPE_USERSTART SetSession(sessionId string) // SetLock disables modification of data that is readonly in the vm context. + // // If called with typ value 0, it will permanently lock all readonly members. SetLock(typ uint8, locked bool) error // Safe returns true if db is safe for use with a vm. @@ -94,6 +105,14 @@ type baseDb struct { seal bool } +// DbBase is a base class that must be extended by all db.Db implementers. +// +// It must be created with NewDbBase() +type DbBase struct { + *baseDb +} + +// NewDbBase instantiates a new DbBase. func NewDbBase() *DbBase { db := &DbBase{ baseDb: &baseDb{}, @@ -102,74 +121,65 @@ func NewDbBase() *DbBase { return db } -type DbBase struct { - *baseDb -} - -type BaseDb baseDb - // ensures default locking of read-only entries func(db *baseDb) defaultLock() { db.lock |= safeLock } -func(db *baseDb) Safe() bool { - return db.lock & safeLock == safeLock +func(bd *DbBase) Safe() bool { + return bd.baseDb.lock & safeLock == safeLock } -func(db *baseDb) Prefix() uint8 { - return db.pfx +func(bd *DbBase) Prefix() uint8 { + return bd.baseDb.pfx } // SetPrefix implements the Db interface. -func(db *baseDb) SetPrefix(pfx uint8) { - db.pfx = pfx +func(bd *DbBase) SetPrefix(pfx uint8) { + bd.baseDb.pfx = pfx } // SetLanguage implements the Db interface. -func(db *baseDb) SetLanguage(ln *lang.Language) { - db.lang = ln +func(bd *DbBase) SetLanguage(ln *lang.Language) { + bd.baseDb.lang = ln } // SetSession implements the Db interface. -func(db *baseDb) SetSession(sessionId string) { - db.sid = append([]byte(sessionId), 0x2E) +func(bd *DbBase) SetSession(sessionId string) { + bd.baseDb.sid = append([]byte(sessionId), 0x2E) } // SetLock implements the Db interface. -func(db *baseDb) SetLock(pfx uint8, lock bool) error { - if db.seal { +func(bd *DbBase) SetLock(pfx uint8, lock bool) error { + if bd.baseDb.seal { return errors.New("SetLock on sealed db") } if pfx == 0 { - db.seal = true - db.defaultLock() + bd.baseDb.defaultLock() + bd.baseDb.seal = true return nil } if lock { - db.lock |= pfx + bd.baseDb.lock |= pfx } else { - db.lock &= ^pfx + bd.baseDb.lock &= ^pfx } return nil } -func(db *baseDb) checkPut() bool { - return db.pfx & db.lock == 0 -} - // CheckPut returns true if the current selected data type can be written to. -func(db *baseDb) CheckPut() bool { - return db.checkPut() +func(bd *DbBase) CheckPut() bool { + return bd.baseDb.pfx & bd.baseDb.lock == 0 } // ToKey creates a DbKey within the current session context. // // TODO: hard to read, clean up -func(db *baseDb) ToKey(ctx context.Context, key []byte) (LookupKey, error) { +func(bd *DbBase) ToKey(ctx context.Context, key []byte) (LookupKey, error) { var ln *lang.Language var lk LookupKey var b []byte + db := bd.baseDb if db.pfx == DATATYPE_UNKNOWN { return lk, errors.New("datatype prefix cannot be UNKNOWN") } diff --git a/db/fs/fs.go b/db/fs/fs.go @@ -34,7 +34,8 @@ func NewFsDb() *fsDb { // Connect implements the Db interface. func(fdb *fsDb) Connect(ctx context.Context, connStr string) error { if fdb.dir != "" { - panic("already connected") + logg.WarnCtxf(ctx, "already connected", "conn", fdb.dir) + return nil } err := os.MkdirAll(connStr, 0700) if err != nil { diff --git a/db/gdbm/gdbm.go b/db/gdbm/gdbm.go @@ -28,7 +28,8 @@ func NewGdbmDb() *gdbmDb { // Connect implements Db func(gdb *gdbmDb) Connect(ctx context.Context, connStr string) error { if gdb.conn != nil { - panic("already connected") + logg.WarnCtxf(ctx, "already connected", "conn", gdb.conn) + return nil } var db *gdbm.Database _, err := os.Stat(connStr) diff --git a/db/mem/mem.go b/db/mem/mem.go @@ -31,7 +31,8 @@ func NewMemDb() *memDb { // Connect implements Db func(mdb *memDb) Connect(ctx context.Context, connStr string) error { if mdb.store != nil { - panic("already connected") + logg.WarnCtxf(ctx, "already connected") + return nil } mdb.store = make(map[string][]byte) return nil diff --git a/db/postgres/pg.go b/db/postgres/pg.go @@ -36,6 +36,7 @@ func(pdb *pgDb) WithSchema(schema string) *pgDb { // Connect implements Db. func(pdb *pgDb) Connect(ctx context.Context, connStr string) error { if pdb.conn != nil { + logg.WarnCtxf(ctx, "already connected", "conn", pdb.conn) panic("already connected") } var err error diff --git a/internal/resourcetest/util.go b/internal/resourcetest/util.go @@ -71,3 +71,7 @@ func(tr *TestResource) AddMenu(ctx context.Context, key string, val string) erro func(tr *TestResource) AddFunc(ctx context.Context, key string, fn resource.EntryFunc) { tr.AddLocalFunc(key, fn) } + +func(tr *TestResource) Lock() { + tr.db.SetLock(0, true) +} diff --git a/render/page_test.go b/render/page_test.go @@ -122,6 +122,7 @@ func TestWithError(t *testing.T) { ca := cache.NewCache() rs := resourcetest.NewTestResource() rs.AddTemplate(ctx, "foo", "bar") + rs.Lock() pg := NewPage(ca, rs) ca.Push() diff --git a/render/size_test.go b/render/size_test.go @@ -80,6 +80,7 @@ func TestSizeLimit(t *testing.T) { ca := cache.NewCache() mn := NewMenu() rs := newTestSizeResource() + rs.Lock() szr := NewSizer(128) pg := NewPage(ca, rs).WithMenu(mn).WithSizer(szr) ca.Push() @@ -129,6 +130,7 @@ func TestSizePages(t *testing.T) { ca := cache.NewCache() mn := NewMenu() rs := newTestSizeResource() + rs.Lock() szr := NewSizer(128) pg := NewPage(ca, rs).WithSizer(szr).WithMenu(mn) ca.Push() @@ -186,6 +188,7 @@ func TestManySizes(t *testing.T) { ca := cache.NewCache() mn := NewMenu() //.WithOutputSize(32) rs := newTestSizeResource() //.WithEntryFuncGetter(funcFor).WithTemplateGetter(getTemplate) + rs.Lock() //rs := TestSizeResource{ // mrs, //} @@ -216,6 +219,7 @@ func TestManySizesMenued(t *testing.T) { ca := cache.NewCache() mn := NewMenu() //.WithOutputSize(32) rs := newTestSizeResource() + rs.Lock() szr := NewSizer(uint32(i)) pg := NewPage(ca, rs).WithSizer(szr).WithMenu(mn) ca.Push() @@ -244,6 +248,7 @@ func TestMenuCollideSink(t *testing.T) { ca := cache.NewCache() rs := resourcetest.NewTestResource() rs.AddTemplate(ctx, "foo", "bar") + rs.Lock() szr := NewSizer(30) pg := NewPage(ca, rs).WithSizer(szr) ca.Push() @@ -277,6 +282,7 @@ func TestMenuSink(t *testing.T) { ca := cache.NewCache() rs := resourcetest.NewTestResource() rs.AddTemplate(ctx, "foo", "bar {{.baz}}") + rs.Lock() szr := NewSizer(45) mn := NewMenu().WithSink().WithBrowseConfig(DefaultBrowseConfig()) diff --git a/resource/db.go b/resource/db.go @@ -24,6 +24,9 @@ type DbResource struct { // // By default it will handle db.DATATYPE_TEPMLATE, db.DATATYPE_MENU and db.DATATYPE_BIN. func NewDbResource(store db.Db) *DbResource { + if !store.Safe() { + logg.Warnf("Db is not safe for use with resource. Make sure it is properly locked before issuing the first retrieval, or it will panic!") + } return &DbResource{ MenuResource: NewMenuResource(), db: store, @@ -49,8 +52,15 @@ func(g *DbResource) WithOnly(typ uint8) *DbResource { return g } +func(g *DbResource) mustSafe() { + if !g.db.Safe() { + panic("db unsafe for resource (db.Db.Safe() == false)") + } +} + // retrieve from underlying db. func(g *DbResource) fn(ctx context.Context, sym string) ([]byte, error) { + g.mustSafe() return g.db.Get(ctx, []byte(sym)) } diff --git a/vm/runner_test.go b/vm/runner_test.go @@ -68,6 +68,7 @@ func newTestResource(st *state.State) testResource { rs.AddBytecode(ctx, "ouf", b) rs.AddBytecode(ctx, "root", tr.RootCode) + return tr } @@ -180,6 +181,7 @@ func(r testResource) getCode(sym string) ([]byte, error) { func TestRun(t *testing.T) { st := state.NewState(5) rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil) @@ -201,6 +203,7 @@ func TestRun(t *testing.T) { func TestRunLoadRender(t *testing.T) { st := state.NewState(5) rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil) @@ -253,6 +256,7 @@ func TestRunLoadRender(t *testing.T) { func TestRunMultiple(t *testing.T) { st := state.NewState(5) rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil) @@ -271,6 +275,7 @@ func TestRunMultiple(t *testing.T) { func TestRunReload(t *testing.T) { st := state.NewState(5) rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() szr := render.NewSizer(128) vm := NewVm(&st, &rs, ca, szr) @@ -303,6 +308,7 @@ func TestRunReload(t *testing.T) { func TestHalt(t *testing.T) { st := state.NewState(5) rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil) @@ -328,6 +334,7 @@ func TestHalt(t *testing.T) { func TestRunArg(t *testing.T) { st := state.NewState(5) rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil) @@ -355,6 +362,7 @@ func TestRunArg(t *testing.T) { func TestRunInputHandler(t *testing.T) { st := state.NewState(5) rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil) @@ -383,6 +391,7 @@ func TestRunInputHandler(t *testing.T) { func TestRunArgInvalid(t *testing.T) { st := state.NewState(5) rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil) @@ -425,6 +434,7 @@ func TestRunMenu(t *testing.T) { ctx := context.Background() rs.AddBytecode(ctx, "foo", []byte{}) + rs.Lock() b := NewLine(nil, MOVE, []string{"foo"}, nil, nil) b = NewLine(b, MOUT, []string{"one", "0"}, nil, nil) b = NewLine(b, MOUT, []string{"two", "1"}, nil, nil) @@ -461,6 +471,7 @@ func TestRunMenuBrowse(t *testing.T) { ctx := context.Background() rs.AddBytecode(ctx, "foo", []byte{}) + rs.Lock() b := NewLine(nil, MOVE, []string{"foo"}, nil, nil) b = NewLine(b, MOUT, []string{"one", "0"}, nil, nil) b = NewLine(b, MOUT, []string{"two", "1"}, nil, nil) @@ -488,6 +499,7 @@ func TestRunMenuBrowse(t *testing.T) { func TestRunReturn(t *testing.T) { st := state.NewState(5) rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil) @@ -524,6 +536,7 @@ func TestRunReturn(t *testing.T) { func TestRunLoadInput(t *testing.T) { st := state.NewState(5) rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil) @@ -567,6 +580,7 @@ func TestInputBranch(t *testing.T) { b = NewLine(b, CATCH, []string{"one"}, []byte{9}, []uint8{1}) rs.RootCode = b rs.AddBytecode(ctx, "root", rs.RootCode) + rs.Lock() ctx := context.Background() @@ -594,6 +608,7 @@ func TestInputBranch(t *testing.T) { func TestInputIgnore(t *testing.T) { st := state.NewState(5) rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil) @@ -622,6 +637,7 @@ func TestInputIgnore(t *testing.T) { func TestInputIgnoreWildcard(t *testing.T) { st := state.NewState(5) rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil) @@ -649,6 +665,7 @@ func TestInputIgnoreWildcard(t *testing.T) { func TestCatchCleanMenu(t *testing.T) { st := state.NewState(5) rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil) @@ -685,6 +702,7 @@ func TestCatchCleanMenu(t *testing.T) { func TestSetLang(t *testing.T) { st := state.NewState(0) rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil) @@ -711,6 +729,7 @@ func TestLoadError(t *testing.T) { st := state.NewState(0) st.UseDebug() rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil) @@ -744,6 +763,7 @@ func TestMatchFlag(t *testing.T) { st := state.NewState(1) st.UseDebug() rs := newTestResource(&st) + rs.Lock() ca := cache.NewCache() vm := NewVm(&st, &rs, ca, nil)