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:
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)