diff --git a/src/main/java/org/java_websocket/SSLSocketChannel2.java b/src/main/java/org/java_websocket/SSLSocketChannel2.java index 23c4f8af..dadcd8b7 100644 --- a/src/main/java/org/java_websocket/SSLSocketChannel2.java +++ b/src/main/java/org/java_websocket/SSLSocketChannel2.java @@ -105,6 +105,12 @@ public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel, ISSLC **/ protected int bufferallocations = 0; + /** + * 2022-06-17 Handshake start time in WSS for the underlying channel. + * If wss handshake is not completed in 10s, close this channel to prevent cpu overload or unexpected channel error. see #896. + */ + protected long handshakeStartTime = System.currentTimeMillis() ; + public SSLSocketChannel2(SocketChannel channel, SSLEngine sslEngine, ExecutorService exec, SelectionKey key) throws IOException { if (channel == null || sslEngine == null || exec == null) { @@ -396,8 +402,24 @@ public void close() throws IOException { private boolean isHandShakeComplete() { HandshakeStatus status = sslEngine.getHandshakeStatus(); - return status == SSLEngineResult.HandshakeStatus.FINISHED - || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + + // handshake status + boolean ret = status == SSLEngineResult.HandshakeStatus.FINISHED + || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + + if (ret == false) { + // 2022-06-17 If wss handshake is not completed in 10s, close this channel to prevent cpu overload or unexpected channel error. see #896. + if (handshakeStartTime > 0 && (System.currentTimeMillis() - handshakeStartTime) > 10000) { + try { + close(); + } catch(Exception exception){ + exception.printStackTrace(); + }; + ret = true; + } + } + + return ret; } public SelectableChannel configureBlocking(boolean b) throws IOException { @@ -498,4 +520,4 @@ private void tryRestoreCryptedData() { saveCryptData = null; } } -} \ No newline at end of file +} diff --git a/src/main/java/org/java_websocket/server/HighCycleThrottler.java b/src/main/java/org/java_websocket/server/HighCycleThrottler.java new file mode 100644 index 00000000..dc858cdb --- /dev/null +++ b/src/main/java/org/java_websocket/server/HighCycleThrottler.java @@ -0,0 +1,69 @@ +package org.java_websocket.server; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * HighCycleThrottler is a class that checks for high cycling caused by the jdk epoll selector bug. For some + * reason the selector.select() code becomes not blocking causing a high CPU condition. When this condition is detected, + * this class will add a 1ms delay to keep from loading up the CPU when the selector.select() code is no longer blocking. + * */ +public class HighCycleThrottler +{ + private final Logger log = LoggerFactory.getLogger( HighCycleThrottler.class ); + private static final int CYCLE_THRESHOLD = 600 * 60; // 600 cycles a second for 60 seconds; + private final boolean enabled; + private long nextCycle; + private long cyclesPerMinute = 0; + private boolean highCPUDetected = false; + + public HighCycleThrottler() { + enabled = isEnabled( ); + nextCycle = System.currentTimeMillis() + 60 * 1000; + } + + /** + * Checks for high cycling and throttles with a 1ms delay if detected. + */ + public void checkHighCycleRate() { + if ( enabled ) { + cyclesPerMinute++; + if ( System.currentTimeMillis() >= nextCycle ) { + String cycles = String.format( "Cycles last minute = %d", cyclesPerMinute ); + log.warn( cycles ); + + if ( cyclesPerMinute > CYCLE_THRESHOLD ){ + if( !highCPUDetected ){ + highCPUDetected = true; + log.warn( "High CPU condition detected" ); + } + } else if ( highCPUDetected ) { + log.warn( "High CPU condition cleared" ); + highCPUDetected = false; + } + + nextCycle = System.currentTimeMillis() + 60 * 1000; + cyclesPerMinute = 0; + } + + if ( highCPUDetected ) { + try { + Thread.sleep( 1L ); + } catch ( InterruptedException e ) { + log.warn( "Thread.sleep(1L) failed" ); + } + } + } + } + + /** + * Set the enabled flag and log if USE_EPOLL_SELECTOR_FIX is defined. + */ + private boolean isEnabled() { + if (System.getenv( "USE_EPOLL_SELECTOR_FIX" ) != null){ + log.warn( "Using EPoll Selector Fix" ); + return true; + } + return false; + } +} diff --git a/src/main/java/org/java_websocket/server/WebSocketServer.java b/src/main/java/org/java_websocket/server/WebSocketServer.java index e4f8790e..bf091964 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -383,6 +383,7 @@ public void run() { if (!doSetupSelectorAndServerThread()) { return; } + HighCycleThrottler highCycleThrottler = new HighCycleThrottler(); try { int shutdownCount = 5; int selectTimeout = 0; @@ -432,6 +433,7 @@ public void run() { // FIXME controlled shutdown (e.g. take care of buffermanagement) Thread.currentThread().interrupt(); } + highCycleThrottler.checkHighCycleRate(); } } catch (RuntimeException e) { // should hopefully never occur