Skip to content

Commit ae72f28

Browse files
committed
Fix performance issue with async responses
For async responses, the function rest.util.servlet/make-output-stream creates a subclass of java.io.FilterOutputStream in order to complete the async context after the output stream has been closed. Unfortunately for the performance, FilterOutputStream implements the write(byte[], int, int) method by looping through the byte array and calling the single byte write(int) method. The solution is simple: add an appropriate write method to the proxy, that calls write(byte[], int, int) on the underlying servlet output stream.
1 parent ebce6f5 commit ae72f28

File tree

2 files changed

+33
-22
lines changed

2 files changed

+33
-22
lines changed

ring-bench/src/ring/bench/servlet.clj

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
(ns ring.bench.servlet
2-
(:require [clojure.pprint :as pp]
3-
[jmh.core :as jmh]
2+
(:require [jmh.core :as jmh]
43
[ring.util.servlet :as servlet])
5-
(:import [java.util HashMap ArrayList]))
4+
(:import [java.util HashMap ArrayList]
5+
[javax.servlet AsyncContext]))
66

77
(defn http-servlet-request []
88
(let [headers (HashMap.
@@ -18,7 +18,7 @@
1818
(getServerName [_] "localhost")
1919
(getRemoteAddr [_] "localhost")
2020
(getRequestURI [_] "/example")
21-
(getQueryString [_] "q=test")
21+
(getQueryString [_] "q=test")
2222
(getScheme [_] "http")
2323
(getMethod [_] "GET")
2424
(getProtocol [_] "HTTP/1.1")
@@ -42,29 +42,38 @@
4242
(close [] (.close os))
4343
(flush [] (.flush os))
4444
(write
45-
([b] (.write os b))
45+
([b] (.write os ^int b))
4646
([b off len] (.write os b off len))))))))
4747

48-
(def ring-response
48+
(defn async-context []
49+
(reify AsyncContext
50+
(complete [_])))
51+
52+
(defn ring-response [body-size]
4953
{:status 200
5054
:headers {"Content-Type" "application/json"}
51-
:body "{\"hello\" \"world\"}"})
55+
:body (apply str (repeat body-size "x"))})
5256

53-
(let [response ring-response
54-
handler (fn [_] response)]
55-
(defn servlet-handler [request response]
56-
(->> request servlet/build-request-map handler (servlet/update-servlet-response response))))
57+
(defn servlet-handler [servlet-request servlet-response response]
58+
(let [handler (fn [_] response)]
59+
(->> servlet-request
60+
servlet/build-request-map
61+
handler
62+
(servlet/update-servlet-response servlet-response))))
5763

5864
(def bench-env
5965
{:benchmarks
60-
[{:name :build, :fn `servlet/build-request-map, :args [:state/request]}
61-
{:name :update, :fn `servlet/update-servlet-response, :args [:state/response :param/response]}
62-
{:name :handler, :fn `servlet-handler, :args [:state/request :state/response]}]
66+
[{:name :build, :fn `servlet/build-request-map, :args [:state/servlet-request]}
67+
{:name :update, :fn `servlet/update-servlet-response, :args [:state/servlet-response :state/response]}
68+
{:name :update-async, :fn `servlet/update-servlet-response, :args [:state/servlet-response :state/async-context :state/response]}
69+
{:name :handler, :fn `servlet-handler, :args [:state/servlet-request :state/servlet-response :state/response]}]
6370
:states
64-
{:request {:fn `http-servlet-request, :args []}
65-
:response {:fn `http-servlet-response, :args []}}
71+
{:servlet-request {:fn `http-servlet-request, :args []}
72+
:servlet-response {:fn `http-servlet-response, :args []}
73+
:response {:fn `ring-response, :args [:param/response-body-size]}
74+
:async-context {:fn `async-context, :args []}}
6675
:params
67-
{:response ring-response}})
76+
{:response-body-size [128 1024 8192 65536]}})
6877

6978
(def bench-opts
7079
{:type :quick
@@ -73,8 +82,8 @@
7382
(defn -main []
7483
(println "Benchmarking...")
7584
(doseq [result (jmh/run bench-env bench-opts)]
76-
(let [[score unit] (:score result)]
77-
(println (format " %s - %.2f ops/s (σ=%.2f)"
78-
(:name result)
79-
(-> result :statistics :mean)
80-
(-> result :statistics :stdev))))))
85+
(println (format " %-13s - %5s - %.2f ops/s (σ=%.2f)"
86+
(:name result)
87+
(-> result :params :response_body_size (or "n/a"))
88+
(-> result :statistics :mean)
89+
(-> result :statistics :stdev)))))

ring-servlet/src/ring/util/servlet.clj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@
8484
(if (nil? context)
8585
os
8686
(proxy [java.io.FilterOutputStream] [os]
87+
(write [b off len]
88+
(.write os b off len))
8789
(close []
8890
(.close os)
8991
(.complete context))))))

0 commit comments

Comments
 (0)