1

Let's say we have a Java NIO Selector that selects with a timeout on multiple SocketChannels for read operations:

Selector selector = Selector.open();
channel1.register(selector, SelectionKey.OP_READ);
channel2.register(selector, SelectionKey.OP_READ);
channel3.register(selector, SelectionKey.OP_READ);
channel4.register(selector, SelectionKey.OP_READ);
// ... maybe even more ...

while (true) {
    if (selector.select(TIMEOUT) > 0) {
        Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
        while (iterator.hasNext()) {
            SelectionKey key = iterator.next();
            if (key.isValid() && key.isReadable())
                read(key);
            iterator.remove();
        }
    } else {
        // All channels timed-out! Cancel and close them all
        cancelAndCloseAll(selector.keys());
    }
}

We need to cancel and close a channel if it's been idle for a specific time, and that's why we're using the selector.select(TIMEOUT) method.

But this doesn't work if we have a few very active channels. Those active channels will never let the select to timeout, while all the other channels might be idle ...

A naive solution for this is as follows (also mentioned here) :

Attach the last time of activity to each channel's SelectionKey using the key.attach(object) method. After each successful select, update the activity time of all ready keys. Then iterate over all the keys and find the ones that have been idle more than the specific threshold.

This could be really inefficient because the active channels will cause the select to fire very frequently and everytime iterating all over the keys set.

So is there a better (more efficient) way of solving this problem ?

Community
  • 1
  • 1
Seyed Mohammad
  • 798
  • 10
  • 29

2 Answers2

1

You could adjust your naive solution:

  • Update the time of ready keys after select
  • Only iterate over all the other keys, if a specific time intervall has passed
    • Keep a global variable threshold, and a variable last_check_time, compute the current time every time a key is ready, if (last_check_time - current time) > threshold, set last_check_time to current time and iterate over the keys.
    • Or use a single timertask to start the iteration checks
mike
  • 4,929
  • 4
  • 40
  • 80
  • Yes, this is exactly the solution I came up and explained in my last comment to EJP. – Seyed Mohammad Nov 19 '14 at 03:42
  • Not exactly. Point #4 is, but if you use point #3, you don't have to use an additional thread for the timing. But the efficiency depends on the mean idle time. – mike Nov 19 '14 at 11:50
0

Use a Java.util.Timer, and submit a TimerTask that closes the channel after the idle period. Cancel the task if it exists and submit a new one when you get activity once channel before it runs. Save the TimerTasks somewhere you can find them by channel, e,g, in a session object stored as the key attachment, which would also hold the channel's ByteBuffer(s), user ID, etc, whatever else you need per session.

You'll run into concurrency issues that will require you to either wakeup the selector in the timer task, and have the selector thread cope correctly with being awoken when nothing is ready (select() returns zero), or put up with channels not being closed exactly on time, due to the timer task blocking in close() while the selector is blocking in select().

user207421
  • 305,947
  • 44
  • 307
  • 483
  • Thanks. If I understand you correctly, you are suggesting to create a new `TimerTask` object for each channel, and submit it to a `Timer`; Right? But I don't think this is a good idea, because new channels arrive and are accepted at different rates, and sometimes very frequently. This means frequently creating short living objects (`TimerTasks`), which wastes memory. If I'm mistaken, please correct me. – Seyed Mohammad Oct 09 '13 at 14:18
  • You have to trade off space versus time in most programming choices. Personally I use the first method you mentioned, a scan when select() returns zero. – user207421 Oct 09 '13 at 21:12
  • I think I've found a better idea: Just like the previous solution, we `attach` the latest activity time to each key, but instead of scanning the entire key set every time the `select` returns (zero or not), we set up a `Timer` (or `Thread`) that every `TIMEOUT` interval, scans the entire key set of the `selector` for channels that have timed out? Isn't this solution more efficient both regarding memory and CPU time? Of course, I think there are concurrency issues that have to be solved here... Right? – Seyed Mohammad Oct 11 '13 at 05:47