Skip to content

Commit

Permalink
fix: create local doc cache loader
Browse files Browse the repository at this point in the history
This change creates a local JSON-LD Document Cache loader using a cache of type *sync.Map
to avoid 'error on cache writes' panics.

This change also introduces the 'benchmark' Make target to support benchmarking code in the framework.

closes hyperledger-archives#1833 for the fatal error mentioned in the comment
Also related and closes the discussion about the same panic topic in hyperledger-archives#2487

Signed-off-by: Baha Shaaban <[email protected]>
  • Loading branch information
Baha Shaaban committed Mar 9, 2021
1 parent ada2929 commit f83e0fc
Show file tree
Hide file tree
Showing 13 changed files with 494 additions and 28 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ license:
unit-test: mocks
@scripts/check_unit.sh

.PHONY: benchmark
benchmark:
@scripts/check_bench.sh

.PHONY: unit-test-wasm
unit-test-wasm: export GOBIN=$(GOBIN_PATH)
unit-test-wasm: depend
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKe
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hyperledger/aries-framework-go/component/storageutil v0.0.0-20210224230531-58e1368e5661 h1:VmyvcZ2fMyrE7UCXPSAsMwjKOm5GlpxJSb8sWsTkeQY=
github.com/hyperledger/aries-framework-go/component/storageutil v0.0.0-20210224230531-58e1368e5661/go.mod h1:XaPVDJcbQT8BKmThfQdWPc+hgicHFAQzSOavHw2gn/4=
github.com/hyperledger/aries-framework-go/component/storageutil v0.0.0-20210308190853-e0d06c12b672 h1:ANpcYtXSEMj++pomDnVhLQkQtaIXdHr0IVASVZadGB0=
github.com/hyperledger/aries-framework-go/spi v0.0.0-20210205153949-f852f978a0d6/go.mod h1:fDr9wW00GJJl1lR1SFHmJW8utIocdvjO5RNhAYS05EY=
github.com/hyperledger/aries-framework-go/spi v0.0.0-20210210184327-0b9d0fd4c34e/go.mod h1:fDr9wW00GJJl1lR1SFHmJW8utIocdvjO5RNhAYS05EY=
github.com/hyperledger/aries-framework-go/spi v0.0.0-20210224230531-58e1368e5661 h1:Je5vn+j5wYAC8+uuL6ZabK1VEPX6/fdgNlMA8dJjCFI=
Expand Down
2 changes: 1 addition & 1 deletion pkg/doc/did/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1322,7 +1322,7 @@ func BuildDoc(opts ...DocOption) *Doc {

// CachingJSONLDLoader creates JSON-LD CachingDocumentLoader with preloaded base JSON-LD DID and security contexts.
func CachingJSONLDLoader() ld.DocumentLoader {
loader := jld.NewCachingDocumentLoader()
loader := jld.NewDefaultCachingDocumentLoader()

cacheContext := func(source, url string) {
reader, _ := ld.DocumentFromReader(strings.NewReader(source)) //nolint:errcheck
Expand Down
73 changes: 73 additions & 0 deletions pkg/doc/jsonld/document_loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package jsonld

import (
"errors"
"sync"

"github.com/piprate/json-gold/ld"
)

// CachingDocumentLoader is similar to json-gold's CachingDocumentLoader but uses cache as *sync.Map instead.
type CachingDocumentLoader struct {
nextLoader ld.DocumentLoader
cache map[string]interface{}
rwMutex sync.RWMutex
}

// NewCachingDocLoader creates a new instance of CachingDocumentLoader.
func NewCachingDocLoader(nextLoader ld.DocumentLoader) *CachingDocumentLoader {
cdl := &CachingDocumentLoader{
nextLoader: nextLoader,
cache: map[string]interface{}{},
}

return cdl
}

// LoadDocument returns a RemoteDocument containing the contents of the JSON resource from the given URL (u).
func (cdl *CachingDocumentLoader) LoadDocument(u string) (*ld.RemoteDocument, error) {
cdl.rwMutex.RLock()
if doc, cached := cdl.cache[u]; cached {
defer cdl.rwMutex.RUnlock()

if cachedDoc, ok := doc.(*ld.RemoteDocument); ok {
return cachedDoc, nil
}

return nil, errors.New("invalid document entry")
}

cdl.rwMutex.RUnlock()

docFromLoader, err := cdl.nextLoader.LoadDocument(u)
if err != nil {
return nil, err
}

cdl.rwMutex.Lock()
if _, cached := cdl.cache[u]; !cached {
cdl.cache[u] = docFromLoader
}

cdl.rwMutex.Unlock()

return docFromLoader, nil
}

// AddDocument populates the cache with the given document (doc) for the provided URL (u).
func (cdl *CachingDocumentLoader) AddDocument(u string, doc interface{}) {
cdl.rwMutex.Lock()

// add doc if u is not found in cache
if _, cached := cdl.cache[u]; !cached {
cdl.cache[u] = &ld.RemoteDocument{DocumentURL: u, Document: doc, ContextURL: ""}
}

cdl.rwMutex.Unlock()
}
52 changes: 52 additions & 0 deletions pkg/doc/jsonld/document_loader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package jsonld

import (
"testing"

"github.com/piprate/json-gold/ld"
"github.com/stretchr/testify/require"
)

func TestNewCachingDocLoader(t *testing.T) {
u := "https://www.w3.org/2018/credentials/v1"
loader := NewCachingDocLoader(ld.NewRFC7324CachingDocumentLoader(httpclient()))
_, err := loader.LoadDocument(u)
require.Error(t, err, "network should be disabled")

loader.AddDocument(u, jsonVCWithProperContexts)

expectedDoc := &ld.RemoteDocument{
DocumentURL: "https://www.w3.org/2018/credentials/v1",
Document: jsonVCWithProperContexts,
ContextURL: "",
}

doc, err := loader.LoadDocument(u)
require.NoError(t, err)
require.EqualValues(t, expectedDoc, doc)
}

// nolint
const jsonVCWithProperContexts = `{
"@context": "https://w3id.org/security/v2",
"id": "http://www.example.org/foo/documents/a3480d17-df7f-449f-9480-e2c35e20a865",
"allowedAction": ["read", "write"],
"invocationTarget": {
"ID": "http://www.example.org/foo/documents/a3480d17-df7f-449f-9480-e2c35e20a865",
"Type": "urn:edv:document"
},
"proof": [{
"created": "2020-12-04T15:28:14.673975717-05:00",
"jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..6OfIULug35ZmoU7lysChVpD6sjYfV71UwxqIZ8u0woYSIzRtzCo3MsZJw6cGIZMEaMssnQyRqIzo8B0yHEL2Dw",
"nonce": "da7CcJahAdFG0GXN-JnS2f2mywcFNtaLyXtGVqku2DwVwUaJbGpUQjhlNi5kDbS4ZMi2cNhEN5ac6LponS-C9w",
"proofPurpose": "capabilityDelegation",
"type": "Ed25519Signature2018",
"verificationMethod": "did:key:z6MkmkFTTczYKzU94t45sG65iZi2HA21tAU9ns8bXSmBEap4#z6MkmkFTTczYKzU94t45sG65iZi2HA21tAU9ns8bXSmBEap4"
}]
}`
6 changes: 3 additions & 3 deletions pkg/doc/jsonld/jsonld.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import (
"github.com/piprate/json-gold/ld"
)

// NewCachingDocumentLoader creates a Document Loader with default framework options.
func NewCachingDocumentLoader() *ld.CachingDocumentLoader {
return ld.NewCachingDocumentLoader(ld.NewRFC7324CachingDocumentLoader(httpclient()))
// NewDefaultCachingDocumentLoader creates a Document Loader with default framework options.
func NewDefaultCachingDocumentLoader() *CachingDocumentLoader {
return NewCachingDocLoader(ld.NewRFC7324CachingDocumentLoader(httpclient()))
}

// NewCachingDocumentLoaderWithRemote creates a Document Loader with remote enabled.
Expand Down
5 changes: 4 additions & 1 deletion pkg/doc/jsonld/jsonld_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import (
)

func TestNewVerifier(t *testing.T) {
loader := NewCachingDocumentLoader()
loader := NewDefaultCachingDocumentLoader()
_, err := loader.LoadDocument("https://www.w3.org/2018/credentials/v1")
require.Error(t, err, "network should be disabled")

rLoader := NewCachingDocumentLoaderWithRemote()
require.NotEmpty(t, rLoader)
}
14 changes: 8 additions & 6 deletions pkg/doc/signature/jsonld/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ func WithDocumentLoader(loader ld.DocumentLoader) ProcessorOpts {

// WithDocumentLoaderCache option is for passing cached contexts to be used by JSON-LD context document loader.
// Supported value types: map[string]interface{}, string, []byte, io.Reader.
// Note: Most io.Reader implementations (eg: strings.NewReader()) contain a non thread-safe buffer. Make sure to use
// one that is thread-safe.
func WithDocumentLoaderCache(cache map[string]interface{}) ProcessorOpts {
return func(opts *processorOpts) {
if opts.documentLoaderCache == nil {
opts.documentLoaderCache = make(map[string]interface{})
opts.documentLoaderCache = map[string]interface{}{}
}

for k, v := range cache {
Expand Down Expand Up @@ -514,7 +516,7 @@ func useDocumentLoader(ldOptions *ld.JsonLdOptions, loader ld.DocumentLoader, ca
ldOptions.DocumentLoader = getCachingDocumentLoader(loader, cache)
}

func getCachingDocumentLoader(loader ld.DocumentLoader, cache map[string]interface{}) *ld.CachingDocumentLoader {
func getCachingDocumentLoader(loader ld.DocumentLoader, cache map[string]interface{}) *jsonld.CachingDocumentLoader {
cachingLoader := createCachingDocumentLoader(loader)

for k, v := range cache {
Expand All @@ -524,16 +526,16 @@ func getCachingDocumentLoader(loader ld.DocumentLoader, cache map[string]interfa
return cachingLoader
}

func createCachingDocumentLoader(loader ld.DocumentLoader) *ld.CachingDocumentLoader {
func createCachingDocumentLoader(loader ld.DocumentLoader) *jsonld.CachingDocumentLoader {
if loader == nil {
return jsonld.NewCachingDocumentLoader()
return jsonld.NewDefaultCachingDocumentLoader()
}

if cachingLoader, ok := loader.(*ld.CachingDocumentLoader); ok {
if cachingLoader, ok := loader.(*jsonld.CachingDocumentLoader); ok {
return cachingLoader
}

return ld.NewCachingDocumentLoader(loader)
return jsonld.NewCachingDocLoader(loader)
}

// prepareOpts prepare processorOpts from given CanonicalizationOpts arguments.
Expand Down
Loading

0 comments on commit f83e0fc

Please sign in to comment.