SSLPreProcessor под большой нагрузкой
Один из наших старых проектов использует образец SSLPreProcessor
от Grizzly 1.9 для связи SSL. К сожалению это SSLPreProcessor
Класс кажется ненадежным при высокой нагрузке, в то время как наше приложение записывает в поток SSL из потока (не Grizzly) таймера. (Стресс-тесты не указывают на какие-либо проблемы со связью без SSL.)
Некоторое исследование ниже, TL;DR находится в конце вопроса.
Мы нашли возможную ошибку вокруг fromSelectionKey()
метод, который используется таким образом:
if (CustomProtocolServer.this.protocol == Controller.Protocol.TLS) {
AsyncQueueDataProcessor preProcessor =
SSLPreProcessor.fromSelectionKey(ctx.getSelectionKey());
ctx.getAsyncQueueWritable().writeToAsyncQueue(b, callback, preProcessor);
} else {
ctx.getAsyncQueueWritable().writeToAsyncQueue(b, callback);
}
Подобные структуры существуют и в официальных примерах, например, в классе CustomProtocolServer.
когда fromSelectionKey()
возвращается null
writeToAsyncQueue()
называется с null
preProcessor
параметр, который на самом деле совпадает с else
блок выше. Если я прав, это портит поток SSL и вызывает нестабильность.
fromSelectionKey()
является следующим:
public static SSLPreProcessor fromSelectionKey(SelectionKey key) {
Object attachmentObj = key.attachment();
if(!(attachmentObj instanceof ThreadAttachment)) {
CustomProtocolHelper.log("SelectionKey : "+key+ " without SSLEngine. Maybe key is not ready yet.");
return null;
}
ThreadAttachment attachment = (ThreadAttachment) attachmentObj;
SSLEngine sslEngine = attachment.getSSLEngine();
if(sslEngine==null) {
CustomProtocolHelper.log("SelectionKey : "+key+ " without SSLEngine. Maybe key is not ready yet.");
return null;
}
...
}
Интересно то, что он используетsslEngine
полеThreadAttachment
, Это поле является простой ссылкой без какой-либо синхронизации (а также его установщик и получатель):
public class ThreadAttachment extends SelectionKeyActionAttachment
implements AttributeHolder {
...
private SSLEngine sslEngine;
...
public SSLEngine getSSLEngine() {
return sslEngine;
}
public void setSSLEngine(SSLEngine sslEngine) {
this.sslEngine = sslEngine;
}
...
}
Таким образом, эти методы не должны использоваться из нескольких потоков без надлежащей синхронизации. В любом случае, как выяснилось во время отладки, мы используем его (косвенно) из нашего потока таймера.
Это поле изменено ГризлиWorkerThreadImpl.updateAttachment()
дважды. updateAttachment()
получает блокировку с помощью метода getAttachment(), который вызывает ThreadAttachment.associate() где associate()
приобретает блокировку
Если наш поток таймера вызываетfromSelectionKey()
этот замок не получен образцомSSLPreProcessor
поэтому иногда он видит null
sslEngine
и, наконец, повреждает поток SSL, записывая напрямую в него (с нулевым препроцессором).
Я пытался изменитьfromSelectionKey()
метод для вызоваThreadAttachment.associate()
а также deassociate()
также, но это вызвало странное поведение приложения (при минимальной загрузке).
Еще одна идея, которую мы попробовали, это добавитьvolatile
модификатор кThreadAttachment.sslEngine
поле и опросить его в цикле на некоторое время:
- public static SSLPreProcessor fromSelectionKey(SelectionKey key) {
+ public static SSLPreProcessor fromSelectionKey(final SelectionKey key) {
+ final Stopwatch stopwatch = Stopwatch.createStarted();
+ while (true) {
+ final SSLPreProcessor preProcessor = fromSelectionKey2(key);
+ if (preProcessor != null) {
+ return preProcessor;
+ }
+ final boolean timeout = stopwatch.elapsed(TimeUnit.SECONDS) > 10;
+ if (timeout) {
+ final Object attachmentObj = key.attachment();
+ return null;
+ }
+ }
+ }
+
+ public static SSLPreProcessor fromSelectionKey2(SelectionKey key) {
На самом деле, это кажется надежным при большой нагрузке в нашей среде стресс-тестирования, хотя приведенное выше доказательство концептуального кода определенно можно улучшить с помощью функции wait/notify, но это все равно будет просто уродливым обходным путем.
TL; DR: образецSSLPreProcessor
от Grizzly 1.9 предназначен для обеспечения безопасности потоков? Если да, то как его использовать из других тем?