Limit number of threads running ImageMagick at once. Skip work if connections have...
authorAaron Hopkins <github@die.net>
Wed, 14 May 2014 21:29:41 +0000 (14:29 -0700)
committerAaron Hopkins <github@die.net>
Wed, 14 May 2014 21:29:41 +0000 (14:29 -0700)
image-handler.go
main.go

index 35f4edcc24054e4f11863705f34180c042a65bb9..0783bfcf49ee5d4c41dfadc173e776dbcb2f19a3 100644 (file)
@@ -19,12 +19,20 @@ var (
        maxOutputDimension    = flag.Int("max_output_dimension", 2048, "Maximum width or height of an image response.")
        maxBufferPixels       = flag.Uint("max_buffer_pixels", 6500000, "Maximum number of pixels to allocate for an intermediate image buffer.")
        maxProcessingDuration = flag.Duration("max_processing_duration", time.Minute, "Maximum duration we can be processing an image before assuming we crashed (0 = disable).")
+       pool                  chan bool
 )
 
 func init() {
        http.HandleFunc("/albums/crop", imageCropHandler)
 }
 
+func poolInit(limit int) {
+       pool = make(chan bool, limit)
+       for i := 0; i < limit; i++ {
+               pool <- true
+       }
+}
+
 /*
         Supported geometries:
        WxH#        - scale down so the shorter edge fits within this bounding box, crop to new aspect ratio
@@ -35,6 +43,8 @@ var (
 )
 
 func imageCropHandler(w http.ResponseWriter, r *http.Request) {
+       aborted := w.(http.CloseNotifier).CloseNotify()
+
        if r.Method != "GET" && r.Method != "HEAD" {
                sendError(w, nil, http.StatusMethodNotAllowed)
                return
@@ -58,8 +68,23 @@ func imageCropHandler(w http.ResponseWriter, r *http.Request) {
                return
        }
 
+       // Wait for an image thread to be available.
+       <-pool
+
+       // Has client closed connection while we were waiting?
+       select {
+       case <-aborted:
+               pool <- true // Free up image thread ASAP.
+               sendError(w, nil, http.StatusRequestTimeout)
+               return
+       default:
+       }
+
        thumb, err := processImage(url, orig, width, height, crop)
        orig = nil // Free up image memory ASAP.
+
+       pool <- true // Free up image thread ASAP.
+
        if err != nil {
                sendError(w, err, 0)
                return
@@ -99,10 +124,14 @@ func fetchUrl(url string) ([]byte, error, int) {
        }
 
        switch resp.StatusCode {
-       case http.StatusOK, http.StatusNoContent, http.StatusBadRequest,
-               http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound,
-               http.StatusRequestTimeout, http.StatusGone:
-
+       case http.StatusOK,
+               http.StatusNoContent,
+               http.StatusBadRequest,
+               http.StatusUnauthorized,
+               http.StatusForbidden,
+               http.StatusNotFound,
+               http.StatusRequestTimeout,
+               http.StatusGone:
                return body, nil, resp.StatusCode
        default:
                err := fmt.Errorf("Proxy received %d %s", resp.StatusCode, http.StatusText(resp.StatusCode))
diff --git a/main.go b/main.go
index 673f498c6c0fd824abde9c732d3b16cfa4f7a085..1204840de38ed3ba6d8bf76455501f13439bcf51 100644 (file)
--- a/main.go
+++ b/main.go
@@ -13,14 +13,18 @@ import (
 )
 
 var (
-       listenAddr = flag.String("listen", "127.0.0.1:3520", "[IP]:port to listen for incoming connections.")
-       maxThreads = flag.Int("max_threads", runtime.NumCPU(), "Maximum number of OS threads to create.")
+       listenAddr      = flag.String("listen", "127.0.0.1:3520", "[IP]:port to listen for incoming connections.")
+       maxImageThreads = flag.Int("max_image_threads", runtime.NumCPU(), "Maximum number of threads simultaneously processing images.")
 )
 
 func main() {
        flag.Parse()
 
-       runtime.GOMAXPROCS(*maxThreads)
+       // Up to max_threads will be allowed to be blocked in ImageMagick.
+       poolInit(*maxImageThreads)
+
+       // Allow more threads than that for networking, etc.
+       runtime.GOMAXPROCS(*maxImageThreads * 2)
 
        log.Fatal(http.ListenAndServe(*listenAddr, nil))
 }