From 79cd061838e8a5678f8528aef46c01e45a29ac73 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Fri, 3 Jan 2014 18:57:36 -0800 Subject: [PATCH] pkg/server: stream images not slurp to RAM. More improvement on https://camlistore.org/issue/237 Change-Id: I50076f2fc84de796026029cdab46a897354d0174 --- pkg/server/image.go | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/pkg/server/image.go b/pkg/server/image.go index 3f15389ea..fa726ba11 100644 --- a/pkg/server/image.go +++ b/pkg/server/image.go @@ -161,17 +161,12 @@ func (ih *ImageHandler) scaledCached(buf *bytes.Buffer, file blob.Ref) (format s return format } -// These gates control the max concurrency of slurping raw images -// (e.g. JPEG bytes) to RAM, and then decoding and resizing them, -// respectively. We allow more concurrency for the former because -// it's slower and less memory-intensive. The actual resizing takes -// much more CPU and RAM. +// Gate the number of concurrent image resizes to limit RAM & CPU use. -// TODO: these numbers were just guesses and not based on any +// TODO: this number is just a guess and not based on any // data. measure? make these configurable? Automatically tuned // somehow? Based on memory usage/availability? var ( - scaleImageGateSlurp = syncutil.NewGate(5) scaleImageGateResize = syncutil.NewGate(2) ) @@ -180,6 +175,18 @@ type formatAndImage struct { image []byte } +// TODO(wathiede): move to a common location if the pattern of TeeReader'ing +// to a statWriter proves useful. +type statWriter struct { + *expvar.Int +} + +func (sw statWriter) Write(p []byte) (int, error) { + c := len(p) + sw.Add(int64(c)) + return c, nil +} + func (ih *ImageHandler) scaleImage(fileRef blob.Ref) (*formatAndImage, error) { fr, err := schema.NewFileReader(ih.storageSeekFetcher(), fileRef) if err != nil { @@ -187,11 +194,8 @@ func (ih *ImageHandler) scaleImage(fileRef blob.Ref) (*formatAndImage, error) { } defer fr.Close() - var buf bytes.Buffer - scaleImageGateSlurp.Start() - n, err := io.Copy(&buf, fr) - scaleImageGateSlurp.Done() - imageBytesFetchedVar.Add(n) + sw := statWriter{imageBytesFetchedVar} + tr := io.TeeReader(fr, sw) if err != nil { return nil, fmt.Errorf("image resize: error reading image %s: %v", fileRef, err) @@ -200,8 +204,10 @@ func (ih *ImageHandler) scaleImage(fileRef blob.Ref) (*formatAndImage, error) { scaleImageGateResize.Start() defer scaleImageGateResize.Done() - i, imConfig, err := images.Decode(bytes.NewReader(buf.Bytes()), - &images.DecodeOpts{MaxWidth: ih.MaxWidth, MaxHeight: ih.MaxHeight}) + i, imConfig, err := images.Decode(tr, &images.DecodeOpts{ + MaxWidth: ih.MaxWidth, + MaxHeight: ih.MaxHeight, + }) if err != nil { return nil, err } @@ -218,9 +224,9 @@ func (ih *ImageHandler) scaleImage(fileRef blob.Ref) (*formatAndImage, error) { b = i.Bounds() } + var buf bytes.Buffer if !useBytesUnchanged { // Encode as a new image - buf.Reset() switch format { case "png": err = png.Encode(&buf, i)