diff --git a/src/main/java/dan200/computercraft/ComputerCraft.java b/src/main/java/dan200/computercraft/ComputerCraft.java index 440b4e5cd..32ff07327 100644 --- a/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/src/main/java/dan200/computercraft/ComputerCraft.java @@ -52,6 +52,8 @@ public final class ComputerCraft public static int httpMaxRequests = 16; public static int httpMaxWebsockets = 4; + public static int httpDownloadBandwidth = 32 * 1024 * 1024; + public static int httpUploadBandwidth = 32 * 1024 * 1024; public static boolean enableCommandBlock = false; public static int modemRange = 64; diff --git a/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java b/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java index f3804f847..d5b1b18f2 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java +++ b/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java @@ -20,6 +20,8 @@ import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.timeout.ReadTimeoutException; +import io.netty.handler.traffic.AbstractTrafficShapingHandler; +import io.netty.handler.traffic.GlobalTrafficShapingHandler; import javax.annotation.Nonnull; import javax.net.ssl.SSLException; @@ -28,9 +30,7 @@ import java.net.InetSocketAddress; import java.net.URI; import java.security.KeyStore; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** @@ -38,10 +38,8 @@ */ public final class NetworkUtils { - public static final ExecutorService EXECUTOR = new ThreadPoolExecutor( - 4, Integer.MAX_VALUE, - 60L, TimeUnit.SECONDS, - new SynchronousQueue<>(), + public static final ScheduledThreadPoolExecutor EXECUTOR = new ScheduledThreadPoolExecutor( + 4, ThreadUtils.builder( "Network" ) .setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 ) .build() @@ -52,6 +50,15 @@ public final class NetworkUtils .build() ); + public static final AbstractTrafficShapingHandler SHAPING_HANDLER = new GlobalTrafficShapingHandler( + EXECUTOR, ComputerCraft.httpUploadBandwidth, ComputerCraft.httpDownloadBandwidth + ); + + static + { + EXECUTOR.setKeepAliveTime( 60, TimeUnit.SECONDS ); + } + private NetworkUtils() { } @@ -107,6 +114,16 @@ public static SslContext getSslContext() throws HTTPRequestException } } + public static void reloadConfig() + { + SHAPING_HANDLER.configure( ComputerCraft.httpUploadBandwidth, ComputerCraft.httpDownloadBandwidth ); + } + + public static void reset() + { + SHAPING_HANDLER.trafficCounter().resetCumulativeTime(); + } + /** * Create a {@link InetSocketAddress} from a {@link java.net.URI}. * diff --git a/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java b/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java index 2049575ff..74957b204 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java +++ b/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java @@ -167,6 +167,7 @@ protected void initChannel( SocketChannel ch ) } ChannelPipeline p = ch.pipeline(); + p.addLast( NetworkUtils.SHAPING_HANDLER ); if( sslContext != null ) { p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) ); diff --git a/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java b/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java index 155540560..046efe79d 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java +++ b/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java @@ -145,6 +145,7 @@ private void doConnect() protected void initChannel( SocketChannel ch ) { ChannelPipeline p = ch.pipeline(); + p.addLast( NetworkUtils.SHAPING_HANDLER ); if( sslContext != null ) { p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) ); diff --git a/src/main/java/dan200/computercraft/shared/CommonHooks.java b/src/main/java/dan200/computercraft/shared/CommonHooks.java index 75db335cf..9a30a763e 100644 --- a/src/main/java/dan200/computercraft/shared/CommonHooks.java +++ b/src/main/java/dan200/computercraft/shared/CommonHooks.java @@ -6,6 +6,7 @@ package dan200.computercraft.shared; import dan200.computercraft.ComputerCraft; +import dan200.computercraft.core.apis.http.NetworkUtils; import dan200.computercraft.core.computer.MainThread; import dan200.computercraft.core.tracking.Tracking; import dan200.computercraft.shared.command.CommandComputerCraft; @@ -83,6 +84,7 @@ public static void onServerStarted( FMLServerStartedEvent event ) ComputerCraft.serverComputerRegistry.reset(); WirelessNetwork.resetNetworks(); Tracking.reset(); + NetworkUtils.reset(); } @SubscribeEvent @@ -91,6 +93,7 @@ public static void onServerStopped( FMLServerStoppedEvent event ) ComputerCraft.serverComputerRegistry.reset(); WirelessNetwork.resetNetworks(); Tracking.reset(); + NetworkUtils.reset(); } public static final ResourceLocation LOOT_TREASURE_DISK = new ResourceLocation( ComputerCraft.MOD_ID, "treasure_disk" ); diff --git a/src/main/java/dan200/computercraft/shared/Config.java b/src/main/java/dan200/computercraft/shared/Config.java index 4fb4f2c7d..ab29dc4ec 100644 --- a/src/main/java/dan200/computercraft/shared/Config.java +++ b/src/main/java/dan200/computercraft/shared/Config.java @@ -12,6 +12,7 @@ import com.google.common.base.Converter; import dan200.computercraft.ComputerCraft; import dan200.computercraft.api.turtle.event.TurtleAction; +import dan200.computercraft.core.apis.http.NetworkUtils; import dan200.computercraft.core.apis.http.options.Action; import dan200.computercraft.core.apis.http.options.AddressRuleConfig; import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer; @@ -56,6 +57,9 @@ public final class Config private static final ConfigValue httpMaxRequests; private static final ConfigValue httpMaxWebsockets; + private static final ConfigValue httpDownloadBandwidth; + private static final ConfigValue httpUploadBandwidth; + private static final ConfigValue commandBlockEnabled; private static final ConfigValue modemRange; private static final ConfigValue modemHighAltitudeRange; @@ -187,6 +191,20 @@ private Config() {} .comment( "The number of websockets a computer can have open at one time. Set to 0 for unlimited." ) .defineInRange( "max_websockets", ComputerCraft.httpMaxWebsockets, 1, Integer.MAX_VALUE ); + builder + .comment( "Limits bandwidth used by computers" ) + .push( "bandwidth" ); + + httpDownloadBandwidth = builder + .comment( "The number of bytes which can be downloaded in a second. This is shared across all computers. (bytes/s)" ) + .defineInRange( "global_download", ComputerCraft.httpDownloadBandwidth, 1, Integer.MAX_VALUE ); + + httpUploadBandwidth = builder + .comment( "The number of bytes which can be uploaded in a second. This is shared across all computers. (bytes/s)" ) + .defineInRange( "global_upload", ComputerCraft.httpUploadBandwidth, 1, Integer.MAX_VALUE ); + + builder.pop(); + builder.pop(); } @@ -329,6 +347,8 @@ public static void sync() ComputerCraft.httpMaxRequests = httpMaxRequests.get(); ComputerCraft.httpMaxWebsockets = httpMaxWebsockets.get(); + ComputerCraft.httpDownloadBandwidth = httpDownloadBandwidth.get(); + NetworkUtils.reloadConfig(); // Peripheral ComputerCraft.enableCommandBlock = commandBlockEnabled.get();