From 1df11d5dfc1b9f73f4b0a29d7e2535735f4ab1bd Mon Sep 17 00:00:00 2001 From: Alex Towle Date: Wed, 19 Aug 2020 19:04:25 -0500 Subject: [PATCH] Addressed issues uncovered during a personal review --- Makefile | 2 +- db/dexie_datastore.go | 89 ++++++++++++++++------- db/dexie_implementation.go | 51 +++++-------- packages/mesh-browser/go/jsutil/jsutil.go | 49 +++++++++++++ 4 files changed, 129 insertions(+), 62 deletions(-) diff --git a/Makefile b/Makefile index 784618348..2bbccc085 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ test-key-value-stores-go: .PHONY: test-key-value-stores-wasm test-key-value-stores-wasm: - WASM_INIT_FILE="$$(pwd)/packages/mesh-browser-shim/dist/browser_shim.js" GOOS=js GOARCH=wasm ENABLE_KEY_VALUE_TESTS=true go test ./db -tags=browser -exec="$$GOPATH/bin/wasmbrowsertest" + WASM_INIT_FILE="$$(pwd)/packages/mesh-browser-shim/dist/browser_shim.js" GOOS=js GOARCH=wasm ENABLE_KEY_VALUE_TESTS=true go test ./db -timeout 20m -tags=browser -exec="$$GOPATH/bin/wasmbrowsertest" .PHONY: test-go-serial diff --git a/db/dexie_datastore.go b/db/dexie_datastore.go index b859ab3b0..1c85ec916 100644 --- a/db/dexie_datastore.go +++ b/db/dexie_datastore.go @@ -4,7 +4,6 @@ package db import ( "context" - "fmt" "syscall/js" "github.com/0xProject/0x-mesh/packages/mesh-browser/go/jsutil" @@ -54,12 +53,11 @@ type Operation struct { } func (o *Operation) JSValue() js.Value { - uint8Array := js.Global().Get("Uint8Array").New(len(o.value)) - js.CopyBytesToJS(uint8Array, o.value) + jsBytes, _ := jsutil.CopyBytesToJS(o.value) return js.ValueOf(map[string]interface{}{ "operationType": int(o.operationType), "key": o.key.String(), - "value": uint8Array, + "value": jsBytes, }) } @@ -97,38 +95,45 @@ func (b *Batch) Delete(key ds.Key) error { // Commit performs a batch of operations on the Dexie datastore. In this implementation, // all of these operations occur in the same transactional context. -func (b *Batch) Commit() error { +func (b *Batch) Commit() (err error) { + defer func() { + if r := recover(); r != nil { + err = jsutil.RecoverError(r) + } + }() + convertibleOperations := make([]interface{}, len(b.operations)) for i, operation := range b.operations { convertibleOperations[i] = interface{}(operation) } - _, err := jsutil.AwaitPromiseContext(b.ctx, b.dexieStore.Call("commitAsync", convertibleOperations)) + _, err = jsutil.AwaitPromiseContext(b.ctx, b.dexieStore.Call("commitAsync", convertibleOperations)) if err != nil { return convertJSError(err) } return nil } -func (d *Datastore) Get(key ds.Key) ([]byte, error) { - // FIXME - Remove Defer - var jsResult js.Value +func (d *Datastore) Get(key ds.Key) (_ []byte, err error) { defer func() { if r := recover(); r != nil { - fmt.Println(jsResult.String()) - panic(r) + err = jsutil.RecoverError(r) } }() - var err error - jsResult, err = jsutil.AwaitPromiseContext(d.ctx, d.dexieStore.Call("getAsync", key.String())) + + jsResult, err := jsutil.AwaitPromiseContext(d.ctx, d.dexieStore.Call("getAsync", key.String())) if err != nil { return nil, convertJSError(err) } - result := make([]byte, jsResult.Get("length").Int()) - js.CopyBytesToGo(result, jsResult) - return result, nil + return jsutil.CopyBytesToGo(jsResult) } -func (d *Datastore) Has(key ds.Key) (bool, error) { +func (d *Datastore) Has(key ds.Key) (_ bool, err error) { + defer func() { + if r := recover(); r != nil { + err = jsutil.RecoverError(r) + } + }() + jsResult, err := jsutil.AwaitPromiseContext(d.ctx, d.dexieStore.Call("hasAsync", key.String())) if err != nil { return false, convertJSError(err) @@ -136,7 +141,13 @@ func (d *Datastore) Has(key ds.Key) (bool, error) { return jsResult.Bool(), nil } -func (d *Datastore) GetSize(key ds.Key) (size int, err error) { +func (d *Datastore) GetSize(key ds.Key) (_ int, err error) { + defer func() { + if r := recover(); r != nil { + err = jsutil.RecoverError(r) + } + }() + jsResult, err := jsutil.AwaitPromiseContext(d.ctx, d.dexieStore.Call("getSizeAsync", key.String())) if err != nil { return -1, convertJSError(err) @@ -144,7 +155,13 @@ func (d *Datastore) GetSize(key ds.Key) (size int, err error) { return jsResult.Int(), nil } -func (d *Datastore) Query(q dsq.Query) (dsq.Results, error) { +func (d *Datastore) Query(q dsq.Query) (_ dsq.Results, err error) { + defer func() { + if r := recover(); r != nil { + err = jsutil.RecoverError(r) + } + }() + jsResults, err := jsutil.AwaitPromiseContext(d.ctx, d.dexieStore.Call("queryAsync", q.Prefix)) if err != nil { return nil, convertJSError(err) @@ -152,11 +169,13 @@ func (d *Datastore) Query(q dsq.Query) (dsq.Results, error) { entries := make([]dsq.Entry, jsResults.Get("length").Int()) for i := 0; i < jsResults.Get("length").Int(); i++ { jsResult := jsResults.Index(i) - value := make([]byte, jsResult.Get("size").Int()) - js.CopyBytesToGo(value, jsResult.Get("value")) + jsBytes, err := jsutil.CopyBytesToGo(jsResult.Get("value")) + if err != nil { + return nil, err + } entries[i] = dsq.Entry{ Key: jsResult.Get("key").String(), - Value: value, + Value: jsBytes, Size: jsResult.Get("size").Int(), } } @@ -185,18 +204,32 @@ func (d *Datastore) Query(q dsq.Query) (dsq.Results, error) { return dsq.ResultsWithEntries(q, filteredEntries), nil } -func (d *Datastore) Put(key ds.Key, value []byte) error { - uint8Array := js.Global().Get("Uint8Array").New(len(value)) - js.CopyBytesToJS(uint8Array, value) - _, err := jsutil.AwaitPromiseContext(d.ctx, d.dexieStore.Call("putAsync", key.String(), uint8Array)) +func (d *Datastore) Put(key ds.Key, value []byte) (err error) { + defer func() { + if r := recover(); r != nil { + err = jsutil.RecoverError(r) + } + }() + + jsBytes, err := jsutil.CopyBytesToJS(value) + if err != nil { + return err + } + _, err = jsutil.AwaitPromiseContext(d.ctx, d.dexieStore.Call("putAsync", key.String(), jsBytes)) if err != nil { return convertJSError(err) } return nil } -func (d *Datastore) Delete(key ds.Key) error { - _, err := jsutil.AwaitPromiseContext(d.ctx, d.dexieStore.Call("deleteAsync", key.String())) +func (d *Datastore) Delete(key ds.Key) (err error) { + defer func() { + if r := recover(); r != nil { + err = jsutil.RecoverError(r) + } + }() + + _, err = jsutil.AwaitPromiseContext(d.ctx, d.dexieStore.Call("deleteAsync", key.String())) if err != nil { return convertJSError(err) } diff --git a/db/dexie_implementation.go b/db/dexie_implementation.go index 004c1a3a3..f251a4b7d 100644 --- a/db/dexie_implementation.go +++ b/db/dexie_implementation.go @@ -8,7 +8,6 @@ import ( "fmt" "math/big" "path/filepath" - "runtime/debug" "syscall/js" "time" @@ -66,7 +65,7 @@ func New(ctx context.Context, opts *Options) (database *DB, err error) { } defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() newDexieDatabase := js.Global().Get("__mesh_dexie_newDatabase__") @@ -110,7 +109,7 @@ func (db *DB) DHTStore() ds.Batching { func (db *DB) AddOrders(orders []*types.OrderWithMetadata) (alreadyStored []common.Hash, added []*types.OrderWithMetadata, removed []*types.OrderWithMetadata, err error) { defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -146,7 +145,7 @@ func (db *DB) AddOrders(orders []*types.OrderWithMetadata) (alreadyStored []comm func (db *DB) GetOrder(hash common.Hash) (order *types.OrderWithMetadata, err error) { defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -165,7 +164,7 @@ func (db *DB) GetOrder(hash common.Hash) (order *types.OrderWithMetadata, err er func (db *DB) GetOrderStatuses(hashes []common.Hash) (statuses []*StoredOrderStatus, err error) { defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -201,7 +200,7 @@ func (db *DB) FindOrders(query *OrderQuery) (orders []*types.OrderWithMetadata, } defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -224,7 +223,7 @@ func (db *DB) CountOrders(query *OrderQuery) (count int, err error) { } defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -240,7 +239,7 @@ func (db *DB) CountOrders(query *OrderQuery) (count int, err error) { func (db *DB) DeleteOrder(hash common.Hash) (err error) { defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -258,7 +257,7 @@ func (db *DB) DeleteOrders(query *OrderQuery) (deletedOrders []*types.OrderWithM } defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -278,7 +277,7 @@ func (db *DB) DeleteOrders(query *OrderQuery) (deletedOrders []*types.OrderWithM func (db *DB) UpdateOrder(hash common.Hash, updateFunc func(existingOrder *types.OrderWithMetadata) (updatedOrder *types.OrderWithMetadata, err error)) (err error) { defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -311,7 +310,7 @@ func (db *DB) UpdateOrder(hash common.Hash, updateFunc func(existingOrder *types func (db *DB) AddMiniHeaders(miniHeaders []*types.MiniHeader) (added []*types.MiniHeader, removed []*types.MiniHeader, err error) { defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -331,7 +330,7 @@ func (db *DB) AddMiniHeaders(miniHeaders []*types.MiniHeader) (added []*types.Mi func (db *DB) ResetMiniHeaders(newMiniHeaders []*types.MiniHeader) (err error) { defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -347,7 +346,7 @@ func (db *DB) ResetMiniHeaders(newMiniHeaders []*types.MiniHeader) (err error) { func (db *DB) GetMiniHeader(hash common.Hash) (miniHeader *types.MiniHeader, err error) { defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -365,7 +364,7 @@ func (db *DB) FindMiniHeaders(query *MiniHeaderQuery) (miniHeaders []*types.Mini } defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -381,7 +380,7 @@ func (db *DB) FindMiniHeaders(query *MiniHeaderQuery) (miniHeaders []*types.Mini func (db *DB) DeleteMiniHeader(hash common.Hash) (err error) { defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -399,7 +398,7 @@ func (db *DB) DeleteMiniHeaders(query *MiniHeaderQuery) (deleted []*types.MiniHe } defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -415,7 +414,7 @@ func (db *DB) DeleteMiniHeaders(query *MiniHeaderQuery) (deleted []*types.MiniHe func (db *DB) GetMetadata() (metadata *types.Metadata, err error) { defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -434,7 +433,7 @@ func (db *DB) GetMetadata() (metadata *types.Metadata, err error) { func (db *DB) SaveMetadata(metadata *types.Metadata) (err error) { defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -454,7 +453,7 @@ func (db *DB) SaveMetadata(metadata *types.Metadata) (err error) { func (db *DB) UpdateMetadata(updateFunc func(oldmetadata *types.Metadata) (newMetadata *types.Metadata)) (err error) { defer func() { if r := recover(); r != nil { - err = recoverError(r) + err = jsutil.RecoverError(r) } }() start := time.Now() @@ -481,20 +480,6 @@ func (db *DB) UpdateMetadata(updateFunc func(oldmetadata *types.Metadata) (newMe return nil } -func recoverError(e interface{}) error { - if e != nil { - debug.PrintStack() - } - switch e := e.(type) { - case error: - return e - case string: - return errors.New(e) - default: - return fmt.Errorf("unexpected JavaScript error: (%T) %v", e, e) - } -} - func convertJSError(e error) error { switch e := e.(type) { case js.Error: diff --git a/packages/mesh-browser/go/jsutil/jsutil.go b/packages/mesh-browser/go/jsutil/jsutil.go index d8681986a..3127c470f 100644 --- a/packages/mesh-browser/go/jsutil/jsutil.go +++ b/packages/mesh-browser/go/jsutil/jsutil.go @@ -7,10 +7,43 @@ package jsutil import ( "context" "encoding/json" + "errors" "fmt" + "runtime/debug" "syscall/js" ) +func CopyBytesToJS(bytes []byte) (jsBytes js.Value, err error) { + defer func() { + if r := recover(); r != nil { + err = RecoverError(r) + } + }() + + jsBytes = js.Global().Get("Uint8Array").New(len(bytes)) + copied := js.CopyBytesToJS(jsBytes, bytes) + if copied != len(bytes) { + return js.Undefined(), fmt.Errorf("should have copied %d bytes to JS but only copied %d", len(bytes), copied) + } + return jsBytes, nil +} + +func CopyBytesToGo(jsBytes js.Value) (bytes []byte, err error) { + defer func() { + if r := recover(); r != nil { + err = RecoverError(r) + } + }() + + jsBytesLength := jsBytes.Get("length").Int() + bytes = make([]byte, jsBytesLength) + copied := js.CopyBytesToGo(bytes, jsBytes) + if copied != jsBytesLength { + return nil, fmt.Errorf("should have copied %d bytes to Go but only copied %d", jsBytesLength, copied) + } + return bytes, nil +} + // ErrorToJS converts a Go error to a JavaScript Error. func ErrorToJS(err error) js.Value { return js.Global().Get("Error").New(err.Error()) @@ -123,3 +156,19 @@ func InefficientlyConvertFromJS(jsValue js.Value, value interface{}) (err error) jsonString := js.Global().Get("JSON").Call("stringify", jsValue) return json.Unmarshal([]byte(jsonString.String()), value) } + +// RecoverError allows a function to recover from a thrown Javascript error if +// called inside of a deferred function with a recover statement. +func RecoverError(e interface{}) error { + if e != nil { + debug.PrintStack() + } + switch e := e.(type) { + case error: + return e + case string: + return errors.New(e) + default: + return fmt.Errorf("unexpected JavaScript error: (%T) %v", e, e) + } +}