From 064974fdd5217838780f521ca27cea810b5bb315 Mon Sep 17 00:00:00 2001 From: xirtameht <59612894+xirtameht@users.noreply.github.com> Date: Wed, 6 Oct 2021 08:25:09 -0400 Subject: [PATCH] Added HighCycleThrottler class to mitigate high cpu load defect. --- .../server/HighCycleThrottler.java | 69 +++++++++++++++++++ .../server/WebSocketServer.java | 2 + 2 files changed, 71 insertions(+) create mode 100644 src/main/java/org/java_websocket/server/HighCycleThrottler.java 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 11073619..769610e8 100644 --- a/src/main/java/org/java_websocket/server/WebSocketServer.java +++ b/src/main/java/org/java_websocket/server/WebSocketServer.java @@ -362,6 +362,7 @@ public void run() { if (!doSetupSelectorAndServerThread()) { return; } + HighCycleThrottler highCycleThrottler = new HighCycleThrottler(); try { int shutdownCount = 5; int selectTimeout = 0; @@ -411,6 +412,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