Closed kyleerhabor closed 3 years ago
The Jetty adapter is multi-threaded, so it can handle multiple connections in parallel. The only limit is the hardware and the size the threadpool has been set to.
Asynchronous handlers are useful when you expect the server to have many connections and to spend a long time waiting around not doing much. For example, suppose you have a game where you are matchmaking players of equal skill to play against each other. If there are no players available when the request is made, the server might keep the connection open until a suitable match is found, or the connection times out.
Keeping a thread waiting around takes some resources, while an "paused" asynchronous handler consumes far less. In many cases this resource cost doesn't matter, as server hardware is sufficient to keep many hundreds or even thousands of threads operating simultaneously. However, if you expect connections to last a long time, and if you expect to have large numbers of concurrent users, then asynchronous handlers begin to make more sense.
Thanks for the clarification!
@weavejester Hi, if you don't mind me asking more Qs...
what do you mean by “an paused asynchronous handler”?
The example I saw with async handlers looked like this:
([request respond raise]
(future
(try
(respond {:body (str "Hello from " (:server-name request) " (async)")})
(catch Throwable t
(raise t)))))
This creates a future
, the future
launches a thread
. If the future
takes a long time to complete, the thread
is still running somewhere, not paused.
@Sleepful Consider a synchronous handler like this:
(defn handler [request]
(let [users (get-users db)]
{:status 200, :body (to-json users)}))
In this case, the thread will block until get-users
returns a list of users from the database.
The equivalent asynchronous handler would be:
(defn handler [request respond raise]
(get-users db (fn [users]
(respond {:status 200, :body (to-json users)}))))
Instead of blocking the thread while we wait for the database, we pass a callback function to be run when the database returns the data available.
In other words we "pause" the handler by writing the logic after the pause into a callback function. Here's another contrived example with three pauses:
(defn example
(println "One")
(fn []
(println "Two")
(fn []
(println "Three"))))
user=> (def paused-example (example))
One
user=> (def paused-example (paused-example))
Two
user=> (paused-example)
Three
I've been reading the wiki for a few days now and one thing that's concerned me is using a synchronous or asynchronous handle. Specifically, this line from the Concepts page:
To try simulating a situation where I may prefer the asynchronous handler, I tried blocking with
Thread/sleep 5000
and opening two tabs in my browser (Safari). However, both of them completed in 5 seconds, as opposed to waiting for the first 5 seconds, then another 5 seconds for the second request.My testing may be inadequate, but when would I want to use the asynchronous version? What's an insufficient case for the synchronous version?