Seyed Mohammad
Seyed Mohammad

Reputation: 870

How to find and cancel idle channels of a selector?

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 ?

Upvotes: 1

Views: 1525

Answers (2)

mike
mike

Reputation: 5055

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

Upvotes: 1

user207421
user207421

Reputation: 311023

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().

Upvotes: 0

Related Questions