Skip to content

Commit

Permalink
tplimpl: Fix deadlock in getJSON
Browse files Browse the repository at this point in the history
  • Loading branch information
bep committed Mar 31, 2017
1 parent 3c6b16d commit 07960d0
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 1 deletion.
9 changes: 8 additions & 1 deletion tpl/tplimpl/template_resources.go
Expand Up @@ -36,6 +36,7 @@ var (
remoteURLLock = &remoteLock{m: make(map[string]*sync.Mutex)}
resSleep = time.Second * 2 // if JSON decoding failed sleep for n seconds before retrying
resRetries = 1 // number of retries to load the JSON from URL or local file system
resCacheMu sync.RWMutex
)

type remoteLock struct {
Expand All @@ -49,8 +50,8 @@ func (l *remoteLock) URLLock(url string) {
if _, ok := l.m[url]; !ok {
l.m[url] = &sync.Mutex{}
}
l.m[url].Lock()
l.Unlock()
l.m[url].Lock()
}

// URLUnlock unlocks an URL when the download has been finished. Use only in defer calls.
Expand All @@ -70,6 +71,9 @@ func getCacheFileID(cfg config.Provider, id string) string {
// resGetCache returns the content for an ID from the file cache or an error
// if the file is not found returns nil,nil
func resGetCache(id string, fs afero.Fs, cfg config.Provider, ignoreCache bool) ([]byte, error) {
resCacheMu.RLock()
defer resCacheMu.RUnlock()

if ignoreCache {
return nil, nil
}
Expand All @@ -88,6 +92,9 @@ func resGetCache(id string, fs afero.Fs, cfg config.Provider, ignoreCache bool)

// resWriteCache writes bytes to an ID into the file cache
func resWriteCache(id string, c []byte, fs afero.Fs, cfg config.Provider, ignoreCache bool) error {
resCacheMu.Lock()
defer resCacheMu.Unlock()

if ignoreCache {
return nil
}
Expand Down
43 changes: 43 additions & 0 deletions tpl/tplimpl/template_resources_test.go
Expand Up @@ -20,7 +20,9 @@ import (
"net/http/httptest"
"net/url"
"strings"
"sync"
"testing"
"time"

"github.com/spf13/afero"
"github.com/spf13/hugo/helpers"
Expand Down Expand Up @@ -173,6 +175,47 @@ func TestScpGetRemote(t *testing.T) {
}
}

func TestScpGetRemoteParallel(t *testing.T) {
t.Parallel()
fs := new(afero.MemMapFs)
content := []byte(`T€st Content 123`)
url := "http://Foo.Bar/foo_Bar-Foo"
srv, cl := getTestServer(func(w http.ResponseWriter, r *http.Request) {
w.Write(content)
})
defer func() { srv.Close() }()

for _, ignoreCache := range []bool{false, true} {

cfg := viper.New()
cfg.Set("ignoreCache", ignoreCache)

var wg sync.WaitGroup

for i := 0; i < 50; i++ {
wg.Add(1)
go func(gor int) {
defer wg.Done()
for j := 0; j < 10; j++ {
c, err := resGetRemote(url, fs, cfg, cl)
if err != nil {
t.Errorf("Error getting resource content: %s", err)
}
if !bytes.Equal(c, content) {
t.Errorf("\nNet Expected: %s\nNet Actual: %s\n", string(content), string(c))
}

time.Sleep(23 * time.Millisecond)
}
}(i)
}

wg.Wait()
}

t.Log("Done!")
}

func TestParseCSV(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit 07960d0

Please sign in to comment.