shithub: hugo

Download patch

ref: 79b34c2f1e0ba91ff5f4f879dc42eddfd82cc563
parent: 3c6b16d5a237acb97372c8d64a49478c8c8bd2d1
author: Bjørn Erik Pedersen <[email protected]>
date: Fri Mar 31 04:54:22 EDT 2017

tplimpl: Fix deadlock in getJSON

Fixes #3211

--- a/tpl/tplimpl/template_resources.go
+++ b/tpl/tplimpl/template_resources.go
@@ -36,6 +36,7 @@
 	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 {
@@ -49,8 +50,8 @@
 	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.
@@ -70,6 +71,9 @@
 // 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
 	}
@@ -88,6 +92,9 @@
 
 // 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
 	}
--- a/tpl/tplimpl/template_resources_test.go
+++ b/tpl/tplimpl/template_resources_test.go
@@ -20,7 +20,9 @@
 	"net/http/httptest"
 	"net/url"
 	"strings"
+	"sync"
 	"testing"
+	"time"
 
 	"github.com/spf13/afero"
 	"github.com/spf13/hugo/helpers"
@@ -171,6 +173,47 @@
 			}
 		}
 	}
+}
+
+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) {