Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Close unidentified websocket when the app is in background #13337

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -172,6 +172,13 @@ class IncomingMessageObserver(private val context: Application) {
}
}

private fun shouldKeepAliveUnidentified(): Boolean {
val timeIdle = lock.withLock {
if (appVisible) 0 else System.currentTimeMillis() - lastInteractionTime
}
return timeIdle <= websocketReadTimeout
}

private fun isConnectionNecessary(): Boolean {
val timeIdle: Long
val keepAliveEntries: Set<Pair<String, Long>>
Expand Down Expand Up @@ -379,7 +386,7 @@ class IncomingMessageObserver(private val context: Application) {
decryptionDrained = false
}

signalWebSocket.connect()
signalWebSocket.connect(shouldKeepAliveUnidentified())
try {
while (isConnectionNecessary()) {
try {
Expand Down Expand Up @@ -430,11 +437,12 @@ class IncomingMessageObserver(private val context: Application) {
}
} catch (e: WebSocketUnavailableException) {
Log.i(TAG, "Pipe unexpectedly unavailable, connecting")
signalWebSocket.connect()
signalWebSocket.connect(shouldKeepAliveUnidentified())
} catch (e: TimeoutException) {
Log.w(TAG, "Application level read timeout...")
attempts = 0
}
signalWebSocket.setKeepAliveUnidentified(shouldKeepAliveUnidentified())
}

if (!appVisible) {
Expand Down
Expand Up @@ -110,12 +110,14 @@ private void onStateChange(WebSocketConnectionState connectionState, HealthState
}

@Override
public void onKeepAliveResponse(long sentTimestamp, boolean isIdentifiedWebSocket) {
public void onKeepAliveResponse(long sentTimestamp, boolean isIdentifiedWebSocket, boolean keepMonitoring) {
final long keepAliveTime = System.currentTimeMillis();
executor.execute(() -> {
if (isIdentifiedWebSocket) {
identified.needsKeepAlive = keepMonitoring;
identified.lastKeepAliveReceived = keepAliveTime;
} else {
unidentified.needsKeepAlive = keepMonitoring;
unidentified.lastKeepAliveReceived = keepAliveTime;
}
});
Expand Down Expand Up @@ -143,6 +145,13 @@ private static class HealthState {

private volatile boolean needsKeepAlive;
private volatile long lastKeepAliveReceived;

/**
* Checks if a keep-alive response has not been received yet based on the provided send time.
*/
private boolean isKeepAlivePending(long sendTime) {
return needsKeepAlive && lastKeepAliveReceived < sendTime;
}
}

/**
Expand Down Expand Up @@ -172,9 +181,8 @@ public void run() {
sleepUntil(responseRequiredTime);

if (shouldKeepRunning && isKeepAliveNecessary()) {
if (identified.lastKeepAliveReceived < keepAliveSendTime || unidentified.lastKeepAliveReceived < keepAliveSendTime) {
if (identified.isKeepAlivePending(keepAliveSendTime)) {
Log.w(TAG, "Missed keep alives, identified last: " + identified.lastKeepAliveReceived +
" unidentified last: " + unidentified.lastKeepAliveReceived +
" needed by: " + responseRequiredTime);
signalWebSocket.forceNewWebSockets();
}
Expand Down
Expand Up @@ -47,6 +47,7 @@ public final class SignalWebSocket {
private CompositeDisposable unidentifiedWebSocketStateDisposable;

private boolean canConnect;
private boolean keepAliveUnidentified;

public SignalWebSocket(WebSocketFactory webSocketFactory) {
this.webSocketFactory = webSocketFactory;
Expand Down Expand Up @@ -75,16 +76,26 @@ public Observable<WebSocketConnectionState> getUnidentifiedWebSocketState() {
/**
* Indicate that WebSocketConnections can now be made and attempt to connect both of them.
*/
public synchronized void connect() {
public synchronized void connect(boolean includeUnidentified) {
canConnect = true;
keepAliveUnidentified = includeUnidentified;
try {
getWebSocket();
getUnidentifiedWebSocket();
if (includeUnidentified) {
getUnidentifiedWebSocket();
}
} catch (WebSocketUnavailableException e) {
throw new AssertionError(e);
}
}

public synchronized void setKeepAliveUnidentified(boolean keepAlive) {
if (unidentifiedWebSocket != null) {
unidentifiedWebSocket.setKeepAlive(keepAlive);
}
keepAliveUnidentified = keepAlive;
}

/**
* Indicate that WebSocketConnections can no longer be made and disconnect both of them.
*/
Expand Down Expand Up @@ -183,7 +194,11 @@ public synchronized void sendKeepAlive() throws IOException {
if (canConnect) {
try {
getWebSocket().sendKeepAlive();
getUnidentifiedWebSocket().sendKeepAlive();
// Avoid creating the unidentified socket only for sending keep-alives,
// unless it's already open.
if (keepAliveUnidentified && unidentifiedWebSocket != null && !unidentifiedWebSocket.isDead()) {
getUnidentifiedWebSocket().sendKeepAlive();
}
} catch (WebSocketUnavailableException e) {
throw new AssertionError(e);
}
Expand Down
Expand Up @@ -4,7 +4,7 @@
* Callbacks to provide WebSocket health information to a monitor.
*/
public interface HealthMonitor {
void onKeepAliveResponse(long sentTimestamp, boolean isIdentifiedWebSocket);
void onKeepAliveResponse(long sentTimestamp, boolean isIdentifiedWebSocket, boolean keepMonitoring);

void onMessageError(int status, boolean isIdentifiedWebSocket);
}
Expand Up @@ -76,6 +76,8 @@ public class WebSocketConnection extends WebSocketListener {

private WebSocket client;

private boolean keepAlive;

public WebSocketConnection(String name,
SignalServiceConfiguration serviceConfiguration,
Optional<CredentialsProvider> credentialsProvider,
Expand All @@ -101,6 +103,7 @@ public WebSocketConnection(String name,
this.dns = serviceConfiguration.getDns();
this.signalProxy = serviceConfiguration.getSignalProxy();
this.healthMonitor = healthMonitor;
this.keepAlive = true;
this.webSocketState = BehaviorSubject.createDefault(WebSocketConnectionState.DISCONNECTED);
this.allowStories = allowStories;
this.serviceUrls = serviceConfiguration.getSignalServiceUrls();
Expand All @@ -112,6 +115,10 @@ public String getName() {
return name;
}

public void setKeepAlive(boolean keepAlive) {
this.keepAlive = keepAlive;
}

private Pair<SignalServiceUrl, String> getConnectionInfo() {
SignalServiceUrl serviceUrl = serviceUrls[random.nextInt(serviceUrls.length)];
String uri = serviceUrl.getUrl().replace("https://", "wss://").replace("http://", "ws://");
Expand Down Expand Up @@ -266,7 +273,7 @@ public synchronized void sendResponse(WebSocketResponseMessage response) throws
}

public synchronized void sendKeepAlive() throws IOException {
if (client != null) {
if (client != null && keepAlive) {
log( "Sending keep alive...");
long id = System.currentTimeMillis();
byte[] message = new WebSocketMessage.Builder()
Expand Down Expand Up @@ -311,7 +318,7 @@ public synchronized void onMessage(WebSocket webSocket, ByteString payload) {
healthMonitor.onMessageError(message.response.status, credentialsProvider.isPresent());
}
} else if (keepAlives.remove(message.response.id)) {
healthMonitor.onKeepAliveResponse(message.response.id, credentialsProvider.isPresent());
healthMonitor.onKeepAliveResponse(message.response.id, credentialsProvider.isPresent(), keepAlive);
}
}

Expand All @@ -333,7 +340,9 @@ public synchronized void onClosed(WebSocket webSocket, int code, String reason)

@Override
public synchronized void onFailure(WebSocket webSocket, Throwable t, Response response) {
warn("onFailure()", t);
if (keepAlive || response != null) {
warn("onFailure()", t);
}

if (response != null && (response.code() == 401 || response.code() == 403)) {
webSocketState.onNext(WebSocketConnectionState.AUTHENTICATION_FAILED);
Expand Down