Inside F#

Brian's thoughts on F# and .NET

F# async on the server side

Posted by Brian on March 28, 2010

Last time I talked about cool ways to use F# asyncs to help write client GUIs.  Today I’ll talk about the killer app for F# asyncs, namely, non-blocking I/O on the server side.  Once again, this is material I lifted from a recent talk that Matthew Podwysocki and I gave at TechReady.  (So if today’s blog entry sounds like I’m presenting a powerpoint deck and a demo to an audience in a room, that’s why!)

A server example: stock-quote server

Most typical server applications have a number of things in common:

  • Many clients
  • Streams of data to/from clients
  • I/O to carry out the guts/machinery of the service

There are various kinds of file & application servers: web servers, file servers, chat servers… As an example for today, I’ll show a mocked-up stock-quote server:

  • Clients connect with info about stocker ticker they’re interested in (MSFT, GOOG, whatever), and the server will send back a continual stream of stock price updates, maybe about 1 per second. 
  • We want to be able to handle thousands of concurrent clients.

This small sample application demonstrates some essential scaling features found in many server scenarios.

Server commonalities

At a very high level, servers often have this basic pseudocode structure:

  • Server code often has the form

while (true) {
    client = socket.AcceptClient()
    BeginServicingClient(client)
}

  • Where BeginServicingClient has the form

try {
while (true) {
     SendOrReceiveData()  // local & remote I/O
}
} catch (Exception e) { Handle(e) }

There’s a main loop that listens for new client connections, and each connection spawns a loop that is dedicated to servicing that client, and typically involves I/O (talking to the file system, a database, a web server, whatever).

  • In the case of our stock-quote server, we’ll have a main loop that accepts new clients, and then for each client, we will send updated price data every second until the connection ends.
  • We need to be able to handle multiple clients concurrently; the simplest (but naïve) approach is to use threads – e.g. BeginServicing() creates a new thread, one thread per client.  This is very easy to code up and lets us continue to use easy ‘sync’ APIs (e.g. “Stream.Write”) rather than difficult ‘async’ APIs (BeginWrite/EndWrite).
  • I’ve coded an example of this strategy in C#.  Rather than using any real stock quote data, the server just sends dummy packets back to its clients, but otherwise this is similar to what you might find in practice.  Let’s have a look at the C# code.

(The entirety of the code can be found at the end of this blog entry.)

A synchronous solution in C#, using threads

I’ve coded the server as a short C# program, only about 100 lines of code.  I’ll just call out a couple noteworthy bits:

static void SyncMain(string[] args)
{
...
while (true) { var client = socket.AcceptTcpClient(); ...
var t1 = new Thread(new ThreadStart(() => ...
ServiceClient(client);

In the main loop we accept new clients (AcceptTcpClient), and for each one we start a new thread (ThreadStart) that calls ServiceClient(), which is this little function that is a lot like the pseudocode I mentioned earlier:

static void ServiceClient(TcpClient client)
{
    using (var stream = client.GetStream())
    {
        stream.Write(quote, 0, 1);  // write header
        while (true)
        {
            WriteStockQuote(stream);
        }
    }
}

We write an initial header byte, and then just sit in a loop sending stock quotes back to the client.  To keep the example simple, this just goes forever, until e.g. the client disconnects (which would cause an exception and get out of the loop).

Ok, let’s try running it.  I’ll start the server, and I’ll fire up Task Manager so we can look at CPU usage.  To simulate clients, I’ve got a short F# script I can send to the F# Interactive to add clients on the fly.  So I’ll highlight this F# code and “Send to Interactive”, and now I have 1000 clients hitting the server.  (You can try this yourself with the code attached at the end of this blog entry.  Or just follow along with my narration and imagine you’re seeing on the screen what I’m talking about.)

(Incidentally, this shows a nice use of F#’s Interactive REPL: you can easily write little snippets of test code to test some library or app, whether it be C#, or F#, or whatever.)

Ok, so there’s no flashy UI, but you see the server is handling 1000 quotes per second and we’re hardly using any CPU, maybe just 3-5%.  So everything looks great.  But let’s try adding another 1000 clients.  (I highlight some F# script code and “Send to Interactive” again.)  BOOM, on the screen we get “CSSyncServer has stopped working… Unhandled exception: System.OutOfMemoryException”.  Our server crashed! 

Threads don’t scale well!

Using all those threads ran us out of memory.  Threads are expensive.  We created one thread per client, but our threads were mostly wasted waiting on blocking operations – despite all these threads, we saw the CPU utilization was next to nothing.  After a little more than a thousand clients (and thus a thousand threads, since we had one thread per client), the app falls down, running out of memory since each thread typically needs a megabyte of memory for its call stack.

  • One thread per client is not the right way to write a scalable .NET server.
  • The real solution is to use non-blocking I/O.  This requires switching to Begin/End APIs.  Then we can use the .NET ThreadPool to use only a few threads to service many clients.

So let’s do it right.  It turns out I’ve already written this solution with C#, so let’s have a look at how it performs.

An asynchronous solution in C#, using Begin/End methods

I can switch the “startup project” in the solution to the “CSAsyncServer” project, where I’ve written the same server code in a non-blocking fashion.  That is, I’m using the asynchronous APIs like BeginWrite/EndWrite, so as not to block and to utilize the .NET ThreadPool for callbacks.  If I run that code, I can start the server, and hit it with 1000 clients. Ok, everything looks fine like before, now let’s add another 1000 clients.  (Server keeps running.)  Another 1000.  (Keeps running, starting to show a little CPU usage, in the 10-20% range…) Another 1000 clients.  You get the idea, it scales now.  Hurrah – problem solved?

  • Ok, I’ve convinced you that one-thread-per-client is bad, and that async non-blocking I/O is good…
  • But I did it all with C#.  This is an F# talk, right?  So what’s the story?

The story is that in C#, the async programming model is awful.  Let’s have a look at the C# code written using Begin/End APIs to do async I/O.  Recall back in the synchronous version, our ServiceClient() was this little method we saw before:

static void ServiceClient(TcpClient client)
{
    using (var stream = client.GetStream())
    {
        stream.Write(quote, 0, 1);  // write header
        while (true)
        {
            WriteStockQuote(stream);
        }
    }
}

When I change to using the Begin/End pattern to make it non-blocking, it becomes this monstrosity:

static IAsyncResult BeginServiceClient(TcpClient client, AsyncCallback cb, object state)
{
    var stream = client.GetStream();
    var ar = new ServiceClientAsyncResult(stream, cb, state);

    try
    {
        stream.Write(quote, 0, 1);  // write header
    }
    catch
    {
        stream.Dispose();
        throw;
    }
    var everWentAsync = false;
    Action<Action> wrap = (a) =>
    {
        try { a(); }
        catch (Exception e)
        {
            ar.Complete(!everWentAsync, e);
        }
    };
    Action loop = null;
    Action<IAsyncResult> end = (iar) =>
    {
        wrap(() => EndWriteStockQuote(iar));
    };
    AsyncCallback callback = (iar) =>
    {
        if (iar.CompletedSynchronously)
            return;
        end(iar);
        loop();
    };
    loop = () =>
    {
        wrap(() =>
        {
            while (true)
            {
                var iar = BeginWriteStockQuote(stream, callback, state);
                if (!iar.CompletedSynchronously)
                {
                    everWentAsync = true;
                    break;
                }
                end(iar);
            }
        });
    };
    loop();
    return ar;
}

static void EndServiceClient(IAsyncResult iar)
{
    try
    {
        AsyncResult.End<ServiceClientAsyncResult>(iar);
    }
    finally
    {
        (iar as ServiceClientAsyncResult).Dispose();
    }
}

Gaaaah! You end up duplicating the exception handling for Begin/End calls, putting state in the IAsyncResult object, dealing with CompletedSynchronously (see e.g. the “stack dive” section of this blog by Michael Marucheck for an explanation), … I’m not going to go into all the little details.  You get the point – writing async code in C# is a difficult mess.

So it’s possible to ‘do it right’ in C# to utilize your server hardware and the .NET ThreadPool, but it takes a lot of crazy difficult code.  So now you see why I was showing the C# code in an F# talk: now we can have F# come to the rescue!  Let’s look at the F# code!

F# solutions – sync and async

Here’s the F# synchronous code for serviceClient, it looks very much like the C#:

let serviceClient (client: TcpClient) =
    use stream = client.GetStream()
    stream.Write(quote, 0, 1)  // write header
    while true do
        writeStockQuote(stream)

Now here’s the F# async code:

let asyncServiceClient (client: TcpClient) = async {
    use stream = client.GetStream()
    do! stream.AsyncWrite(quote, 0, 1)  // write header
    while true do
        do! asyncWriteStockQuote(stream) }

See how similar they are?  We just had to wrap the code in an ‘async{…}’ block, and then change “Write” to “AsyncWrite” with a “do!”, and “WriteStockQuote” to “AsyncWriteStockQuote” with a “do!”.  That’s it!  Using F# async, we can write non-blocking I/O, but our code structure stays exactly the same as if we’d written things synchronously.  We can evolve our naïve code or proof of concept ideas smoothly and easily into production quality code.

(Once again I do the demo, this time with the F# async server scaling to thousands of clients.  Cue applause.)

Using F# async has other benefits too.  For example, we get cancellation for free; all F# asyncs use the new .NET 4.0 APIs for cancelation (CancelationTokenSource & CancelationToken) and check for cancelation at every “let!” or “do!”  If I were to add the same checks to the C# code, it would take even more effort there.

The final score

Let’s try to sum this all up.  This slide summarizes what I just demonstrated:

FSharpAsyncServerSlide 

First off, look at column 1: regardless of what language you use, using asynchronous non-blocking I/O is a big win for scaling; you can handle far more concurrent clients this way than by using dedicated threads and synchronous APIs.

Second, looking at the lines of code (LoC) columns objectively, a solution with the traditional .NET Asynchronous Programming Model (Begin/End methods) is much longer and more complex than the sync version (by about three times in this example).  Whereas with F# async, the solutions are about the same length.

Finally, looking at the ‘coding’ columns, just anecdotally, the APM is much harder to reason about and debug.  These numbers describe how long it took me to code and debug these solutions: though it was easy to write the “sync” versions in both C# and F#, it took me about 3 hours to get the C# async example working correctly, compared to about just 10 more minutes for the F# version.  I admit I’m a little out of practice with C# async, but I don’t lack experience: before working on F#, I was a developer on WCF (Windows Communication Foundation), and the entire runtime there is built out of tons of Begin/End calls.  This stuff is just intrinsically difficult to write and debug in C#.

The two take-home messages here are clear:

  • If you are writing .NET server code, you need to use async non-blocking I/O calls to scale.  Otherwise you’ll be throwing money away on extra servers since your software doesn’t scale well to utilize them.
  • F# makes it much easier to write, debug, and reason about async code with non-blocking I/O.

So there you go.  Who would have guessed that a programming language could help save customers money on server hardware?  :)

Source code

You can find a Visual Studio 2010 solution with all the source code here.

11 Responses to “F# async on the server side”

  1. Joel said

    Nice example. I\’ve noticed that one of the new features of ASP.NET MVC 2, Async Controllers, is nearly as convoluted to code as working with the Begin/End API\’s in C#. I thought it would be nice if you could use F# async workflows to write asynchronous controllers in MVC, so I sat down to write support for it. I just got it working last night – you can see an example here: http://wingbeats.codeplex.com/

  2. Cyril said

    2 days ago I had written a multi-threaded approach to a server task. Your example just showed me the error of my ways. Now I need some more input on Begin/end pattern… Going to hit google

  3. Talbott said

    Excellent post Brian. It is amazing how F# makes coding asychronous code so much simpler. And Joel, interesting MVC framework on CodePlex. I downloaded WingBeats and plan to look at it more carefully when I have time.

  4. DaveSF said

    The new C# (5.0?) async/await support looks similar to this async/do! construct. Is the under-the-covers implementation equally similar in concept?

  5. Davesf said

    Has f# done any work on allowing async implementing of ‘standard’ serv
    ice contracts?

    Expressed in c# syntax, I’d like to have a contract like..

    interface IDoSomething {
    string execute(string cmd);
    }

    .. And then create an async server that implements a provider of this contract using async as:

    Class DoSomethingAsync {
    Task async execute(string cmd) { …}
    }

    There would then need to be an async version of the remoting run loop that would call my implementation and await my response.

    Clients should likewise be able to access this remote service either in a synchronous or asynchronous form – from the same service contract. Today’s hack of asking the service providers to define async services as synchronous explicit begin/end contracts is not a desirable solution.

  6. […] while back I read an interesting article by Brian McNamara f-async-on-the-server-side which describes C# and F# versions of a simple asynchronous socket server, one of the driving […]

  7. […] would like to refer you to Brian McNamara’s post that describes this part in more detail.  The only difference in our workflow is that we […]

Leave a reply to Sockets and Bockets Part 1 Cancel reply