Wednesday, August 19, 2009

Improving performance of concurrent WCF calls in Silverlight applications

Many people notice an apparent degradation of latency of concurrent WCF calls in a Silverlight application compared to a similar code in a Windows Console application. This post explains this phenomenon and shows how to optimize performance of concurrent calls in Silverlight.

WCF proxies in Silverlight applications use the SynchronizationContext of the thread from which the web service call is initiated to schedule the invocation of the async event handler when the response is received. When the web service call is initiated from the UI thread of a Silverlight application, the async event handler code will also execute on the UI thread. Given that there is only one UI thread in the Silverlight application, and given it has other chores (primarily managing the UI itself), the average latency of multiple concurrent WCF calls as measured by the code executing on the UI thread may appear to be long. This is because the execution of the event handlers for individual calls needs to be serialized on a single thread and interlaced with other tasks that thread has to perform. The benefit of this model is that the code in the event handler can directly manipulate controls on the page, which is a privilege reserved for code executing on the UI thread. The example below shows how to measure an average latency of multiple WCF calls initiated from a UI thread:

int responseCount;
int totalLatency;
const int MessageCount = 100;

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    TestService.ServiceClient proxy = new TestService.ServiceClient();
    proxy.HelloCompleted += new EventHandler<WcfPerf.TestService.HelloCompletedEventArgs>(proxy_HelloCompleted);
    for (int i = 0; i < MessageCount; i++)
    {
        proxy.HelloAsync("foo", Environment.TickCount);
    }
}

void proxy_HelloCompleted(object sender, WcfPerf.TestService.HelloCompletedEventArgs e)
{
    int end = Environment.TickCount;
    this.responseCount++;
    this.totalLatency += end - (int)e.UserState;           
    if (this.responseCount == MessageCount)
    {
        this.Log.Text = ((double)(this.totalLatency) / MessageCount).ToString();
    }
}   

In order to avoid synchronizing the invocations of event handlers for individual calls on a single UI thread, a worker thread can be used to initiate the web service calls. Newly created worker threads do not have a SynchronizationContext set, which means event handler code for every response from the server will execute on its own thread. This greatly reduces contention in the client application and reduces the average latency of a series of concurrent calls. Consider the code below which uses the worker thread approach:

int responseCount;
int totalLatency;
const int MessageCount = 100;
Thread workerThread;

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    this.workerThread = new Thread(this.StartSending);
    this.workerThread.Start();
}

void StartSending()
{
    TestService.ServiceClient proxy = new TestService.ServiceClient();
    proxy.HelloCompleted += new EventHandler<WcfPerf.TestService.HelloCompletedEventArgs>(proxy_HelloCompleted);
    for (int i = 0; i < MessageCount; i++)
    {
        proxy.HelloAsync("foo", Environment.TickCount);
    }
}

void proxy_HelloCompleted(object sender, WcfPerf.TestService.HelloCompletedEventArgs e)
{
    int end = Environment.TickCount;
    lock (this.workerThread)
    {
        this.responseCount++;
        this.totalLatency += end - (int)e.UserState;
    }
    if (this.responseCount == MessageCount)
    {
        this.Dispatcher.BeginInvoke(delegate
        {
            this.Log.Text = ((double)(this.totalLatency) / MessageCount).ToString();
        });
    }
}

My ad-hoc measurements indicate the average latency of the worker thread approach is about 20% of the average latency of the UI thread approach. The downside of this approach is the more complex way of manipulating the UI controls on the page. The worker thread has to explicitly schedule the code that manipulates the UI to execute on the UI thread, as shown in the proxy_HelloCompleted implementation above.

Thursday, August 13, 2009

Performance of HTTP polling duplex server-side channel in Microsoft Silverlight 3

Introduction

Silverlight supports a communication protocol that allows a server to asynchronously send messages to a Silverlight client (“push”) using Windows Communication Foundation (WCF) services. This feature is based on a polling duplex (HTTP long polling, or Comet-style) protocol, and ships as two DLLs in the Microsoft Silverlight SDK, both named System.ServiceModel.PollingDuplex.dll – one for the Silverlight client and one for the server. More information can be found in MSDN help.

In Silverlight 3, the usability of this feature was greatly improved, and integrated with the Add Service Reference tool in Visual Studio, making it very easy to write client-side code to consume messages “pushed” from the server.

However, writing the server-side “push” service in a way that performs well for a large number of clients, and assessing the viability of the polling duplex protocol for any given application, remained a challenge. This post and the accompanying sample attempt to help you decide whether this technology is suitable for the requirements of your scenario.

Scenarios

Scenarios that were considered for measuring polling duplex performance are based on the pub/sub architecture. Clients can contact the server to subscribe to events associated with a specific topic. When an event is published for that topic, the server sends out notification about that event to all clients who subscribed to the topic.

Within this pub/sub architecture, two classes of scenarios were considered: broadcast and collaboration.

Broadcast Scenario

In this scenario, many clients subscribe to the same topic. When an event is published to that topic, all clients receive a notification.

broadcast

For example, if 3,000 clients are connected, such a scenario could involve all 3,000 clients monitoring the price of a certain stock, or the progress of a certain sporting event, through a Silverlight application. As soon as the stock quote or the sports score changes, the same notification is broadcast to all 3,000 clients.

Collaboration Scenario

In this scenario, several topics exist. Each topic has several clients acting as both subscribers and publishers. Upon publishing of an event for a topic by one of the clients, the server notifies all remaining clients subscribing to this topic of that event.

collaboration

For example, if 3,000 clients are connected, such a scenario could involve 1,500 one-on-one technical support chats between a customer and a technician through a Silverlight chat client, or 1000 real-time games with 3 participants each, or 1,500 collaborations with 2 people editing the same diagram or data grid in real-time. In all these cases, the action of one person immediately gets “pushed” to their collaborator(s), but does not affect the other clients. In reality, many scenarios will fall in between the two extremes of broadcast and 2-client collaboration.

Scalability vs performance

Current implementation of polling duplex protocol in Silverlight 3 requires client affinity to a particular physical server machine for the lifetime of the WCF channel (WCF proxy). Moreover, the server maintains in-memory state for the duration of the session with the client. If you are using a load balancer that cannot guarantee client affinity to a particular backend, or if your hosting infrastructure cannot guarantee that the service will keep running on the same machine, the protocol will fail. In the practice of load balanced web farms, the Silverlight polling duplex protocol in the current form does not scale out well.

You can work around the backend affinity limitation by performing load-balancing manually at the application level. For example, suppose that for your scenario, based on this document or your own measurements, you discover that you can only support 3,000 clients, but need to support 6,000. You can set up two servers, with explicitly different domain names (e.g. at service1.contoso.com and service2.contoso.com), and the client can select one of these to connect to either randomly, based on a hash of a known value (e.g. topic name), or by calling a “discovery service” that returns the address of the duplex service to connect to.

There is no easy way to work around the problem of the server maintaining in-memory session state at the moment. We are actively working on addressing this problem in future releases.

Taking these scalability considerations into account, it is still important to understand performance characteristics of the protocol implementation to assess its suitability for a particular scenario. Performance is the main topic of this post.

Tuning

To optimize the performance of the Polling Duplex component, certain settings in IIS, ASP.NET, the .NET Framework and WCF need to be adjusted. All measurements in this document assume that these modifications have been made. Some of the settings discussed below need to be changed when a system is put into production. Such differences are discussed when appropriate. Please refer to the accompanying sample for a additional tuning information.

WCF configuration tuning

Polling duplex protocol has been implemented on the server side as a WCF binding that provides session channels. Each session channel corresponds to a single client connection. The number of session channels that a WCF service can support concurrently is throttled using ServiceThrottlingBehavior.MaxConcurrentSessions service behavior. In order to measure the maximum number of connections the server can support, this throttle needs to be increased. For the purpose of this measurement, we increased the throttle to Int32.MaxValue using WCF configuration:

<serviceThrottling maxConcurrentSessions="2147483647"/>

When the system is put into production, you would want to set the value of maxConcurrentSessions to match the maximum number of clients your service can support concurrently.

There are several customizations that were introduced in the settings of the PollingDuplexBindingElement for the purposes of this performance measurements:

<binding name="PubSub">
    <binaryMessageEncoding/>
    <pollingDuplex maxPendingSessions="2147483647"
                 maxPendingMessagesPerSession="2147483647"
                 inactivityTimeout="02:00:00"
                 serverPollTimeout="00:05:00"/>
    <httpTransport/>
</binding>

The value of inactivityTimeout controls the maximum time without any activity on the channel before the channel is faulted. The value has been set to 2 hours to avoid a situation when a channel is faulted due to infrequent message exchanges in a test variation. In production, you should set this value to exceed the expected duration of a client connection, which is application specific. Regardless of the value though, the client code should take appropriate fault-tolerance measures to possibly re-establish a connection when the channel is faulted due to inactivity.

The value of serverPollTimeout controls the maximum time the server will hold onto client’s long poll HTTP request before responding. If that time has elapsed without the server having an application message to push back to the client, the server will send back an empty HTTP OK response (causing the client to re-issue a new long poll). For the purpose of this measurement, the value has been set to 5 minutes to minimize the frequency and therefore cost of empty polls. The default value of this setting is 15 seconds. In production, in addition to performance consideration, one should consider the presence of proxy servers which may limit the duration of outstanding HTTP requests.

The value of maxPendingSessions throttles the number of new sessions that wait to be accepted on the server. This situation can occur when the speed at which new sessions are established at the server (new clients connect) exceeds the server’s ability to accept them. This value has been increased to Int32.MaxValue from the default of 4 to allow for the client connection pattern implemented in our performance code, where all the clients attempt to connect at once at the beginning of the test. In a typical production scenario, new client connections are spread more evenly in time, in which case the default value of 4 should be adequate.

The value of the maxPendingMessagesPerSession throttles the number of new messages from a client (sent over a particular session) that wait to be accepted by the server. Similarly to maxPendingSessions, this situation can occur when the speed at which new messages arrive at the server exceeds the server’s ability to accept them. The value has been increased to Int32.MaxValue (the default is 8) to allow for the initial wave of messages given the client connection pattern in the test. In a typical production scenario, messages from the client can be expected to be spread more evenly in time, in which case the default value of 8 should be appropriate.

PollingDuplexHttpBinding standard binding by default uses binary encoding (newly added in Silverlight 3). The custom binding used in the performance measurements also uses binary encoding:

<binaryMessageEncoding/>

Binary encoding has several performance benefits compared to text encoding, which are outlined in the Improving the performance of web services in Silverlight 3 Beta post.

ASP.NET and IIS7 configuration tuning

In .NET Framework 3.5 SP1, WCF introduced a new asynchronous HTTP handler for IIS7 which allows for better scalability of WCF services by not blocking worker threads for the duration of high latency service operations. This feature is described in detail in Wenlong Dong’s blog post about asynchronous WCF HTTP Handlers. In order to realize the full potential of asynchronous HTTP handlers, the concurrent HTTP request quota (MaxConcurrentRequestsPerCPU registry setting) also needs to be adjusted, which is described in more detail in Thomas Marquardt’s post about threading in IIS 6.0 and IIS 7.0.

Registration of the asynchronous HTTP handler for WCF as well as adjustment of the quota for concurrent HTTP requests can we performed with Wenlong Dong’s WcfAsyncWebTool:

WcfAsyncWebTool.exe /ia /t 20000

For the purpose of this test, we are setting the quota at 20000 to ensure it is not going to become the limiting factor in measuring the maximum number of clients the server can accommodate. In production, setting this limit should take into account on one hand a sustainable number of clients (resulting from performance measurements of the actual scenario), as well as an acceptable working set.

Following the setting of the quota for concurrent HTTP requests to 20000, the quotas for the thread pool worker threads and IO threads also need to be adjusted through .NET Configuration in machine.config:

<processModel maxWorkerThreads="20000"
                      maxIoThreads="20000" 
                      minWorkerThreads="10000"/>

These settings ensure there is adequate supply of threads to handle the HTTP requests IIS7 will accept given the concurrent HTTP request quota, as well as for the service to send  bursts of asynchronous notifications to clients.

WCF code tuning

WCF has made it easy to author a well performing RPC service, but requirements and messaging patterns of a pub/sub service are sufficiently different from RPC to require a few performance optimizations in code.

One consideration is that a pub/sub service often sends out multiple identical messages to several clients, in particular in the broadcast scenario. Given that a substantial portion of the cost of sending a message is serializing its content, it is worthwhile to pre-serialize a message once and then send a copy of it multiple times. In order to accomplish this, the callback contract of a pub/sub service should take a Message as a parameter as opposed to typed parameters. This allows the message to be pre-serialized and converted to a MessageBuffer using TypedMessageConverter. Then, for every notification to be sent, the MessageBuffer can be used to create a clone of the Message without incurring the serialization cost.

Sending a large number of notifications from the server is a high latency operation. In order to optimize thread use, sending the notifications to clients in a loop should use asynchronous methods of the callback contract, or enqueue the synchronous invocations using thread poll thread. We did not measure a meaningful difference in performance between these two methods.

The concurrency mode of the WCF service should be set to ConcurrencyMode.Multiple, and instance mode to InstanceMode.Single. This requires explicit synchronization code to be added around access to critical resources (e.g. shared data structures), but the extra effort pays off in reduced contention of concurrent requests to the service.

All of these optimizations are demonstrated in the reference implementation of a pub/sub WCF service using the HTTP polling duplex protocol.

Results

The following server configuration was used in all measurements: 4-proc Intel Xeon 2.66GHz, 4GB RAM, Windows Server 2008 SP1 64bit with .NET Framework 3.5 SP1 and IIS7.

Clients were run on machines other than the server. The number of machines and clients running on each machine was adjusted to achieve the point of saturation of the server.

Message format and content was the same in all measurements. The message consisted of 20 short strings. Although we don’t have formal results as a function of a message size, ad-hoc measurements indicated the results were not affected by message sizes between 1 and 100 strings in a meaningful way.

Broadcast scenario results

The broadcast scenario measurement was based on the following script:

  1. M clients subscribed to a single topic on the server.
  2. The server started generating messages to be published to the topic every P seconds. Upon publishing of a message, M notifications were sent to M clients subscribed to the topic (one per client), which we called a “burst”.
  3. Several runs of the test were performed for increasing numbers of M, up to the point where the server was unable to finish sending all messages in a burst before the next burst was due to be sent. The largest value of M at which the server was able to send all messages of each of at least 100 consecutive bursts before the next burst became due was considered the result of the test for a given burst frequency P.

Results of this measurement are shown on the chart below:

broadcast_results

An example of interpreting this data is that a single server using the HTTP polling duplex protocol from Microsoft Silverlight 3 can support sending notifications to 5000 connected clients every 10 seconds.

Collaboration scenario results

The collaboration scenario measurement was based on the following script, simulating a server supporting multiple chartrooms with many participants each:

  1. T topics (chat rooms) are created on the server.
  2. N distinct clients subscribe to every one of the T topics (the total number of clients connected to the server is then N * T).
  3. One of the clients subscribed to any given topic publishes a single message to that topic. This happens simultaneously for all topics. The server broadcasts the message back to the N-1 other clients subscribed to that topic immediately after receiving the publish message.
  4. After all N-1 clients to whom notifications were sent have received them, no activity occurs in the chat room (topic) for D seconds. The time between the publish message message was sent by the publisher to a topic and received by a subscribers to that topic is captured; we call this metric a latency. Each publish event generates N-1 data points, one for every subscriber of a topic other than the publisher.
  5. Every topic (chat room) repeats the #3-#4 cycle independently (simultaneously) over a minimum of 100 iterations.
  6. The latencies gathered across all iterations, topics, and subscribers are statistically analyzed.

The results for a few distinct sets of defining parameters (number of topics T, number of subscribers per topic N, delay between publications D) are presented below. For each of the variations we have measured the mean, median, and standard deviation of the notification latency.

Topics T

Subscribers per topic N

Total clients N * T

Delay between publications D [s]

Mean latency [ms]

Median latency [ms]

Stdev [ms]

100

5

500

1

91

63

122

500

2

1000

15

4

0

12

500

3

1500

15

108

94

73

800

3

2400

15

2743

1328

3195

1000

2

2000

5

496

498

288

1000

2

2000

15

6

0

17

2000

2

4000

15

25

0

99

Whenever the median latency shows 0ms, it indicates the latency in over 50% of data points was below the threshold of a time span we could capture.

The data indicates a single server can support 2000 simultaneous chat rooms with 2 participants each and a 15 second delay between publications with a 25ms mean latency (0 ms median latency), which should satisfy latency requirements of most UI driven scenarios. At the same time, the data shows that the latency gets out of hand with 800 chatrooms with 3 participants each and 15 second delay between publications.

Wednesday, August 5, 2009

AJAX client for HTTP polling duplex WCF channel in Microsoft Silverlight 3

This sample shows how an AJAX browser application can receive asynchronous data notifications from a WCF service exposed using the HTTP polling duplex protocol that shipped in Microsoft Silverlight 3. The sample code contains a reusable, standalone JavaScript library (sl3duplex.js) which implements the client side of the polling duplex protocol. The full source code and Visual Studio solution is available for download. Please also check the Silverlight development environment prerequisites.

This AJAX sample extends the Silverlight-only pub/sub polling duplex sample I published previously with the following components:

  • sl3duplex.js is a reusable, stand-alone JavaScript library that implements the client side of the HTTP polling duplex protocol compatible with the PollingDuplexHttpBinding in System.ServiceModel.PollingDuplex.dll (a .NET 3.5 library) that shipped in Microsoft Silverlight 3 SDK. The JavaScript library provides the Sl3DuplexProxy class that allows the user to asynchronously send messages to the server and receive asynchronous notifications. The library does not make any assumptions about the service contract (message structure) – it is a conceptual equivalent of IDuplexSession channel in WCF. The library has been tested with Internet Explorer 8, Firefox 3.5.2, and Chrome 2.0.
  • pubsub.js contains classes that represent pub/sub messages sent and received by the pub/sub WCF service in the sample. The classes abstract away the serialization/deserialization of data into XML and are meant to be used in conjunction with the Sl3DuplexProxy. This library is specific to the sample and not intended for direct reuse (meaning I did spend as much time on its quality as on sl3duplex.js).
  • PubSubClientAjax.htm is an AJAX client application that uses the two libraries above to implement a pub/sub client. The functionality of the client is identical to PubSubClient.aspx, a Silverlight 3 version that was discussed in detail in the Silverlight-only pub/sub polling duplex sample.

The Default.aspx page in the sample  contains detailed instructions on how to run it. Below I am going to discuss some of the implementation aspects of the AJAX client.

Sl3DuplexProxy class

The Sl3DuplexProxy class from sl3duplex.js implements the client side of the HTTP polling duplex protocol compatible with PollingDuplexHttpBinding in Microsoft Silverlight 3. It enables client AJAX application to receive asynchronous notifications from a WCF backend service exposed over the polling duplex binding. The class offers a handful of methods and is simple to use.

The constructor of the class allows the user to specify the URL of the backend WCF service, an event handler to be invoked when an asynchronous message arrives from the server, and an event handler to be invoked when an error occurs:

org.janczuk.sl3duplex.Sl3DuplexProxy = function(args /* .url, .onMessageReceived, .onError */)

The PubSubClientAjax.htm uses the constructor in the following way:

proxy = new sl3duplex.Sl3DuplexProxy({
    url: window.location.href.substring(0, window.location.href.lastIndexOf("/")) + "/PubSubService.svc",
    onMessageReceived: function(body) {
        addNotification("SERVER NOTIFICATION: " + (new sl3duplex.NotificationMessage(body)).text);
    },
    onError: function(args) {
        addNotification("ERROR: " + args.error.message);
        alignUIWithConnectionState(false);
    }
});

The URL to the backend service is constructed relative to the URL of the current page.

The onMessageReceived function is invoked every time an asynchronous notification is received from the WCF backend. It accepts one parameter, which is a DOM Element object representing the Body of the SOAP envelope received from the server. In this implementation, the client is using the NotificationMessage helper class from pubsub.js to extract the string content of the message and display it in a textarea of the HTML page.

The onError function is called when a communication or protocol error occurs. The proxy instance is always faulted on error and cannot be used for sending or receiving messages. The function accepts one parameter, which is an object with the following properties:

  • error is an instance of Error that contains the description of the immediate problem
  • httpRequest is an instance of XMLHttpRequest related to the error
  • proxy is an instance of the Sl3DuplexProxy
  • message (optional) represent the content of the message the client attempted to send to the server and is present when the error occurred as a result of the call to Sl3DuplexProxy.send method

After the proxy has been created, the send method can be used to asynchronously send a message to the WCF backend:

org.janczuk.sl3duplex.Sl3DuplexProxy.prototype.send = function(args /* .message.action, .message.body, .onSendSuccessful */)

The first call to send on a proxy creates a new session on the server which in turn enables the client to start receiving notification from the server. As a corollary, the client proxy will not receive any notifications from server before the first call to the send method. The send method allows the user to provide the SOAP action of the message (.message.action), the content of the SOAP Body represented as a string (.message.body), and a handler to be invoked when the server confirms successful reception of the message (.onSendSuccessful).

The PubSubClientAjax.htm uses the send method in the following way:

proxy.send({ message: new sl3duplex.SubscribeMessage(topic),
             onSendSuccessful: function(args) {
                 addNotification("CLIENT ACTION: Subscribed to topic " + topic);
             }
           });

The message object is created using the SubscribeMessage helper class from pubsub.js, which specifies the SOAP action value for that message as well as provides primitive encoding of a string topic name into an XML chunk that will be included in the SOAP Body (the encoding is primitive because the string is embedded “as is”, without proper XML encoding).

The onSendSuccessful is called when the server confirms successful reception of the message with an HTTP 200 response. The function accepts one parameter, which is an object with the following properties:

  • httpRequest is an instance of XMLHttpRequest used to send the message and receive the confirmation
  • proxy is an instance of the Sl3DuplexProxy
  • message is the message instance passed to the send method

Note that when an error occurs when sending the message, the onError handler specified in Sl3DuplexProxy constructor will be called.

When the application is done using the proxy instance (wants to stop receiving notifications), the close method must be explicitly called:

org.janczuk.sl3duplex.Sl3DuplexProxy.prototype.close = function()

Until the close method is called, the proxy will continue issuing HTTP long polls to the server, hence taking up one connection out of the connection pool to the backend, and generating unnecessary network traffic. More details on the actual protocol and connection use are in he subsequent section. The close method can be called any number of times.

The Sl3DuplexProxy instance maintains a notion of its state which can be queried with the getStatus method:

org.janczuk.sl3duplex.Sl3DuplexProxy.prototype.getStatus = function()

The function returns a numeric value representing the state. Sl3DuplexProxy defines defines instance properties of Created, Active, Closed, and Faulted that capture bit masks for inspecting the state of the proxy. Possible proxy states and state transitions are as follows:

  • Created – constructor has been called but the send method has not been called yet. This means the proxy is not receiving any notifications from the server. Calling send method will transition the proxy to Active or Closed | Faulted state, depending on whether the send was successful. Calling close method will transition the proxy to the Closed state.
  • Active – in this state the proxy may receive notifications from the server. It also means the client AJAX application is performing active HTTP polling. Calling close method in this state will transition the proxy to the Closed state. A communication error as a result of calling send or receiving an erroneous notification from the server will transition the proxy to the Closed | Faulted state.
  • Closed – in this state the proxy does not receive notifications and cannot be used for sending messages to the server
  • Faulted – an error occurred and the proxy was faulted. A faulted proxy is also in the Closed state, with the same implications for sending and receiving messages.

Sl3DuplexProxy code has been tested with Internet Explorer 8, Firefox 3.5.2, and Chrome 2.0.

Polling duplex protocol considerations in AJAX

The implementation of the polling duplex protocol in sl3duplex.js has a few interesting traces and limitations.

An application can create many Sl3DuplexProxy instances to different WCF services on the same host (scheme/hostname/port combination) at any point in time. Specifically, creating proxies to services on different hosts at the same time is not supported by the implementation. For example, an application can create two instances of Sl3DuplexProxy, one for http://foo.com:1234/abc.svc, and the other for http://foo.com:1234/def.svc, but not for http://foo.com:1234/abc.svc and http://baz.com:1234/abc.svc. This limitation should not affect any practical scenario, since the underlying XMLHttpRequest stack only supports communication with the origin server of the page without breaching default cross-domain security setting of a browser.

The implementation will start long HTTP polling the moment the send method is called on one of the created proxies. Regardless how many proxy instances are created, there is only one active poll request at any point in time. This means that an AJAX application will use only up to one HTTP connection from the browser pool to perform the HTTP polling at any time, regardless of the number of proxy instances. Note that any messages send asynchronously to the server using the send method will use an additional connection, but the latency of these calls should be low (i.e.  the WCF server side implementation immediately responds to these HTTP request with an empty HTTP 200 response.

The polling continues in the background of an AJAX application as long as there is at least one Sl3DuplexProxy instance in the Active state, so care must be taken to call the close() method on a proxy instance once it has served its purpose.

Using only one connection for polling requires a mechanism to demux server notifications to a client proxy the message is intended for. This is achieved with a notion of a session identifier. Every instance of Sl3DuplexProxy has its own unique session identifier created on instantiation, which can be queried with the getSessionId method:

org.janczuk.sl3duplex.Sl3DuplexProxy = function(args /* .url, .onMessageReceived, .onError */) {
    this.getSessionId = (
        function() {
            var sessionId = org.janczuk.sl3duplex._createGuid();
            return function() { return sessionId; };
        }
    )();

Session identifier of the client proxy is included in all messages sent from that proxy to the server as well as notifications from the server intended for that particular client. This enables demux of notifications received over a single polling connection to the appropriate client proxy instance.

My Photo
My name is Tomasz Janczuk. I am currently working on my own venture - Mobile Chapters (http://mobilechapters.com). Formerly at Microsoft (12 years), focusing on node.js, JavaScript, Windows Azure, and .NET Framework.