2014. április 20., vasárnap

Firefox and testing concurrent web application

TL;DR:

firefox won't start a request for an URL if a request is already being executed for that same URL.

If you open http://example.com/foo, and it takes several seconds to complete, when you simultaneously open a new tab and enter the very same URL, the latter request WILL NOT BE SENT until the former has completed.

If you ever need to make parallel requests for the same URL make sure you put some random query string in them, so the browser won't serialize requests for them.

I was testing a concurrent web application written in Haskell, Yesod. I love Haskell and the Yesod framework, this combination gave me the best web development experience so far. I especially loved how easy it was to develop a simple web-based chat application by using TVars and TChans - variables and "channels" - basically message queues - to store state in a Yesod web application and pass messages between threads.

I stumbled into a strange bug, and it took a good two hours of my life.



For those who aren't familiar with Yesod, just imageine a usual MVC-based web application. My problem was to block the execution of a thread executing a certain controller, and continue it when an other thread executing an other controller sends a message down a pipe (a TChan to be more precise). This makes long polling possible.

This is easy to achieve in Yesod, since it is multithreaded by default, and it isn't particularily hard to set up the TChan and use it from the Handlers (controllers).

But when I tried to test the application, the strangest thing would happen.

I opened up a browser (Firefox) and entered the URL that should been blocked, and it did: the loading indicator spun indefinitely until the fairly long timeout has been expired.

When I simultaneously opened the other URL for the message sending controller, the receiver URL loading completed immediately, and the message appeared on the screen.

So far, so good.

So let's open two receivers, and thest the broadcast capabilities of TChans!

I opened up two receiver tabs, both started to load. Then I opened up a sender, and ONLY ONE RECEIVER got the message.

I spent about two hours debugging, including digging into the Yesod codebase trying to figure out what was going on.

Finally I realized that the two receiver executions are completely serialized, and this could not happen inside Yesod: testing with curl and ab showed that calls to the same Handler are correctly being served in parallel.

Then it hit me. What if Firefox blocks the execution of requests for the SAME URL for - I don't know - preventing simple (intentional or unintentional) DOS scripts from running.

Aaaand I was right. Adding a "?asdasd=asdasd" after one of the receiver URLs fixed the problem, the requests was made parallel and the broadcast feature worked like a charm.