Здравствуйте.
Я делаю сервер на неблокирующих сокетах. К нему будут подключаться всякие разные клиенты и общаться с сервером по протоколу. В протоколе определены пакеты типа "начало соединения" и "конец соединения". В норме, клиент устанавливает TCP соединение с сервером, посылает пакет "начало соединения", сервер проверяет валидность логина с паролем из этого пакета и отвечает "соединение - ок". Далее клиент асинхронно отправляет большим потоком кучу сообщений, которые обрабатываются сервером в ThreadPoolExecutor.
В какой-то момент одна из сторон хочет завершить соединение и отправляет пакет "конец соединения". Другая сторона отвечает "конец соединения - ок" и после этого сокет закрывается.
Всё работает хорошо, пока администратор не решает перезапустить сервер. Для этого jvm процессу посылается сигнал SIGTERM, который обрабатывается достаточно прямолинейно - устанавливается interrupt статус всем потокам.
При этом я хочу корректно завершить все клиентские соединения, обработав до конца всё ещё выполняющиеся запросы, отправив каждому клиенту по пакету "завершение соединения", дождаться от каждого "завершение соединения - ок", закрыть сокет и спокойно завершить поток.
Проблема в том, что сокет закрывается с исключением ClosedByInterruptException на первом же вызове read()/write() совершенным после установки статуса прерывания. И это закрывает для меня возможности красиво отсоединить клиентов.
Вопрос в том, как сделать так, чтобы сокет не закрывался при поступлении запроса на прерывание?
Код, иллюстрирующий ситуацию, можно созерцать по ссылке:
pastebin.com/nVsVvWXq
Варианты, которые меня не устраивают:
1. Как-то ещё оповестить процесс о том, что нужно завершиться.
Тут придётся везде понатыкать флаги-индикаторы завершения работы. При этом я лишаюсь возможности прервать другие блокирующие операции (типа Thread.sleep()), которые могут выполняться потенциально долго и получаю лишний код. Ещё администратору придётся помнить, что данный конкретный процесс особенный и его нельзя завершить как любой другой процесс в системе.
2. Сделать всё на традиционных блокирующих сокетах.
Это не годится, потому что у меня слишком много клиентов, чтобы каждому выделять по потоку. Собственно, из-за этого я и переделываю сетевую часть на NIO.
3. Натыкать перед каждым вызовом read()/write() проверку interrupt статуса. Что-то типа:
if ( Thread.interrupted() ) {
// ...
// завершающие действия
// ...
socketChannel.write(byeBye);
// ...
socketChannel.read(byeByeOk);
socketChannel.close();
Thread.currentThread().interrupt();
} else {
socketChannel.read(inputBuffer);
}
Но это проблемы не решает, так как статус прерывания может быть установлен ровно между вызовами Thread.interrupted() и socketChannel.read().
Хотя и уменьшает вероятность её возникновения.
4. Переделать на NIO.2.
Я проверил, при работе с AsynchronousSocketChannel принудительного закрытия сокета не происходит и проблемы просто не существует. Но я ограничен java 6.
5. Так же я не хочу рассматривать варианты с модификацией протокола, клиентов, привлечении сторонних библиотек и софта. То есть вопрос принципиально про NIO и поведение Channel'ов при установке потоку interrupted статуса. Мне кажется, что не могли архитекторы NIO не предусмотреть подобный сценарий. Должен же быть обходной путь, но я его не вижу или просто задаю неправильные вопросы гуглу.