18

I have a basic question. Why and how SelectableChannel's register method can be in blocking call. Let me provide a scenario.

I have created a Selector object in class Register as follows.

private static Selector selector = Selector.open();

I also have a method in same class(Register) to register the channel with the selector.

public static SelectionKey registerChannel(SelectableChannel channel, int ops)
                             throws IOException {
   channel.configureBlocking(false);
   return channel.register(selector, ops);
}

And there is another class named Request, which has method which reads the data from channels, processes and calls following method to register the channel.

selectonKey = Register.register(socketChannel, SelectionKey.OP_READ);

Here at this point the thread is blocked, not giving clue of what it is waiting for. I have verified that the selector is open. Please provide me some help to understand how can I resolve this. Is there any lock that I can release.

Any input would be appreciated.

Adding to what I described. Further tests revealed that if the Register.register method is called from the same thread, it is able to register but after that if some other thread tries to invoke the method, thread doesn,t move ahead.

5 Answers 5

40

This is a basic feature of most NIO implementations that isn't obvious from the documentation.

You need to make all register calls from the same thread that is doing your selecting or deadlocks will occur. Usually this is done by providing a queue of registrations/deregistrations/interest-changes that is written to and then selector.wakeup() is called. When the selecting thread wakes up it checks the queue and performs any requested operations.

Sign up to request clarification or add additional context in comments.

4 Comments

This is a basic feature of all NIO implementations that is completely specified in the Javadoc. There are three nested synchronizations while you're in select(), and register() attempts one of them. The result is not a deadlock but a block, which only lasts until the concurrent ` select()` call returns. The solution is to call wakeup() before register(). That forces the select() to unblock and return zero, which releases its three locks, which allows register() to claim the one it needs and proceed.
The provided solution in the above comment is wrong–at least, given the explanation. Depending on the scheduling of the threads, the selecting thread might complete an entire loop and reenter the select() before the registering thread is able to call register().
Current Java 8 javadoc only mentions this issue in SelectableChannel.register(). AbstractSelectableChannel.register omits any mention (and omits some other details). I had code like this: SocketChannel sc = SocketChannel.open(); . . . sc.register(....); The last line calls AbstractSelectableChannel.register(). So if viewing Javadocs in an IDE, you won't see the doc for SelectableChannel.register()
@dsd A short sleep if select() returns zero should take care of that.
14

You need to use a lock and manually synchronize.

In the same thread you are running the selector loop have a ReentrantLock:

final ReentrantLock selectorLock = new ReentrantLock();

Then when you need to register with the selector do something like this:

selectorLock.lock();
try {
    selector.wakeup();
    socketChannel.register(selector, ops);
} finally {
    selectorLock.unlock();
}

Finally, during your loop that you are calling accept(), something like this:

selectorLock.lock();
selectorLock.unlock();

selector.select(500);

And then continue on with the rest of your logic.

This construct guarantees that the register() call will not block by ensuring that there is never another select() between corresponding wakeup() and register() calls.

5 Comments

+1 for the code sample. I second this, from experience. Nicely done.
This adds up to 500 ms of unnecessary latency to each registration. Use the approach in stackoverflow.com/a/2179612/448970 instead.
@DavidB. It doesn't add any latency to each registration. It is the selecting thread that blocks for up to 500ms, not the registering thread, and the selecting thread will always want to block in select() anyway. The claims in the answer you are mostly incorrect.
I got a problem when implementing this. At start, there is no other task than selector.select(500) statements between selectorLock.lock() and selectorLock.unlock() in the selector while loop because no channel is registered yet. the loop turns so fast that another thread is hard to intervene and acquire the selectorLock (takes 30 seconds to minutes). If I print something to the console in the while loop but outside selectorLock.lock() and selectorLock.unlock(), other threads get a chance to acquire the selectorLock. It may need to sleep several ms if no task in the loop.
Objection: there is still a race condition. Suppose the select thread makes it through the lock/unlock lines, and then pauses. Another thread begins the register block, finishes the wakeup line, then pauses. The select thread resumes, and begins the select call. This is a counterexample to the concluding statement. I'm a little fuzzy on the exact conditions that cause deadlock, here, but some form of "select and register happen at the same time", which is reachable from this point in the given scenario, should do it. The timeout may prevent full deadlock, but still waste time.
3

I agree with @Darron's answer that you should pass register calls to the selector thread, but you should not use selector.wakeup as it would introduce race conditions (imagine selector thread was busying processing other registrations and your wakeup fail to wake up anyone). Luckily, Java NIO provided Pipe so that you can let the selector listen for both register calls and other events.

Basically, here's what needs to be done:

val registrationPipe = Pipe.open()
registrationPipe.source().configureBlocking(false)
registrationPipe.source().register(selector, SelectionKey.OP_READ)
// now start your selector thread

// now to register a call from other threads using message pleaseRegisterMe
registrationPipe.sink().write(pleaseRegisterMe)

// inside your selector thread
val selectionKey = iterator.next()
if (selectionKey.channel() === registrationPipe.source()) {
    registrationPipe.source().read(pleaseRegisterMe)
    // do something with the message pleaseRegisterMe and do the actual register
}

Here is a full working example.

Comments

0

Have you tried printing a stack trace of all threads in your program (using either kill -QUIT in Unix or Ctrl+Break in Windows or using the jstack utility)?

AbstractSelectableChannel contains a lock on which configureBlocking and register need to synchronize. This lock also is accessible through the blockingLock() method, and so another thread could potentially be holding the lock causing your register call to block indefinitely (but without a stack trace it's difficult to tell).

Comments

0

Register your channel from any thread:

synchronized (selectorLock2) {
   selector.wakeup();
   synchronized (selectorLock1) {
       channel.register(selector, ops);
   }
}

Your selector loop should look like this:

while (true) {
   synchronized (selectorLock1) {
       selector.select();
   }
   synchronized (selectorLock2) {}

   ....
}

1 Comment

You only need a single lock as show in stackoverflow.com/a/1112809/194894

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.