Skip to content
Snippets Groups Projects
Commit d192817b authored by Jake Taylor's avatar Jake Taylor :lips:
Browse files

Merge branch 'hotfix/ContextIndexedDb' into 'release'

adjust structure of context deadline in impl.go

See merge request !113
parents 1245f8e6 d03ca644
No related branches found
No related tags found
3 merge requests!116XX-4684 / Use new wasm-utils repo,!113adjust structure of context deadline in impl.go,!67fix for latest client release
...@@ -121,19 +121,19 @@ func (w *wasmModel) deleteMsgByChannel(channelID *id.ID) error { ...@@ -121,19 +121,19 @@ func (w *wasmModel) deleteMsgByChannel(channelID *id.ID) error {
"Unable to get Index: %+v", err) "Unable to get Index: %+v", err)
} }
// Perform the operation // Set up the operation
keyRange, err := idb.NewKeyRangeOnly(impl.EncodeBytes(channelID.Marshal())) keyRange, err := idb.NewKeyRangeOnly(impl.EncodeBytes(channelID.Marshal()))
cursorRequest, err := index.OpenCursorRange(keyRange, idb.CursorNext) cursorRequest, err := index.OpenCursorRange(keyRange, idb.CursorNext)
if err != nil { if err != nil {
return errors.WithMessagef(parentErr, "Unable to open Cursor: %+v", err) return errors.WithMessagef(parentErr, "Unable to open Cursor: %+v", err)
} }
ctx, cancel := impl.NewContext()
err = cursorRequest.Iter(ctx, // Perform the operation
err = impl.SendCursorRequest(cursorRequest,
func(cursor *idb.CursorWithValue) error { func(cursor *idb.CursorWithValue) error {
_, err := cursor.Delete() _, err := cursor.Delete()
return err return err
}) })
cancel()
if err != nil { if err != nil {
return errors.WithMessagef(parentErr, return errors.WithMessagef(parentErr,
"Unable to delete Message data: %+v", err) "Unable to delete Message data: %+v", err)
......
...@@ -81,6 +81,8 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher, ...@@ -81,6 +81,8 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
db, err := openRequest.Await(ctx) db, err := openRequest.Await(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} else if ctx.Err() != nil {
return nil, ctx.Err()
} }
wrapper := &wasmModel{ wrapper := &wasmModel{
...@@ -90,7 +92,6 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher, ...@@ -90,7 +92,6 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
deletedMessageCB: deletedMessageCB, deletedMessageCB: deletedMessageCB,
mutedUserCB: mutedUserCB, mutedUserCB: mutedUserCB,
} }
return wrapper, nil return wrapper, nil
} }
......
...@@ -75,10 +75,11 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher, ...@@ -75,10 +75,11 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher,
db, err := openRequest.Await(ctx) db, err := openRequest.Await(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} else if ctx.Err() != nil {
return nil, ctx.Err()
} }
wrapper := &wasmModel{db: db, receivedMessageCB: cb, cipher: encryption} wrapper := &wasmModel{db: db, receivedMessageCB: cb, cipher: encryption}
return wrapper, nil return wrapper, nil
} }
......
...@@ -30,9 +30,6 @@ const ( ...@@ -30,9 +30,6 @@ const (
// ErrDoesNotExist is an error string for got undefined on Get operations. // ErrDoesNotExist is an error string for got undefined on Get operations.
ErrDoesNotExist = "result is undefined" ErrDoesNotExist = "result is undefined"
// ErrUniqueConstraint is an error string for failed uniqueness inserts.
ErrUniqueConstraint = "at least one key does not satisfy the uniqueness requirements"
) )
// NewContext builds a context for indexedDb operations. // NewContext builds a context for indexedDb operations.
...@@ -45,6 +42,31 @@ func EncodeBytes(input []byte) js.Value { ...@@ -45,6 +42,31 @@ func EncodeBytes(input []byte) js.Value {
return js.ValueOf(base64.StdEncoding.EncodeToString(input)) return js.ValueOf(base64.StdEncoding.EncodeToString(input))
} }
// SendRequest is a wrapper for the request.Await() method providing a timeout.
func SendRequest(request *idb.Request) (js.Value, error) {
ctx, cancel := NewContext()
defer cancel()
result, err := request.Await(ctx)
if err != nil {
return js.Undefined(), err
} else if ctx.Err() != nil {
return js.Undefined(), ctx.Err()
}
return result, nil
}
// SendCursorRequest is a wrapper for the cursorRequest.Await() method providing a timeout.
func SendCursorRequest(cur *idb.CursorWithValueRequest,
iterFunc func(cursor *idb.CursorWithValue) error) error {
ctx, cancel := NewContext()
defer cancel()
err := cur.Iter(ctx, iterFunc)
if ctx.Err() != nil {
return ctx.Err()
}
return err
}
// Get is a generic helper for getting values from the given [idb.ObjectStore]. // Get is a generic helper for getting values from the given [idb.ObjectStore].
// Only usable by primary key. // Only usable by primary key.
func Get(db *idb.Database, objectStoreName string, key js.Value) (js.Value, error) { func Get(db *idb.Database, objectStoreName string, key js.Value) (js.Value, error) {
...@@ -62,22 +84,18 @@ func Get(db *idb.Database, objectStoreName string, key js.Value) (js.Value, erro ...@@ -62,22 +84,18 @@ func Get(db *idb.Database, objectStoreName string, key js.Value) (js.Value, erro
"Unable to get ObjectStore: %+v", err) "Unable to get ObjectStore: %+v", err)
} }
// Perform the operation // Set up the operation
getRequest, err := store.Get(key) getRequest, err := store.Get(key)
if err != nil { if err != nil {
return js.Undefined(), errors.WithMessagef(parentErr, return js.Undefined(), errors.WithMessagef(parentErr,
"Unable to Get from ObjectStore: %+v", err) "Unable to Get from ObjectStore: %+v", err)
} }
// Wait for the operation to return // Perform the operation
ctx, cancel := NewContext() resultObj, err := SendRequest(getRequest)
resultObj, err := getRequest.Await(ctx)
cancel()
if err != nil { if err != nil {
return js.Undefined(), errors.WithMessagef(parentErr, return js.Undefined(), errors.WithMessagef(parentErr,
"Unable to get from ObjectStore: %+v", err) "Unable to get from ObjectStore: %+v", err)
} else if err = ctx.Err(); errors.Is(err, context.DeadlineExceeded) {
return js.Null(), errors.Wrapf(err, "timed out after %s", dbTimeout)
} else if resultObj.IsUndefined() { } else if resultObj.IsUndefined() {
return js.Undefined(), errors.WithMessagef(parentErr, return js.Undefined(), errors.WithMessagef(parentErr,
"Unable to get from ObjectStore: %s", ErrDoesNotExist) "Unable to get from ObjectStore: %s", ErrDoesNotExist)
...@@ -105,14 +123,15 @@ func GetAll(db *idb.Database, objectStoreName string) ([]js.Value, error) { ...@@ -105,14 +123,15 @@ func GetAll(db *idb.Database, objectStoreName string) ([]js.Value, error) {
"Unable to get ObjectStore: %+v", err) "Unable to get ObjectStore: %+v", err)
} }
// Perform the operation // Set up the operation
result := make([]js.Value, 0)
cursorRequest, err := store.OpenCursor(idb.CursorNext) cursorRequest, err := store.OpenCursor(idb.CursorNext)
if err != nil { if err != nil {
return nil, errors.WithMessagef(parentErr, "Unable to open Cursor: %+v", err) return nil, errors.WithMessagef(parentErr, "Unable to open Cursor: %+v", err)
} }
ctx, cancel := NewContext() result := make([]js.Value, 0)
err = cursorRequest.Iter(ctx,
// Perform the operation
err = SendCursorRequest(cursorRequest,
func(cursor *idb.CursorWithValue) error { func(cursor *idb.CursorWithValue) error {
row, err := cursor.Value() row, err := cursor.Value()
if err != nil { if err != nil {
...@@ -121,7 +140,6 @@ func GetAll(db *idb.Database, objectStoreName string) ([]js.Value, error) { ...@@ -121,7 +140,6 @@ func GetAll(db *idb.Database, objectStoreName string) ([]js.Value, error) {
result = append(result, row) result = append(result, row)
return nil return nil
}) })
cancel()
if err != nil { if err != nil {
return nil, errors.WithMessagef(parentErr, err.Error()) return nil, errors.WithMessagef(parentErr, err.Error())
} }
...@@ -152,22 +170,18 @@ func GetIndex(db *idb.Database, objectStoreName, ...@@ -152,22 +170,18 @@ func GetIndex(db *idb.Database, objectStoreName,
"Unable to get Index: %+v", err) "Unable to get Index: %+v", err)
} }
// Perform the operation // Set up the operation
getRequest, err := idx.Get(key) getRequest, err := idx.Get(key)
if err != nil { if err != nil {
return js.Undefined(), errors.WithMessagef(parentErr, return js.Undefined(), errors.WithMessagef(parentErr,
"Unable to Get from ObjectStore: %+v", err) "Unable to Get from ObjectStore: %+v", err)
} }
// Wait for the operation to return // Perform the operation
ctx, cancel := NewContext() resultObj, err := SendRequest(getRequest)
resultObj, err := getRequest.Await(ctx)
cancel()
if err != nil { if err != nil {
return js.Undefined(), errors.WithMessagef(parentErr, return js.Undefined(), errors.WithMessagef(parentErr,
"Unable to get from ObjectStore: %+v", err) "Unable to get from ObjectStore: %+v", err)
} else if err = ctx.Err(); errors.Is(err, context.DeadlineExceeded) {
return js.Null(), errors.Wrapf(err, "timed out after %s", dbTimeout)
} else if resultObj.IsUndefined() { } else if resultObj.IsUndefined() {
return js.Undefined(), errors.WithMessagef(parentErr, return js.Undefined(), errors.WithMessagef(parentErr,
"Unable to get from ObjectStore: %s", ErrDoesNotExist) "Unable to get from ObjectStore: %s", ErrDoesNotExist)
...@@ -193,25 +207,21 @@ func Put(db *idb.Database, objectStoreName string, value js.Value) (js.Value, er ...@@ -193,25 +207,21 @@ func Put(db *idb.Database, objectStoreName string, value js.Value) (js.Value, er
return js.Undefined(), errors.Errorf("Unable to get ObjectStore: %+v", err) return js.Undefined(), errors.Errorf("Unable to get ObjectStore: %+v", err)
} }
// Perform the operation // Set up the operation
request, err := store.Put(value) request, err := store.Put(value)
if err != nil { if err != nil {
return js.Undefined(), errors.Errorf("Unable to Put: %+v", err) return js.Undefined(), errors.Errorf("Unable to Put: %+v", err)
} }
// Wait for the operation to return // Perform the operation
ctx, cancel := NewContext() resultObj, err := SendRequest(request)
result, err := request.Await(ctx)
cancel()
if err != nil { if err != nil {
return js.Undefined(), errors.Errorf("Putting value failed: %+v\n%s", return js.Undefined(), errors.Errorf("Putting value failed: %+v\n%s",
err, utils.JsToJson(value)) err, utils.JsToJson(value))
} else if err = ctx.Err(); errors.Is(err, context.DeadlineExceeded) {
return js.Null(), errors.Wrapf(err, "timed out after %s", dbTimeout)
} }
jww.DEBUG.Printf("Successfully put value in %s: %s", jww.DEBUG.Printf("Successfully put value in %s: %s",
objectStoreName, utils.JsToJson(value)) objectStoreName, utils.JsToJson(value))
return result, nil return resultObj, nil
} }
// Delete is a generic helper for removing values from the given // Delete is a generic helper for removing values from the given
...@@ -232,21 +242,17 @@ func Delete(db *idb.Database, objectStoreName string, key js.Value) error { ...@@ -232,21 +242,17 @@ func Delete(db *idb.Database, objectStoreName string, key js.Value) error {
} }
// Perform the operation // Perform the operation
_, err = store.Delete(key) deleteRequest, err := store.Delete(key)
if err != nil { if err != nil {
return errors.WithMessagef(parentErr, return errors.WithMessagef(parentErr,
"Unable to Delete from ObjectStore: %+v", err) "Unable to Delete from ObjectStore: %+v", err)
} }
// Wait for the operation to return // Perform the operation
ctx, cancel := NewContext() _, err = SendRequest(deleteRequest.Request)
err = txn.Await(ctx)
cancel()
if err != nil { if err != nil {
return errors.WithMessagef(parentErr, return errors.WithMessagef(parentErr,
"Unable to Delete from ObjectStore: %+v", err) "Unable to Delete from ObjectStore: %+v", err)
} else if err = ctx.Err(); errors.Is(err, context.DeadlineExceeded) {
return errors.Wrapf(err, "timed out after %s", dbTimeout)
} }
jww.DEBUG.Printf("Successfully deleted value at %s/%s", jww.DEBUG.Printf("Successfully deleted value at %s/%s",
objectStoreName, utils.JsToJson(key)) objectStoreName, utils.JsToJson(key))
...@@ -290,17 +296,18 @@ func Dump(db *idb.Database, objectStoreName string) ([]string, error) { ...@@ -290,17 +296,18 @@ func Dump(db *idb.Database, objectStoreName string) ([]string, error) {
return nil, errors.WithMessagef(parentErr, return nil, errors.WithMessagef(parentErr,
"Unable to get ObjectStore: %+v", err) "Unable to get ObjectStore: %+v", err)
} }
// Set up the operation
cursorRequest, err := store.OpenCursor(idb.CursorNext) cursorRequest, err := store.OpenCursor(idb.CursorNext)
if err != nil { if err != nil {
return nil, errors.WithMessagef(parentErr, return nil, errors.WithMessagef(parentErr,
"Unable to open Cursor: %+v", err) "Unable to open Cursor: %+v", err)
} }
// Run the query
jww.DEBUG.Printf("%s values:", objectStoreName) jww.DEBUG.Printf("%s values:", objectStoreName)
results := make([]string, 0) results := make([]string, 0)
ctx, cancel := NewContext()
err = cursorRequest.Iter(ctx, // Perform the operation
err = SendCursorRequest(cursorRequest,
func(cursor *idb.CursorWithValue) error { func(cursor *idb.CursorWithValue) error {
value, err := cursor.Value() value, err := cursor.Value()
if err != nil { if err != nil {
...@@ -311,7 +318,6 @@ func Dump(db *idb.Database, objectStoreName string) ([]string, error) { ...@@ -311,7 +318,6 @@ func Dump(db *idb.Database, objectStoreName string) ([]string, error) {
jww.DEBUG.Printf("- %v", valueStr) jww.DEBUG.Printf("- %v", valueStr)
return nil return nil
}) })
cancel()
if err != nil { if err != nil {
return nil, errors.WithMessagef(parentErr, return nil, errors.WithMessagef(parentErr,
"Unable to dump ObjectStore: %+v", err) "Unable to dump ObjectStore: %+v", err)
......
...@@ -11,9 +11,11 @@ package impl ...@@ -11,9 +11,11 @@ package impl
import ( import (
"github.com/hack-pad/go-indexeddb/idb" "github.com/hack-pad/go-indexeddb/idb"
jww "github.com/spf13/jwalterweatherman"
"strings" "strings"
"syscall/js" "syscall/js"
"testing" "testing"
"time"
) )
// Error path: Tests that Get returns an error when trying to get a message that // Error path: Tests that Get returns an error when trying to get a message that
...@@ -92,3 +94,52 @@ func newTestDB(name, index string, t *testing.T) *idb.Database { ...@@ -92,3 +94,52 @@ func newTestDB(name, index string, t *testing.T) *idb.Database {
return db return db
} }
// TestBenchmark ensures IndexedDb can take at least n operations per second.
func TestBenchmark(t *testing.T) {
jww.SetStdoutThreshold(jww.LevelInfo)
benchmarkDb(50, t)
}
// benchmarkDb sends n operations to IndexedDb and prints errors.
func benchmarkDb(n int, t *testing.T) {
jww.INFO.Printf("Benchmarking IndexedDb: %d total.", n)
objectStoreName := "test"
testValue := js.ValueOf(make(map[string]interface{}))
db := newTestDB(objectStoreName, "index", t)
type metric struct {
didSucceed bool
duration time.Duration
}
done := make(chan metric)
// Spawn n operations at the same time
startTime := time.Now()
for i := 0; i < n; i++ {
go func() {
opStart := time.Now()
_, err := Put(db, objectStoreName, testValue)
done <- metric{
didSucceed: err == nil,
duration: time.Since(opStart),
}
}()
}
// Wait for all to complete
didSucceed := true
for i := 0; i < n; i++ {
result := <-done
if !result.didSucceed {
didSucceed = false
}
jww.DEBUG.Printf("Operation time: %s", result.duration)
}
timeElapsed := time.Since(startTime)
jww.INFO.Printf("Benchmarking complete. Succeeded: %t\n"+
"Took %s, Average of %s.",
didSucceed, timeElapsed, timeElapsed/time.Duration(n))
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment