Testing a chat server

by Colin Ross on January 6, 2009

After my feeble fumblings towards what is quite possibly the most minimal HTTP server designed (minimal as in functionality rather than in minimal but complete according to specification), it was clear that part of the problem was trying to use HTTP preformance tools to test the chat server. To rectify this, it made sense that I write my own little test utility that would allow me to hammer the chat server and test it in a more appropriate way.

Aside

It is still clear that there are some issues with the original chat server since the shutdowns shouldn’t be occurring regardless of what kind of mad clients try to connect. However, the hope is that with a more focused test utility, the problems can be resolved more easily.

The plan

Rather conveniently, there is a corresponding simple TCP client for the original simple TCP server. Once again, this is where we shall begin our quest.

The idea is to convert the client from a command line user-interface to an automated tool that will connect to a server, send some input, receive some output, close the connection and then return the output received. In this way we will be able to test that we receive from the server what we expect.

The implementation

Rather suprisingly, implementation went relatively smoothly. I rapidly got a command line utility that takes a host, port and number of connections to make. It then forks a new thread for each one and each writes a single line, consisting of its number. Each then listens for a second beforewriting a final string and then closing the connection.

While this is happening, I am also listening to replies from the server. Any replies are tagged with the client id and added to a giant map. When the terminating string is recognised, that client is removed from a large list and when all clients have completed, this in turn returns. The value returned is a map of client id to the strings received from the server by that client. This should now allow some analysis to ensure all are working as they should.

Initial testing

First testing was against the minimal HTTP server. And it worked. But being keen to test it aggressively, I immediately cranked up the number of connections to make to 2000. At that point I got this rather worrying error.

getProtocolByName: does not exist (no such protocol name: tcp)

Thinking that it was likely that I had deleted the TCP protocol, I suspected an issue with open sockets once more. This was confirmed by a quick search over the number of connections. The maximum I was allowed without getting the delightful error above was in fact 1021. This looked fair enough when the output from socklist included the following TCP sockets

type  port      inode     uid    pid   fd  name
tcp   9999    2284399    1000  15202    3  minimal
tcp    631    2245789       0      0    0
tcp     25      99077       0      0    0

and ulimit -a provided the information

open files (-n) 1024

which implied that there would be 1021 sockets available.

Interestingly enough…

Doing a Google search for “getprotocolbyname does not exist no such protocol name tcp” turns up several Haskell-related links. Could this be an indicator that Haskell is often being used for large-scale socket manipulation? Or that Haskell programmers are more likely to be doing strange things with large numbers of sockets? Or even that Haskell programmers don’t know what they are doing and do crazy things like that?

Further testing

Since I was flush with my success, I moved on to testing the original chat server. Here, as expected, things did not go as well.

Small number of connections were fine. But when I rolled out the big guns and set 500 connections at the server, it promptly fell over (gracefully as ever).

Although disappointing that the chat server is somehow broken, the fact that the test utility reproduces the problem is reassuring as I slowly add to my minimal HTTP server making it more fully-featured.

Scary code

The thing about Haskell is that it lets you do some frightening stuff in a single line. Case in point:

sequence_ $ map forkIO $ zipWith ($) (zipWith ($) (repeat (start host portStr chan)) [1..n]) $ map (:[]) $ map show [1..n]

which is just scary. Of course there is probably a much simpler way to do something like this, but if it works, don’t mess with it, at least unless there is some sort of reliable test framework to rely on.

Conclusion

Getting a basic test framework was easy, but the proof of the pudding will be in the eating. Or, in other words, I shall wait and see how much use I can make of it as I progress. The final state of the code is reproduced below – for all its pretty colours, it is really quite messy!

The code

> module Main where
 
> import Prelude hiding (catch)
 
> import Network (connectTo, withSocketsDo, PortID(..))
 
> import System.IO
> import System.IO.Error (isEOFError)
> import System.Environment (getArgs)
> import Control.Exception (finally, catch, Exception(..))
> import Control.Concurrent
> import Control.Concurrent.STM
> import qualified Data.List as L
> import qualified Data.Map as M
> import Control.Monad
 
> main = withSocketsDo $ do
>     [host, portStr, nStr] <- getArgs
>     let n = (read nStr :: Int)
>     chan <- atomically newTChan
>     sequence_ $ map forkIO $ zipWith ($) (zipWith ($) (repeat (start host portStr chan)) [1..n]) $ map (:[]) $ map show [1..n]
>     output <- mainLoop chan  [1..n] $ M.fromList $ map (\i -> (i, [])) [1..n]
>     putStrLn $ (show output)
 
> start :: String -> String -> TChan (Int, String) -> Int -> [String] -> IO ()
> start host portStr chan i ss = do
>     let port = fromIntegral (read portStr :: Int)
>     sock <- connectTo host $ PortNumber port
>
>     netTID   <- spawn $ listenLoop i (hGetLine sock) chan
>     sequence_ $ map (hPutStrLn sock) ss
>     threadDelay 1000000
>     atomically $ writeTChan chan (i, "COMPLETE")
>     hClose sock
Bookmark and Share

No related posts.

{ 1 trackback }

Haskell and chat « absolute regularity
July 5, 2009 at 9:07 pm

{ 4 comments… read them below or add one }

Anon January 12, 2009 at 8:22 pm

Being limited to 1024 open file descriptors by GHC 6.10.1 is quite a serious issue. Haskell would be well suited as a comet server especially given its lightweight threads. There exists an old ticket at http://hackage.haskell.org/trac/ghc/ticket/635 which addresses the problem and according to a recent e-mail at haskell-café, work is under way to get epoll support in which should allow four hundreds of throusands if not a million concurrent connections. Looking forward to try this out …

Reply

Colin Ross January 12, 2009 at 10:18 pm

@Anon

I have seen the thread in Haskell-Cafe and also look forward to trying it out when the implementation makes an appearance.

Reply

Felix January 13, 2009 at 12:29 am

The 1024 sockets is a limitation of select. I did create an experimental Haskell IO library last year that has Epoll and IOCP support. The GHC rts can easily handle 60,000 threads on Linux and Windows. I tried to add the code to GHC but GHC’s build system is slow and complex so that isn’t much fun if you’re not very familiar with GHC’s code and quickly want to test code. But Brian O’Sullivan will likely manage to add Epoll support to GHC.

Reply

pumpkin January 13, 2009 at 1:45 am

@Anon

how do you get hundreds of thousands of connections when tcp only allows for 65k ports? 1024 seems a little tight, but there’s a (relatively low) limit isn’t there?

Reply

Leave a Comment

Previous post:

Next post: