Skip to content

Commit

Permalink
resources/images: Make the image cache more robust
Browse files Browse the repository at this point in the history
Also allow timeout to be set as a duration string, e.g. `30s`.

Fixes gohugoio#6501
  • Loading branch information
bep committed Nov 25, 2019
1 parent 9f9c4b5 commit d521cad
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 25 deletions.
2 changes: 1 addition & 1 deletion cache/filecache/filecache.go
Expand Up @@ -129,7 +129,7 @@ func (c *Cache) WriteCloser(id string) (ItemInfo, io.WriteCloser, error) {
// If not found a new file is created and passed to create, which should close
// it when done.
func (c *Cache) ReadOrCreate(id string,
read func(info ItemInfo, r io.Reader) error,
read func(info ItemInfo, r io.ReadSeeker) error,
create func(info ItemInfo, w io.WriteCloser) error) (info ItemInfo, err error) {
id = cleanID(id)

Expand Down
4 changes: 2 additions & 2 deletions cache/filecache/filecache_test.go
Expand Up @@ -250,9 +250,9 @@ func TestFileCacheReadOrCreateErrorInRead(t *testing.T) {

var result string

rf := func(failLevel int) func(info ItemInfo, r io.Reader) error {
rf := func(failLevel int) func(info ItemInfo, r io.ReadSeeker) error {

return func(info ItemInfo, r io.Reader) error {
return func(info ItemInfo, r io.ReadSeeker) error {
if failLevel > 0 {
if failLevel > 1 {
return ErrFatal
Expand Down
2 changes: 1 addition & 1 deletion hugolib/config.go
Expand Up @@ -620,7 +620,7 @@ func loadDefaultSettingsFor(v *viper.Viper) error {
v.SetDefault("disableAliases", false)
v.SetDefault("debug", false)
v.SetDefault("disableFastRender", false)
v.SetDefault("timeout", 30000) // 30 seconds
v.SetDefault("timeout", "30s")
v.SetDefault("enableInlineShortcodes", false)

return nil
Expand Down
46 changes: 37 additions & 9 deletions hugolib/image_test.go
Expand Up @@ -35,11 +35,12 @@ func TestImageOps(t *testing.T) {
c.Assert(err, qt.IsNil)
defer clean()

newBuilder := func() *sitesBuilder {
newBuilder := func(timeout string) *sitesBuilder {

v := viper.New()
v.Set("workingDir", workDir)
v.Set("baseURL", "https://example.org")
v.Set("timeout", timeout)

b := newTestSitesBuilder(t).WithWorkingDir(workDir)
b.Fs = hugofs.NewDefault(v)
Expand All @@ -49,9 +50,17 @@ func TestImageOps(t *testing.T) {
title: "My bundle"
---
{{< imgproc >}}
`)

b.WithTemplatesAdded("index.html", `
b.WithTemplatesAdded(
"shortcodes/imgproc.html", `
{{ $img := resources.Get "images/sunset.jpg" }}
{{ $r := $img.Resize "129x239" }}
IMG SHORTCODE: {{ $r.RelPermalink }}/{{ $r.Width }}
`,
"index.html", `
{{ $p := .Site.GetPage "mybundle" }}
{{ $img1 := resources.Get "images/sunset.jpg" }}
{{ $img2 := $p.Resources.GetMatch "sunset.jpg" }}
Expand Down Expand Up @@ -83,7 +92,7 @@ BG3: {{ $blurryGrayscale3.RelPermalink }}/{{ $blurryGrayscale3.Width }}
{{ $blurryGrayscale4 := $r.Filter $filters }}
BG4: {{ $blurryGrayscale4.RelPermalink }}/{{ $blurryGrayscale4.Width }}
{{ $p.Content }}
`)

Expand Down Expand Up @@ -112,8 +121,8 @@ BG4: {{ $blurryGrayscale4.RelPermalink }}/{{ $blurryGrayscale4.Width }}
out.Close()
src.Close()

b := newBuilder()
b.Build(BuildCfg{})
// First build it with a very short timeout to trigger errors.
b := newBuilder("1ns")

imgExpect := `
Resized1: images/sunset.jpg|123|234|image/jpg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg|
Expand All @@ -126,16 +135,35 @@ BG1: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_2ae8bb993431ec1aec4
BG2: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_2ae8bb993431ec1aec40fe59927b46b4.jpg/123
BG3: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_ed7740a90b82802261c2fbdb98bc8082.jpg/123
BG4: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_ed7740a90b82802261c2fbdb98bc8082.jpg/123
IMG SHORTCODE: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_129x239_resize_q75_box.jpg/129
`

b.AssertFileContent(filepath.Join(workDir, "public/index.html"), imgExpect)
b.AssertImage(350, 219, "public/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_350x0_resize_q75_box.a86fe88d894e5db613f6aa8a80538fefc25b20fa24ba0d782c057adcef616f56.jpg")
assertImages := func() {
b.Helper()
b.AssertFileContent(filepath.Join(workDir, "public/index.html"), imgExpect)
b.AssertImage(350, 219, "public/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_350x0_resize_q75_box.a86fe88d894e5db613f6aa8a80538fefc25b20fa24ba0d782c057adcef616f56.jpg")
b.AssertImage(129, 239, "public/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_129x239_resize_q75_box.jpg")
}

err = b.BuildE(BuildCfg{})
c.Assert(err, qt.Not(qt.IsNil))

b = newBuilder("30s")
b.Build(BuildCfg{})

assertImages()

// Truncate one image.
imgInCache := filepath.Join(workDir, "resources/_gen/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_ed7740a90b82802261c2fbdb98bc8082.jpg")
f, err := os.Create(imgInCache)
c.Assert(err, qt.IsNil)
f.Close()

// Build it again to make sure we read images from file cache.
b = newBuilder()
b = newBuilder("30s")
b.Build(BuildCfg{})

b.AssertFileContent(filepath.Join(workDir, "public/index.html"), imgExpect)
assertImages()

}

Expand Down
15 changes: 14 additions & 1 deletion hugolib/site.go
Expand Up @@ -410,10 +410,23 @@ func newSite(cfg deps.DepsCfg) (*Site, error) {
return nil, err
}

timeout := 30 * time.Second
if cfg.Language.IsSet("timeout") {
switch v := cfg.Language.Get("timeout").(type) {
case int:
timeout = time.Duration(v) * time.Millisecond
case string:
d, err := time.ParseDuration(v)
if err == nil {
timeout = d
}
}
}

siteConfig := siteConfigHolder{
sitemap: config.DecodeSitemap(config.Sitemap{Priority: -1, Filename: "sitemap.xml"}, cfg.Language.GetStringMap("sitemap")),
taxonomiesConfig: taxonomies,
timeout: time.Duration(cfg.Language.GetInt("timeout")) * time.Millisecond,
timeout: timeout,
hasCJKLanguage: cfg.Language.GetBool("hasCJKLanguage"),
enableEmoji: cfg.Language.Cfg.GetBool("enableEmoji"),
}
Expand Down
2 changes: 1 addition & 1 deletion resources/image.go
Expand Up @@ -88,7 +88,7 @@ func (i *imageResource) getExif() (*exif.Exif, error) {

key := i.getImageMetaCacheTargetPath()

read := func(info filecache.ItemInfo, r io.Reader) error {
read := func(info filecache.ItemInfo, r io.ReadSeeker) error {
meta := &imageMeta{}
data, err := ioutil.ReadAll(r)
if err != nil {
Expand Down
9 changes: 8 additions & 1 deletion resources/image_cache.go
Expand Up @@ -96,12 +96,18 @@ func (c *imageCache) getOrCreate(
// These funcs are protected by a named lock.
// read clones the parent to its new name and copies
// the content to the destinations.
read := func(info filecache.ItemInfo, r io.Reader) error {
read := func(info filecache.ItemInfo, r io.ReadSeeker) error {
img = parent.clone(nil)
rp := img.getResourcePaths()
rp.relTargetDirFile.file = relTarget.file
img.setSourceFilename(info.Name)

if err := img.InitConfig(r); err != nil {
return err
}

r.Seek(0, 0)

w, err := img.openDestinationsForWriting()
if err != nil {
return err
Expand All @@ -114,6 +120,7 @@ func (c *imageCache) getOrCreate(

defer w.Close()
_, err = io.Copy(w, r)

return err
}

Expand Down
20 changes: 11 additions & 9 deletions resources/images/image.go
Expand Up @@ -123,29 +123,31 @@ func (i Image) WithSpec(s Spec) *Image {
return &i
}

// InitConfig reads the image config from the given reader.
func (i *Image) InitConfig(r io.Reader) error {
var err error
i.configInit.Do(func() {
i.config, _, err = image.DecodeConfig(r)
})
return err
}

func (i *Image) initConfig() error {
var err error
i.configInit.Do(func() {
if i.configLoaded {
return
}

var (
f hugio.ReadSeekCloser
config image.Config
)
var f hugio.ReadSeekCloser

f, err = i.Spec.ReadSeekCloser()
if err != nil {
return
}
defer f.Close()

config, _, err = image.DecodeConfig(f)
if err != nil {
return
}
i.config = config
i.config, _, err = image.DecodeConfig(f)
})

if err != nil {
Expand Down

0 comments on commit d521cad

Please sign in to comment.