From 876efee1a8dd67436cb50268b6ba4836b2368f05 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Thu, 7 May 2020 17:51:56 +0100 Subject: [PATCH 1/7] Use keepalives to detect dead connections. --- .../bramble/plugin/bluetooth/AndroidBluetoothPlugin.java | 5 +++-- .../plugin/bluetooth/AndroidBluetoothPluginFactory.java | 4 +++- .../bramble/plugin/bluetooth/BluetoothPlugin.java | 9 +++++---- .../bramble/plugin/bluetooth/JavaBluetoothPlugin.java | 5 +++-- .../plugin/bluetooth/JavaBluetoothPluginFactory.java | 4 +++- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java index f43812ad5..19fd8cb8c 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java @@ -78,9 +78,10 @@ class AndroidBluetoothPlugin extends BluetoothPlugin { AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter, Executor ioExecutor, AndroidExecutor androidExecutor, Context appContext, SecureRandom secureRandom, Clock clock, - Backoff backoff, PluginCallback callback, int maxLatency) { + Backoff backoff, PluginCallback callback, int maxLatency, + int maxIdleTime) { super(connectionLimiter, ioExecutor, secureRandom, backoff, callback, - maxLatency); + maxLatency, maxIdleTime); this.androidExecutor = androidExecutor; this.appContext = appContext; this.clock = clock; diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java index 6dd5c094f..f74967577 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java @@ -25,6 +25,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; public class AndroidBluetoothPluginFactory implements DuplexPluginFactory { private static final int MAX_LATENCY = 30 * 1000; // 30 seconds + private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins private static final double BACKOFF_BASE = 1.2; @@ -68,7 +69,8 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory { MAX_POLLING_INTERVAL, BACKOFF_BASE); AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin( connectionLimiter, ioExecutor, androidExecutor, appContext, - secureRandom, clock, backoff, callback, MAX_LATENCY); + secureRandom, clock, backoff, callback, MAX_LATENCY, + MAX_IDLE_TIME); eventBus.addListener(plugin); return plugin; } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java index 0dec35bc5..1c574dd6b 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java @@ -65,7 +65,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { private final SecureRandom secureRandom; private final Backoff backoff; private final PluginCallback callback; - private final int maxLatency; + private final int maxLatency, maxIdleTime; private final AtomicBoolean used = new AtomicBoolean(false); private volatile boolean running = false, contactConnections = false; @@ -106,13 +106,15 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter, Executor ioExecutor, SecureRandom secureRandom, - Backoff backoff, PluginCallback callback, int maxLatency) { + Backoff backoff, PluginCallback callback, int maxLatency, + int maxIdleTime) { this.connectionLimiter = connectionLimiter; this.ioExecutor = ioExecutor; this.secureRandom = secureRandom; this.backoff = backoff; this.callback = callback; this.maxLatency = maxLatency; + this.maxIdleTime = maxIdleTime; } void onAdapterEnabled() { @@ -141,8 +143,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { @Override public int getMaxIdleTime() { - // Bluetooth detects dead connections so we don't need keepalives - return Integer.MAX_VALUE; + return maxIdleTime; } @Override diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java index 59a105f20..62fad72a8 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java @@ -35,9 +35,10 @@ class JavaBluetoothPlugin extends BluetoothPlugin { JavaBluetoothPlugin(BluetoothConnectionLimiter connectionManager, Executor ioExecutor, SecureRandom secureRandom, - Backoff backoff, PluginCallback callback, int maxLatency) { + Backoff backoff, PluginCallback callback, int maxLatency, + int maxIdleTime) { super(connectionManager, ioExecutor, secureRandom, backoff, callback, - maxLatency); + maxLatency, maxIdleTime); } @Override diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java index a2ada859d..62e51c6c8 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java @@ -21,6 +21,7 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; public class JavaBluetoothPluginFactory implements DuplexPluginFactory { private static final int MAX_LATENCY = 30 * 1000; // 30 seconds + private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins private static final double BACKOFF_BASE = 1.2; @@ -56,7 +57,8 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter, - ioExecutor, secureRandom, backoff, callback, MAX_LATENCY); + ioExecutor, secureRandom, backoff, callback, MAX_LATENCY, + MAX_IDLE_TIME); eventBus.addListener(plugin); return plugin; } From f2f278c3933ff22e789c16e3114af34a75639daf Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 8 May 2020 14:19:43 +0100 Subject: [PATCH 2/7] Add timeout monitor for Bluetooth connections. --- .../bluetooth/AndroidBluetoothPlugin.java | 20 ++--- .../AndroidBluetoothPluginFactory.java | 11 ++- .../AndroidBluetoothTransportConnection.java | 19 +++-- .../bramble/api/io/TimeoutMonitor.java | 8 ++ .../bramble/api/system/Clock.java | 5 ++ .../briarproject/bramble/test/ArrayClock.java | 5 ++ .../bramble/test/SettableClock.java | 5 ++ .../bramble/BrambleCoreModule.java | 2 + .../org/briarproject/bramble/io/IoModule.java | 18 +++++ .../bramble/io/TimeoutInputStream.java | 79 +++++++++++++++++++ .../bramble/io/TimeoutMonitorImpl.java | 71 +++++++++++++++++ .../plugin/bluetooth/BluetoothPlugin.java | 9 ++- .../bramble/system/SystemClock.java | 5 ++ .../bramble/db/JdbcDatabaseTest.java | 5 ++ .../bramble/plugin/DesktopPluginModule.java | 9 ++- .../plugin/bluetooth/JavaBluetoothPlugin.java | 17 ++-- .../bluetooth/JavaBluetoothPluginFactory.java | 13 +-- .../JavaBluetoothTransportConnection.java | 19 +++-- .../briarproject/briar/android/AppModule.java | 10 ++- 19 files changed, 280 insertions(+), 50 deletions(-) create mode 100644 bramble-api/src/main/java/org/briarproject/bramble/api/io/TimeoutMonitor.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/io/IoModule.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java create mode 100644 bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java index 19fd8cb8c..9b121f09d 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPlugin.java @@ -9,6 +9,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; @@ -76,12 +77,12 @@ class AndroidBluetoothPlugin extends BluetoothPlugin { private volatile BluetoothAdapter adapter = null; AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter, - Executor ioExecutor, AndroidExecutor androidExecutor, - Context appContext, SecureRandom secureRandom, Clock clock, - Backoff backoff, PluginCallback callback, int maxLatency, - int maxIdleTime) { - super(connectionLimiter, ioExecutor, secureRandom, backoff, callback, - maxLatency, maxIdleTime); + TimeoutMonitor timeoutMonitor, Executor ioExecutor, + SecureRandom secureRandom, AndroidExecutor androidExecutor, + Context appContext, Clock clock, Backoff backoff, + PluginCallback callback, int maxLatency, int maxIdleTime) { + super(connectionLimiter, timeoutMonitor, ioExecutor, secureRandom, + backoff, callback, maxLatency, maxIdleTime); this.androidExecutor = androidExecutor; this.appContext = appContext; this.clock = clock; @@ -173,9 +174,10 @@ class AndroidBluetoothPlugin extends BluetoothPlugin { return wrapSocket(ss.accept()); } - private DuplexTransportConnection wrapSocket(BluetoothSocket s) { - return new AndroidBluetoothTransportConnection(this, - connectionLimiter, s); + private DuplexTransportConnection wrapSocket(BluetoothSocket s) + throws IOException { + return new AndroidBluetoothTransportConnection(this, connectionLimiter, + timeoutMonitor, s); } @Override diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java index f74967577..bbf2e2744 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothPluginFactory.java @@ -3,6 +3,7 @@ package org.briarproject.bramble.plugin.bluetooth; import android.content.Context; import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.BackoffFactory; @@ -36,18 +37,20 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory { private final SecureRandom secureRandom; private final EventBus eventBus; private final Clock clock; + private final TimeoutMonitor timeoutMonitor; private final BackoffFactory backoffFactory; public AndroidBluetoothPluginFactory(Executor ioExecutor, AndroidExecutor androidExecutor, Context appContext, SecureRandom secureRandom, EventBus eventBus, Clock clock, - BackoffFactory backoffFactory) { + TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) { this.ioExecutor = ioExecutor; this.androidExecutor = androidExecutor; this.appContext = appContext; this.secureRandom = secureRandom; this.eventBus = eventBus; this.clock = clock; + this.timeoutMonitor = timeoutMonitor; this.backoffFactory = backoffFactory; } @@ -68,9 +71,9 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin( - connectionLimiter, ioExecutor, androidExecutor, appContext, - secureRandom, clock, backoff, callback, MAX_LATENCY, - MAX_IDLE_TIME); + connectionLimiter, timeoutMonitor, ioExecutor, secureRandom, + androidExecutor, appContext, clock, backoff, + callback, MAX_LATENCY, MAX_IDLE_TIME); eventBus.addListener(plugin); return plugin; } diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java index 226ad0330..bf6b3d69f 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/bluetooth/AndroidBluetoothTransportConnection.java @@ -2,6 +2,7 @@ package org.briarproject.bramble.plugin.bluetooth; import android.bluetooth.BluetoothSocket; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection; @@ -17,22 +18,26 @@ import static org.briarproject.bramble.util.AndroidUtils.isValidBluetoothAddress class AndroidBluetoothTransportConnection extends AbstractDuplexTransportConnection { - private final BluetoothConnectionLimiter connectionManager; + private final BluetoothConnectionLimiter connectionLimiter; private final BluetoothSocket socket; + private final InputStream in; AndroidBluetoothTransportConnection(Plugin plugin, - BluetoothConnectionLimiter connectionManager, - BluetoothSocket socket) { + BluetoothConnectionLimiter connectionLimiter, + TimeoutMonitor timeoutMonitor, BluetoothSocket socket) + throws IOException { super(plugin); - this.connectionManager = connectionManager; + this.connectionLimiter = connectionLimiter; this.socket = socket; + in = timeoutMonitor.createTimeoutInputStream( + socket.getInputStream(), plugin.getMaxIdleTime() * 2); String address = socket.getRemoteDevice().getAddress(); if (isValidBluetoothAddress(address)) remote.put(PROP_ADDRESS, address); } @Override - protected InputStream getInputStream() throws IOException { - return socket.getInputStream(); + protected InputStream getInputStream() { + return in; } @Override @@ -45,7 +50,7 @@ class AndroidBluetoothTransportConnection try { socket.close(); } finally { - connectionManager.connectionClosed(this); + connectionLimiter.connectionClosed(this); } } } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/io/TimeoutMonitor.java b/bramble-api/src/main/java/org/briarproject/bramble/api/io/TimeoutMonitor.java new file mode 100644 index 000000000..04c1dc3cb --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/io/TimeoutMonitor.java @@ -0,0 +1,8 @@ +package org.briarproject.bramble.api.io; + +import java.io.InputStream; + +public interface TimeoutMonitor { + + InputStream createTimeoutInputStream(InputStream in, long timeoutMs); +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/system/Clock.java b/bramble-api/src/main/java/org/briarproject/bramble/api/system/Clock.java index 2b55c4196..a2b4fef41 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/system/Clock.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/system/Clock.java @@ -11,6 +11,11 @@ public interface Clock { */ long currentTimeMillis(); + /** + * @see System#nanoTime() + */ + long nanoTime(); + /** * @see Thread#sleep(long) */ diff --git a/bramble-api/src/test/java/org/briarproject/bramble/test/ArrayClock.java b/bramble-api/src/test/java/org/briarproject/bramble/test/ArrayClock.java index fc7c40e68..6b108bd82 100644 --- a/bramble-api/src/test/java/org/briarproject/bramble/test/ArrayClock.java +++ b/bramble-api/src/test/java/org/briarproject/bramble/test/ArrayClock.java @@ -16,6 +16,11 @@ public class ArrayClock implements Clock { return times[index++]; } + @Override + public long nanoTime() { + return times[index++] * 1_000_000; + } + @Override public void sleep(long milliseconds) throws InterruptedException { Thread.sleep(milliseconds); diff --git a/bramble-api/src/test/java/org/briarproject/bramble/test/SettableClock.java b/bramble-api/src/test/java/org/briarproject/bramble/test/SettableClock.java index 26f885de8..692465e41 100644 --- a/bramble-api/src/test/java/org/briarproject/bramble/test/SettableClock.java +++ b/bramble-api/src/test/java/org/briarproject/bramble/test/SettableClock.java @@ -17,6 +17,11 @@ public class SettableClock implements Clock { return time.get(); } + @Override + public long nanoTime() { + return time.get() * 1_000_000; + } + @Override public void sleep(long milliseconds) throws InterruptedException { Thread.sleep(milliseconds); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java index 6876c587a..d95b07e0a 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/BrambleCoreModule.java @@ -9,6 +9,7 @@ import org.briarproject.bramble.db.DatabaseExecutorModule; import org.briarproject.bramble.db.DatabaseModule; import org.briarproject.bramble.event.EventModule; import org.briarproject.bramble.identity.IdentityModule; +import org.briarproject.bramble.io.IoModule; import org.briarproject.bramble.keyagreement.KeyAgreementModule; import org.briarproject.bramble.lifecycle.LifecycleModule; import org.briarproject.bramble.plugin.PluginModule; @@ -35,6 +36,7 @@ import dagger.Module; DatabaseExecutorModule.class, EventModule.class, IdentityModule.class, + IoModule.class, KeyAgreementModule.class, LifecycleModule.class, PluginModule.class, diff --git a/bramble-core/src/main/java/org/briarproject/bramble/io/IoModule.java b/bramble-core/src/main/java/org/briarproject/bramble/io/IoModule.java new file mode 100644 index 000000000..f8aed8ae3 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/io/IoModule.java @@ -0,0 +1,18 @@ +package org.briarproject.bramble.io; + +import org.briarproject.bramble.api.io.TimeoutMonitor; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class IoModule { + + @Provides + @Singleton + TimeoutMonitor provideTimeoutMonitor(TimeoutMonitorImpl timeoutMonitor) { + return timeoutMonitor; + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java new file mode 100644 index 000000000..6ff8cf0e8 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java @@ -0,0 +1,79 @@ +package org.briarproject.bramble.io; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.Clock; + +import java.io.IOException; +import java.io.InputStream; + +import javax.annotation.concurrent.GuardedBy; + +@NotNullByDefault +class TimeoutInputStream extends InputStream { + + private final Clock clock; + private final InputStream in; + private final long timeoutNs; + private final CloseListener listener; + private final Object lock = new Object(); + @GuardedBy("lock") + private long readStartedNs = -1; + + TimeoutInputStream(Clock clock, InputStream in, long timeoutNs, + CloseListener listener) { + this.clock = clock; + this.in = in; + this.timeoutNs = timeoutNs; + this.listener = listener; + } + + @Override + public int read() throws IOException { + synchronized (lock) { + readStartedNs = clock.nanoTime(); + } + int input = in.read(); + synchronized (lock) { + readStartedNs = -1; + } + return input; + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + synchronized (lock) { + readStartedNs = clock.nanoTime(); + } + int read = in.read(b, off, len); + synchronized (lock) { + readStartedNs = -1; + } + return read; + } + + @Override + public void close() throws IOException { + try { + in.close(); + } finally { + listener.onClose(this); + } + } + + boolean hasTimedOut() { + synchronized (lock) { + return readStartedNs != -1 && + clock.nanoTime() - readStartedNs > timeoutNs; + } + } + + interface CloseListener { + + void onClose(TimeoutInputStream closed); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java new file mode 100644 index 000000000..0411e9c19 --- /dev/null +++ b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java @@ -0,0 +1,71 @@ +package org.briarproject.bramble.io; + +import org.briarproject.bramble.api.io.TimeoutMonitor; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.system.Scheduler; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.logging.Level.INFO; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logException; + +class TimeoutMonitorImpl implements TimeoutMonitor { + + private static final Logger LOG = + getLogger(TimeoutMonitorImpl.class.getName()); + + private static final long CHECK_INTERVAL_MS = SECONDS.toMillis(10); + + private final Executor ioExecutor; + private final Clock clock; + + private final List streams = + new CopyOnWriteArrayList<>(); + + @Inject + TimeoutMonitorImpl(@IoExecutor Executor ioExecutor, Clock clock, + @Scheduler ScheduledExecutorService scheduler) { + this.ioExecutor = ioExecutor; + this.clock = clock; + scheduler.scheduleWithFixedDelay(this::checkTimeouts, + CHECK_INTERVAL_MS, CHECK_INTERVAL_MS, MILLISECONDS); + } + + @Override + public InputStream createTimeoutInputStream(InputStream in, + long timeoutMs) { + TimeoutInputStream stream = new TimeoutInputStream(clock, in, + timeoutMs * 1_000_000, streams::remove); + streams.add(stream); + return stream; + } + + @Scheduler + private void checkTimeouts() { + ioExecutor.execute(() -> { + LOG.info("Checking input stream timeouts"); + for (TimeoutInputStream stream : streams) { + if (stream.hasTimedOut()) { + LOG.info("Input stream has timed out"); + try { + stream.close(); + } catch (IOException e) { + logException(LOG, INFO, e); + } + } + } + }); + } +} diff --git a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java index 1c574dd6b..cdee708a4 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java @@ -5,6 +5,7 @@ import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection; import org.briarproject.bramble.api.keyagreement.KeyAgreementListener; import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent; @@ -60,6 +61,7 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { getLogger(BluetoothPlugin.class.getName()); final BluetoothConnectionLimiter connectionLimiter; + final TimeoutMonitor timeoutMonitor; private final Executor ioExecutor; private final SecureRandom secureRandom; @@ -105,10 +107,11 @@ abstract class BluetoothPlugin implements DuplexPlugin, EventListener { abstract DuplexTransportConnection discoverAndConnect(String uuid); BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter, - Executor ioExecutor, SecureRandom secureRandom, - Backoff backoff, PluginCallback callback, int maxLatency, - int maxIdleTime) { + TimeoutMonitor timeoutMonitor, Executor ioExecutor, + SecureRandom secureRandom, Backoff backoff, + PluginCallback callback, int maxLatency, int maxIdleTime) { this.connectionLimiter = connectionLimiter; + this.timeoutMonitor = timeoutMonitor; this.ioExecutor = ioExecutor; this.secureRandom = secureRandom; this.backoff = backoff; diff --git a/bramble-core/src/main/java/org/briarproject/bramble/system/SystemClock.java b/bramble-core/src/main/java/org/briarproject/bramble/system/SystemClock.java index b1737ad80..648578a1a 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/system/SystemClock.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/system/SystemClock.java @@ -12,6 +12,11 @@ public class SystemClock implements Clock { return System.currentTimeMillis(); } + @Override + public long nanoTime() { + return System.nanoTime(); + } + @Override public void sleep(long milliseconds) throws InterruptedException { Thread.sleep(milliseconds); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java index ac4231216..ef26a479c 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java @@ -2420,6 +2420,11 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { return time; } + @Override + public long nanoTime() { + return time * 1_000_000; + } + @Override public void sleep(long milliseconds) throws InterruptedException { Thread.sleep(milliseconds); diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java index 5b4cf7014..c00bd99d7 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/DesktopPluginModule.java @@ -1,6 +1,7 @@ package org.briarproject.bramble.plugin; import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.ShutdownManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @@ -31,10 +32,10 @@ public class DesktopPluginModule extends PluginModule { PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor, SecureRandom random, BackoffFactory backoffFactory, ReliabilityLayerFactory reliabilityFactory, - ShutdownManager shutdownManager, EventBus eventBus) { - DuplexPluginFactory bluetooth = - new JavaBluetoothPluginFactory(ioExecutor, random, eventBus, - backoffFactory); + ShutdownManager shutdownManager, EventBus eventBus, + TimeoutMonitor timeoutMonitor) { + DuplexPluginFactory bluetooth = new JavaBluetoothPluginFactory( + ioExecutor, random, eventBus, timeoutMonitor, backoffFactory); DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor, reliabilityFactory); DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor, diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java index 62fad72a8..ff9b7ee79 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPlugin.java @@ -1,5 +1,6 @@ package org.briarproject.bramble.plugin.bluetooth; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; @@ -34,11 +35,11 @@ class JavaBluetoothPlugin extends BluetoothPlugin { private volatile LocalDevice localDevice = null; JavaBluetoothPlugin(BluetoothConnectionLimiter connectionManager, - Executor ioExecutor, SecureRandom secureRandom, - Backoff backoff, PluginCallback callback, int maxLatency, - int maxIdleTime) { - super(connectionManager, ioExecutor, secureRandom, backoff, callback, - maxLatency, maxIdleTime); + TimeoutMonitor timeoutMonitor, Executor ioExecutor, + SecureRandom secureRandom, Backoff backoff, + PluginCallback callback, int maxLatency, int maxIdleTime) { + super(connectionManager, timeoutMonitor, ioExecutor, secureRandom, + backoff, callback, maxLatency, maxIdleTime); } @Override @@ -120,7 +121,9 @@ class JavaBluetoothPlugin extends BluetoothPlugin { return "btspp://" + address + ":" + uuid + ";name=RFCOMM"; } - private DuplexTransportConnection wrapSocket(StreamConnection s) { - return new JavaBluetoothTransportConnection(this, connectionLimiter, s); + private DuplexTransportConnection wrapSocket(StreamConnection s) + throws IOException { + return new JavaBluetoothTransportConnection(this, connectionLimiter, + timeoutMonitor, s); } } diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java index 62e51c6c8..59fc25c86 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothPluginFactory.java @@ -1,6 +1,7 @@ package org.briarproject.bramble.plugin.bluetooth; import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.BackoffFactory; @@ -28,16 +29,18 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory { private final Executor ioExecutor; private final SecureRandom secureRandom; - private final BackoffFactory backoffFactory; private final EventBus eventBus; + private final TimeoutMonitor timeoutMonitor; + private final BackoffFactory backoffFactory; public JavaBluetoothPluginFactory(Executor ioExecutor, SecureRandom secureRandom, EventBus eventBus, - BackoffFactory backoffFactory) { + TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) { this.ioExecutor = ioExecutor; this.secureRandom = secureRandom; - this.backoffFactory = backoffFactory; this.eventBus = eventBus; + this.timeoutMonitor = timeoutMonitor; + this.backoffFactory = backoffFactory; } @Override @@ -57,8 +60,8 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory { Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, MAX_POLLING_INTERVAL, BACKOFF_BASE); JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter, - ioExecutor, secureRandom, backoff, callback, MAX_LATENCY, - MAX_IDLE_TIME); + timeoutMonitor, ioExecutor, secureRandom, backoff, callback, + MAX_LATENCY, MAX_IDLE_TIME); eventBus.addListener(plugin); return plugin; } diff --git a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java index 097c3c191..319d9af76 100644 --- a/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java +++ b/bramble-java/src/main/java/org/briarproject/bramble/plugin/bluetooth/JavaBluetoothTransportConnection.java @@ -1,5 +1,6 @@ package org.briarproject.bramble.plugin.bluetooth; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.Plugin; import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection; @@ -14,20 +15,24 @@ import javax.microedition.io.StreamConnection; class JavaBluetoothTransportConnection extends AbstractDuplexTransportConnection { - private final BluetoothConnectionLimiter connectionManager; + private final BluetoothConnectionLimiter connectionLimiter; private final StreamConnection stream; + private final InputStream in; JavaBluetoothTransportConnection(Plugin plugin, - BluetoothConnectionLimiter connectionManager, - StreamConnection stream) { + BluetoothConnectionLimiter connectionLimiter, + TimeoutMonitor timeoutMonitor, + StreamConnection stream) throws IOException { super(plugin); + this.connectionLimiter = connectionLimiter; this.stream = stream; - this.connectionManager = connectionManager; + in = timeoutMonitor.createTimeoutInputStream( + stream.openInputStream(), plugin.getMaxIdleTime() * 2); } @Override - protected InputStream getInputStream() throws IOException { - return stream.openInputStream(); + protected InputStream getInputStream() { + return in; } @Override @@ -40,7 +45,7 @@ class JavaBluetoothTransportConnection try { stream.close(); } finally { - connectionManager.connectionClosed(this); + connectionLimiter.connectionClosed(this); } } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index 07b37f2a8..b9e074897 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -14,6 +14,7 @@ import org.briarproject.bramble.api.crypto.KeyStrengthener; import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.io.TimeoutMonitor; import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.network.NetworkManager; @@ -122,11 +123,12 @@ public class AppModule { LocationUtils locationUtils, EventBus eventBus, ResourceProvider resourceProvider, CircumventionProvider circumventionProvider, - BatteryManager batteryManager, Clock clock) { + BatteryManager batteryManager, Clock clock, + TimeoutMonitor timeoutMonitor) { Context appContext = app.getApplicationContext(); - DuplexPluginFactory bluetooth = - new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor, - appContext, random, eventBus, clock, backoffFactory); + DuplexPluginFactory bluetooth = new AndroidBluetoothPluginFactory( + ioExecutor, androidExecutor, appContext, random, eventBus, + clock, timeoutMonitor, backoffFactory); DuplexPluginFactory tor = new AndroidTorPluginFactory(ioExecutor, scheduler, appContext, networkManager, locationUtils, eventBus, torSocketFactory, backoffFactory, resourceProvider, From d3fd30960969807b3c08aec80a0d298a009924d1 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 8 May 2020 14:35:50 +0100 Subject: [PATCH 3/7] Only check timeouts when we have some streams to monitor. --- .../bramble/io/TimeoutMonitorImpl.java | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java index 0411e9c19..25ea97869 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java @@ -7,12 +7,14 @@ import org.briarproject.bramble.api.system.Scheduler; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; +import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.logging.Logger; +import javax.annotation.concurrent.GuardedBy; import javax.inject.Inject; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -28,35 +30,58 @@ class TimeoutMonitorImpl implements TimeoutMonitor { private static final long CHECK_INTERVAL_MS = SECONDS.toMillis(10); + private final ScheduledExecutorService scheduler; private final Executor ioExecutor; private final Clock clock; + private final Object lock = new Object(); + @GuardedBy("lock") + private final List streams = new ArrayList<>(); - private final List streams = - new CopyOnWriteArrayList<>(); + @GuardedBy("lock") + private Future task = null; @Inject - TimeoutMonitorImpl(@IoExecutor Executor ioExecutor, Clock clock, - @Scheduler ScheduledExecutorService scheduler) { + TimeoutMonitorImpl(@Scheduler ScheduledExecutorService scheduler, + @IoExecutor Executor ioExecutor, Clock clock) { + this.scheduler = scheduler; this.ioExecutor = ioExecutor; this.clock = clock; - scheduler.scheduleWithFixedDelay(this::checkTimeouts, - CHECK_INTERVAL_MS, CHECK_INTERVAL_MS, MILLISECONDS); } @Override public InputStream createTimeoutInputStream(InputStream in, long timeoutMs) { TimeoutInputStream stream = new TimeoutInputStream(clock, in, - timeoutMs * 1_000_000, streams::remove); - streams.add(stream); + timeoutMs * 1_000_000, this::removeStream); + synchronized (lock) { + if (streams.isEmpty()) { + task = scheduler.scheduleWithFixedDelay(this::checkTimeouts, + CHECK_INTERVAL_MS, CHECK_INTERVAL_MS, MILLISECONDS); + } + streams.add(stream); + } return stream; } + private void removeStream(TimeoutInputStream stream) { + Future toCancel = null; + synchronized (lock) { + if (streams.remove(stream) && streams.isEmpty()) { + toCancel = task; + task = null; + } + } + if (toCancel != null) toCancel.cancel(false); + } + @Scheduler private void checkTimeouts() { ioExecutor.execute(() -> { - LOG.info("Checking input stream timeouts"); - for (TimeoutInputStream stream : streams) { + List snapshot; + synchronized (lock) { + snapshot = new ArrayList<>(streams); + } + for (TimeoutInputStream stream : snapshot) { if (stream.hasTimedOut()) { LOG.info("Input stream has timed out"); try { From 0281eec0da5ab93609bb5e0d01ed125c2e03a173 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 8 May 2020 15:06:27 +0100 Subject: [PATCH 4/7] Add unit test for TimeoutInputStream. --- .../bramble/io/TimeoutInputStreamTest.java | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/io/TimeoutInputStreamTest.java diff --git a/bramble-core/src/test/java/org/briarproject/bramble/io/TimeoutInputStreamTest.java b/bramble-core/src/test/java/org/briarproject/bramble/io/TimeoutInputStreamTest.java new file mode 100644 index 000000000..6a5c75975 --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/io/TimeoutInputStreamTest.java @@ -0,0 +1,143 @@ +package org.briarproject.bramble.io; + +import org.briarproject.bramble.test.BrambleTestCase; +import org.briarproject.bramble.test.SettableClock; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class TimeoutInputStreamTest extends BrambleTestCase { + + private static final long TIMEOUT_MS = MINUTES.toMillis(1); + + private final long now = System.currentTimeMillis(); + + private AtomicLong time; + private UnresponsiveInputStream in; + private AtomicBoolean listenerCalled; + private TimeoutInputStream stream; + private CountDownLatch readReturned; + + @Before + public void setUp() { + time = new AtomicLong(now); + in = new UnresponsiveInputStream(); + listenerCalled = new AtomicBoolean(false); + stream = new TimeoutInputStream(new SettableClock(time), in, + TIMEOUT_MS * 1_000_000, stream -> listenerCalled.set(true)); + readReturned = new CountDownLatch(1); + } + + @Test + public void testTimeoutIsReportedIfReadDoesNotReturn() throws Exception { + startReading(); + try { + // The stream should not report a timeout + assertFalse(stream.hasTimedOut()); + + // Time passes + time.set(now + TIMEOUT_MS); + + // The stream still shouldn't report a timeout + assertFalse(stream.hasTimedOut()); + + // Time passes + time.set(now + TIMEOUT_MS + 1); + + // The stream should report a timeout + assertTrue(stream.hasTimedOut()); + + // The listener should not have been called yet + assertFalse(listenerCalled.get()); + + // Close the stream + stream.close(); + + // The listener should have been called + assertTrue(listenerCalled.get()); + } finally { + // Allow the read to return + in.readFinished.countDown(); + } + } + + @Test + public void testTimeoutIsNotReportedIfReadReturns() throws Exception { + startReading(); + try { + // The stream should not report a timeout + assertFalse(stream.hasTimedOut()); + + // Time passes + time.set(now + TIMEOUT_MS); + + // The stream still shouldn't report a timeout + assertFalse(stream.hasTimedOut()); + + // Allow the read to finish and wait for it to return + in.readFinished.countDown(); + readReturned.await(10, SECONDS); + + // Time passes + time.set(now + TIMEOUT_MS + 1); + + // The stream should not report a timeout as the read has returned + assertFalse(stream.hasTimedOut()); + + // The listener should not have been called yet + assertFalse(listenerCalled.get()); + + // Close the stream + stream.close(); + + // The listener should have been called + assertTrue(listenerCalled.get()); + } finally { + // Allow the read to return in case an assertion was thrown + in.readFinished.countDown(); + } + } + + private void startReading() throws Exception { + // Start a background thread to read from the unresponsive stream + new Thread(() -> { + try { + assertEquals(123, stream.read()); + readReturned.countDown(); + } catch (IOException e) { + fail(); + } + }).start(); + // Wait for the background thread to start reading + assertTrue(in.readStarted.await(10, SECONDS)); + } + + private class UnresponsiveInputStream extends InputStream { + + private final CountDownLatch readStarted = new CountDownLatch(1); + private final CountDownLatch readFinished = new CountDownLatch(1); + + @Override + public int read() throws IOException { + readStarted.countDown(); + try { + readFinished.await(); + return 123; + } catch (InterruptedException e) { + throw new IOException(e); + } + } + } +} From 21f3a9f3c7f289cdd5941cec76e6a3b9fde6e685 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 8 May 2020 15:41:32 +0100 Subject: [PATCH 5/7] Add javadoc. --- .../org/briarproject/bramble/api/io/TimeoutMonitor.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/io/TimeoutMonitor.java b/bramble-api/src/main/java/org/briarproject/bramble/api/io/TimeoutMonitor.java index 04c1dc3cb..92d440946 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/io/TimeoutMonitor.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/io/TimeoutMonitor.java @@ -4,5 +4,12 @@ import java.io.InputStream; public interface TimeoutMonitor { + /** + * Returns an {@link InputStream} that wraps the given stream and allows + * read timeouts to be detected. + * + * @param timeoutMs The read timeout in milliseconds. Timeouts will be + * detected eventually but are not guaranteed to be detected immediately. + */ InputStream createTimeoutInputStream(InputStream in, long timeoutMs); } From c4273d22edbf6774364d1b8038145fc2047b303f Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 8 May 2020 16:10:32 +0100 Subject: [PATCH 6/7] Delegate all other methods to wrapped InputStream. --- .../bramble/io/TimeoutInputStream.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java index 6ff8cf0e8..4c5a2ce21 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java @@ -65,6 +65,31 @@ class TimeoutInputStream extends InputStream { } } + @Override + public int available() throws IOException { + return in.available(); + } + + @Override + public void mark(int readlimit) { + in.mark(readlimit); + } + + @Override + public boolean markSupported() { + return in.markSupported(); + } + + @Override + public void reset() throws IOException { + in.reset(); + } + + @Override + public long skip(long n) throws IOException { + return in.skip(n); + } + boolean hasTimedOut() { synchronized (lock) { return readStartedNs != -1 && From c80d3196af20f453fd7c32b46640b8bdb6b44bf8 Mon Sep 17 00:00:00 2001 From: akwizgran Date: Mon, 11 May 2020 15:42:23 +0100 Subject: [PATCH 7/7] Use milliseconds for timing. --- .../bramble/api/system/Clock.java | 5 ----- .../briarproject/bramble/test/ArrayClock.java | 5 ----- .../bramble/test/SettableClock.java | 5 ----- .../bramble/io/TimeoutInputStream.java | 20 +++++++++---------- .../bramble/io/TimeoutMonitorImpl.java | 2 +- .../bramble/system/SystemClock.java | 5 ----- .../bramble/db/JdbcDatabaseTest.java | 5 ----- .../bramble/io/TimeoutInputStreamTest.java | 2 +- 8 files changed, 12 insertions(+), 37 deletions(-) diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/system/Clock.java b/bramble-api/src/main/java/org/briarproject/bramble/api/system/Clock.java index a2b4fef41..2b55c4196 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/system/Clock.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/system/Clock.java @@ -11,11 +11,6 @@ public interface Clock { */ long currentTimeMillis(); - /** - * @see System#nanoTime() - */ - long nanoTime(); - /** * @see Thread#sleep(long) */ diff --git a/bramble-api/src/test/java/org/briarproject/bramble/test/ArrayClock.java b/bramble-api/src/test/java/org/briarproject/bramble/test/ArrayClock.java index 6b108bd82..fc7c40e68 100644 --- a/bramble-api/src/test/java/org/briarproject/bramble/test/ArrayClock.java +++ b/bramble-api/src/test/java/org/briarproject/bramble/test/ArrayClock.java @@ -16,11 +16,6 @@ public class ArrayClock implements Clock { return times[index++]; } - @Override - public long nanoTime() { - return times[index++] * 1_000_000; - } - @Override public void sleep(long milliseconds) throws InterruptedException { Thread.sleep(milliseconds); diff --git a/bramble-api/src/test/java/org/briarproject/bramble/test/SettableClock.java b/bramble-api/src/test/java/org/briarproject/bramble/test/SettableClock.java index 692465e41..26f885de8 100644 --- a/bramble-api/src/test/java/org/briarproject/bramble/test/SettableClock.java +++ b/bramble-api/src/test/java/org/briarproject/bramble/test/SettableClock.java @@ -17,11 +17,6 @@ public class SettableClock implements Clock { return time.get(); } - @Override - public long nanoTime() { - return time.get() * 1_000_000; - } - @Override public void sleep(long milliseconds) throws InterruptedException { Thread.sleep(milliseconds); diff --git a/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java index 4c5a2ce21..1a433254f 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutInputStream.java @@ -13,28 +13,28 @@ class TimeoutInputStream extends InputStream { private final Clock clock; private final InputStream in; - private final long timeoutNs; + private final long timeoutMs; private final CloseListener listener; private final Object lock = new Object(); @GuardedBy("lock") - private long readStartedNs = -1; + private long readStartedMs = -1; - TimeoutInputStream(Clock clock, InputStream in, long timeoutNs, + TimeoutInputStream(Clock clock, InputStream in, long timeoutMs, CloseListener listener) { this.clock = clock; this.in = in; - this.timeoutNs = timeoutNs; + this.timeoutMs = timeoutMs; this.listener = listener; } @Override public int read() throws IOException { synchronized (lock) { - readStartedNs = clock.nanoTime(); + readStartedMs = clock.currentTimeMillis(); } int input = in.read(); synchronized (lock) { - readStartedNs = -1; + readStartedMs = -1; } return input; } @@ -47,11 +47,11 @@ class TimeoutInputStream extends InputStream { @Override public int read(byte[] b, int off, int len) throws IOException { synchronized (lock) { - readStartedNs = clock.nanoTime(); + readStartedMs = clock.currentTimeMillis(); } int read = in.read(b, off, len); synchronized (lock) { - readStartedNs = -1; + readStartedMs = -1; } return read; } @@ -92,8 +92,8 @@ class TimeoutInputStream extends InputStream { boolean hasTimedOut() { synchronized (lock) { - return readStartedNs != -1 && - clock.nanoTime() - readStartedNs > timeoutNs; + return readStartedMs != -1 && + clock.currentTimeMillis() - readStartedMs > timeoutMs; } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java index 25ea97869..eb68cb3a4 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/io/TimeoutMonitorImpl.java @@ -52,7 +52,7 @@ class TimeoutMonitorImpl implements TimeoutMonitor { public InputStream createTimeoutInputStream(InputStream in, long timeoutMs) { TimeoutInputStream stream = new TimeoutInputStream(clock, in, - timeoutMs * 1_000_000, this::removeStream); + timeoutMs, this::removeStream); synchronized (lock) { if (streams.isEmpty()) { task = scheduler.scheduleWithFixedDelay(this::checkTimeouts, diff --git a/bramble-core/src/main/java/org/briarproject/bramble/system/SystemClock.java b/bramble-core/src/main/java/org/briarproject/bramble/system/SystemClock.java index 648578a1a..b1737ad80 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/system/SystemClock.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/system/SystemClock.java @@ -12,11 +12,6 @@ public class SystemClock implements Clock { return System.currentTimeMillis(); } - @Override - public long nanoTime() { - return System.nanoTime(); - } - @Override public void sleep(long milliseconds) throws InterruptedException { Thread.sleep(milliseconds); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java index ef26a479c..ac4231216 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java @@ -2420,11 +2420,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase { return time; } - @Override - public long nanoTime() { - return time * 1_000_000; - } - @Override public void sleep(long milliseconds) throws InterruptedException { Thread.sleep(milliseconds); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/io/TimeoutInputStreamTest.java b/bramble-core/src/test/java/org/briarproject/bramble/io/TimeoutInputStreamTest.java index 6a5c75975..3760210d2 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/io/TimeoutInputStreamTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/io/TimeoutInputStreamTest.java @@ -36,7 +36,7 @@ public class TimeoutInputStreamTest extends BrambleTestCase { in = new UnresponsiveInputStream(); listenerCalled = new AtomicBoolean(false); stream = new TimeoutInputStream(new SettableClock(time), in, - TIMEOUT_MS * 1_000_000, stream -> listenerCalled.set(true)); + TIMEOUT_MS, stream -> listenerCalled.set(true)); readReturned = new CountDownLatch(1); }