4

I have a minimal JMS provider, which sends topic messages over UDP and queue messages over TCP. I use a single selector to handle UDP and TCP selection keys (registering both SocketChannels and DatagramChannels).

My problem is: if I only send and receive UDP packets, everything goes well, but as soon as I start writing on a TCP socket (using Selector.wakeup() to have the selector do the actual writing), the selector enters an infinite loop, returning an empty selection key set, and eating 100% CPU.

The code of the main loop (somewhat simplified) is:

public void run() {
  while (!isInterrupted()) {
   try {
    selector.select();
   } catch (final IOException ex) {
    break;
   }

  final Iterator<SelectionKey> selKeys = selector.selectedKeys().iterator();
  while (selKeys.hasNext()) {
    final SelectionKey key = selKeys.next();
    selKeys.remove();
    if (key.isValid()) {
     if (key.isReadable()) {
      this.read(key);
     }
     if (key.isConnectable()) {
      this.connect(key);
     }
     if (key.isAcceptable()) {
      this.accept(key);
     }
     if (key.isWritable()) {
      this.write(key);
      key.cancel();
     }
    }
   }
   synchronized(waitingToWrite) {
    for (final SelectableChannel channel: waitingToWrite) {
     try {
      channel.register(selector, SelectionKey.OP_WRITE);
     } catch (ClosedChannelException ex) {
      // TODO: reopen
     }
    }
    waitingToWrite.clear();
   }
  }
 }

And for a UDP send (TCP send is similar):

public void udpSend(final String xmlString) throws IOException {
  synchronized(outbox) {
    outbox.add(xmlString);
  }
  synchronized(waitingToWrite) {
    waitingToWrite.add(dataOutChannel);
  }
  selector.wakeup();
}

So, what's wrong here? Should I use 2 different selectors to handle UDP and TCP packets?

G B
  • 2,951
  • 2
  • 28
  • 50
  • I found somebody who has a similar problem: http://web.archiveorange.com/archive/v/xyOAds3Uh2NVesezD5VH. But no solution yet... – G B Nov 02 '10 at 12:43

4 Answers4

1

I suggest you check the return value of select() method.

try {
 if(selector.select() == 0) continue;
} catch (final IOException ex) {
 break;
}

Did you try debugging to see where the loop is?

Edit:

  • I recomend that instead of calling "remove()" on the iterator, you call selectedKeys.clear() after you iterate over them. It is possible that the implementation of the iterator, does not remove it from the underlying set.
  • Check that you are not registering OP_CONNECT on a connected channel.
krico
  • 5,723
  • 2
  • 25
  • 28
  • Already tried to do that, the loop is on the select() call, and the return value is always 0. – G B Nov 02 '10 at 12:39
  • when you do a selector.wakeup(), select() will return with 0 the next time you call it. – krico Nov 02 '10 at 13:55
  • I deleted all wakeup() calls from my code, still doesn't work. However, a single wakeup should not trigger an endless loop. – G B Nov 02 '10 at 14:04
  • Yes, I did. Actually, OP_CONNECT is registered only once, and since selectedKeys is an empty set, the iterator has nothing to do with it. – G B Nov 02 '10 at 16:39
1

Problem went away after upgrading to Java 1.6.0_22.

G B
  • 2,951
  • 2
  • 28
  • 50
1

You are probably getting an IOException and ignoring it in the empty catch block. Never do that. And just continuing after an IOException is practically never the correct action. The only exception to that rule I can think of offhand is a SocketTimeoutException, and you're in non-blocking mode so you won't be getting those, and you don't get them on selectors anyway. I would want to see the content of your methods that handle connect, accept, read, and write.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • The catch block isn't empty, it should break out of the while-loop on I/O Exceptions. However, the problem solved itself by upgrading to the latest 1.6 JDK. – G B Nov 03 '10 at 08:09
  • Why the downvote? Some error in the above? If so it is a courtesy to us all to note it. – user207421 Nov 04 '10 at 00:24
  • Downvote because you didn't even read the code before answering (catch block not empty) and the answer is "you won't be getting any exception anyway". I can't really understand your point. – G B Nov 10 '10 at 12:21
  • Evidently you didn't read the code before downvoting. There is a catch block in the above containing nothing but a break statement. You didn't read what I wrote either. 'You won't be getting those anyway' isn't an 'answer', it refers to SocketTimeoutExceptions in the context of exceptions it is OK *not* to close on. Perhaps you should downvote your own comment. – user207421 Nov 10 '10 at 23:17
  • I still can't understand your point, and can't remove the downvote if you don't edit your answer: "You are probably getting an IOException and ignoring it in the empty catch block" can't be the right answer: the break statement has an effect you are ignoring. – G B Nov 17 '10 at 08:22
  • I'm not ignoring it. The effect is to break out of the select loop altogether, for a reason that will never be known because it isn't logged. It will appear just like a thread interrupt. I've edited my answer to clarify 'exception' vs. 'Exception'. – user207421 Nov 21 '10 at 09:12
0

Modify your design to have one thread per incoming network connection.

The selector should be used when you're using one thread to process incoming messages on multiple TCP sockets. You register each socket with the selector and then select(), which blocks until there is data available on one of them. Then you loop through each key and process the waiting data. This is the method I've always used when writing C code, and it will work, but I don't think it's the best way to do it in Java.

Java has great native thread support, which C does not. I think it makes a lot more sense to have one thread per TCP socket and not to use selectors at all. If you just do a read operation on a socket, the thread will block until data arrives or the socket is closed. This is effectively the same thing as selecting with only one registered channel.

If you want to get this to work with only one thread, you should use the selector only for TCP sockets where you want incoming connections. This way, the only time the call to select() will return is when there is incoming data waiting on one of the sockets. That thread will be asleep at all other times, and no other operation will wake it up.

Erick Robertson
  • 32,125
  • 13
  • 69
  • 98
  • I don't see why he shouldn't try to work on a single-threaded paradigm. It is certainly valid performance wise. – krico Nov 02 '10 at 12:03
  • I'd like to have all together in a single thread, since this class is both a client and a server. – G B Nov 02 '10 at 12:42
  • I find myself in a similar situation, and I am choosing a similar design pattern. I am now using one thread to accept incoming connections and to read from open connections. I almost would delete this answer, but someone might find this point of view useful. – Erick Robertson Sep 02 '11 at 12:17