Bug 50495 - Comet connector needs ability to access the client send queue.
Summary: Comet connector needs ability to access the client send queue.
Status: RESOLVED WONTFIX
Alias: None
Product: Tomcat 7
Classification: Unclassified
Component: Connectors (show other bugs)
Version: 7.0.5
Hardware: PC All
: P2 enhancement (vote)
Target Milestone: ---
Assignee: Tomcat Developers Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2010-12-18 00:18 UTC by goberman
Modified: 2011-01-04 10:38 UTC (History)
1 user (show)



Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description goberman 2010-12-18 00:18:04 UTC
I use NIO HTTP Tomcat connector org.apache.coyote.Http11NioProtocol to
implement Comet streaming to browsers and mobile devices. 
The application opens 'primary' persistent Comet connection to receive data
from the server. Server sends data to the client periodically.

It will be possible that one of the clients is "slow reader" for various reasons. If I call PrintWriter.print("message") to send data to such client, the message will be queued internally by the Tomcat "Comet Processor". If message flow is heavy, the client message queues may become large and Tomcat can run out of memory. I was able to duplicate this quite easily by sending messages in a loop.

It will be critical to address these issues for any "production quality" application.

I would like to request 2 enhancements to address this:
1) I would like to somehow "compress" the pending client's queue. Messages I send are financial market data messages. So if I send message for stock IBM and the message is queued, then when a new message for IBM is generated, I should be able to detect that there is a message in the queue pending to be sent and just replace content of the one already pending instead of queuing a new one.

2) If queue for a client reaches a high water mark, I should be able to detect it, disconnect the client and delete the queue.

This is how data is sent now with Tomcat Comet:
PrintWriter out = response.getWriter();
out.print("my message");



This is how I would prefer it to work. This is very rough idea.
// Comet PrintWriter will be exposed:
CometPrintWriter out = (CometPrintWriter)response.getWriter();

// detect queue size and disconnect if necessary.
int queueSize = out.getQueue().size()

// get cached message for the stock symbol
ICometMessage existingMessageForTheSymbol = ...
if (existingMessageForTheSymbol == null) {
    ICometMessage newMessage = new MyMessage(...)
    out.addMessage(newMessage);
    // cache the newMessage in a Map.
}
else {
    if (!existingMessageForTheSymbol.replaceMessage(...)) {
        // message is already sent. need to create a new one:
        ICometMessage newMessage = new MyMessage(...)
        out.addMessage(newMessage);
        // cache the newMessage in a Map.
    }
}

// this interface would be a part of Tomcat Comet
interface ICometMessage {
   // called by Tomcat comet before message is sent.
   void beforeMessageSent();

   // called by Tomcat comet after message is sent.
   void afterMessageSent();
}

class MyMessage implements ICometMessage {
   ReentrantLock lock = ...;
   boolean messageSent;

   public void beforeMessageSent() {
      lock.lock();
      messageSent = true;
   }

   public void afterMessageSent() {
      lock.unlock();
   }

   public boolean replaceMessage(...) {
      lock.lock();
      if (messageSent) {
         lock.unlock();
         return false;
      }

      // replace message data.

      lock.unlock();

      return true;
   }
}
Comment 1 Mark Thomas 2011-01-04 09:42:15 UTC
This is an enhancement request, not a blocker.

This enhancement looks like something that is application specific. There are various reasons why a generic solution would be difficult:
- the Comet API (and all of Tomcat's internals) has no concept of a message
- bytes written to clients are buffered are various points both under Tomcat's control and completely outside of Tomcat's control
- the criteria for compressing messages is something that would be very hard to make generic

With all this in mind, I am resolving this as WONTFIX.

That said, if there are changes to the Comet API you would like to propose (in the form of a patch) that would make an application level implementation of these features easier, feel free to re-open this issue and attach your proposed patch.
Comment 2 goberman 2011-01-04 09:56:22 UTC
Mark, 
this is not application specific. I have no idea when data is written to a socket from Tomcat.
I need to know when you write messages "under Tomcat control" - this is all I need. I am sure you have some concept of "client message queue".
This is an a enhancement to address a bug in my opinion. As of now, a single client has the ability to deplete resources of the whole server. This makes Tomcat Comet technology a toy for college projects at best. Completely unsuitable for production application.
I have no choice but to look for alternative implementations.
Comment 3 Mark Thomas 2011-01-04 10:38:25 UTC
(In reply to comment #2)
> I have no idea when data is written to a socket from Tomcat.
This is open source. You could have looked it up. The users list is always very helpful to folks trying to understand Tomcat's internals and needing a few pointers. The short version is:
'out' -> 8k buffer -> socket

> I need to know when you write messages "under Tomcat control" - this is all I
> need.
It depends. The above buffer is flushed if it is full or if a call is made to out.flush(). Once the buffer is flushed, Tomcat has no control over the data.

> I am sure you have some concept of "client message queue".
As I said before, Tomcat has no concept of a message so there is no message queue. The bytes are treated as just that - bytes. There is nothing the delimits messages which are an application defined concept. If there are 5,000 bytes to be written the client, Tomcat has no idea if they represent 10*500 byte message or 1000*5 byte messages.

> This is an a enhancement to address a bug in my opinion. As of now, a single
> client has the ability to deplete resources of the whole server.
That shouldn't happen. The NIO connector uses simulated blocking so the call to out.write() should block if the socket send buffer is full. If memory serves me correctly, that is around 30k. If you have a simple test case that demonstrates an OOME then please re-open this issue, attach it and someone will take a look.

> This makes  Tomcat Comet technology a toy for college projects at best.
> Completely unsuitable for production application.
That sort of comment isn't going to encourage any of the folks here (who are all volunteers) to help you.

> I have no choice but to look for alternative implementations.
You are, of course, free to do that is you wish.