Commit 739efd88 authored by Leonardo Aramaki's avatar Leonardo Aramaki

Keep always a single thread for any number of signed-in servers

parent c6132148
...@@ -21,6 +21,8 @@ import okhttp3.OkHttpClient; ...@@ -21,6 +21,8 @@ import okhttp3.OkHttpClient;
public class DDPClient { public class DDPClient {
// reference: https://github.com/eddflrs/meteor-ddp/blob/master/meteor-ddp.js // reference: https://github.com/eddflrs/meteor-ddp/blob/master/meteor-ddp.js
public static final int REASON_CLOSED_BY_USER = 1000;
public static final int REASON_NETWORK_ERROR = 1001;
private static volatile DDPClient singleton; private static volatile DDPClient singleton;
private static volatile OkHttpClient client; private static volatile OkHttpClient client;
...@@ -49,10 +51,7 @@ public class DDPClient { ...@@ -49,10 +51,7 @@ public class DDPClient {
} }
public Task<DDPClientCallback.Connect> connect(String url, String session) { public Task<DDPClientCallback.Connect> connect(String url, String session) {
String oldHostname = hostname.getAndSet(url); hostname.set(url);
if (oldHostname != null && !oldHostname.equalsIgnoreCase(url)) {
close();
}
TaskCompletionSource<DDPClientCallback.Connect> task = new TaskCompletionSource<>(); TaskCompletionSource<DDPClientCallback.Connect> task = new TaskCompletionSource<>();
impl.connect(task, url, session); impl.connect(task, url, session);
return task.getTask(); return task.getTask();
...@@ -85,7 +84,7 @@ public class DDPClient { ...@@ -85,7 +84,7 @@ public class DDPClient {
} }
public void close() { public void close() {
impl.close(1000, "closed by DDPClient#close()"); impl.close(REASON_CLOSED_BY_USER, "closed by DDPClient#close()");
} }
/** /**
......
...@@ -18,6 +18,7 @@ import chat.rocket.android.log.RCLog; ...@@ -18,6 +18,7 @@ import chat.rocket.android.log.RCLog;
import chat.rocket.android.service.ConnectivityManagerApi; import chat.rocket.android.service.ConnectivityManagerApi;
import chat.rocket.android.service.ServerConnectivity; import chat.rocket.android.service.ServerConnectivity;
import chat.rocket.android.shared.BasePresenter; import chat.rocket.android.shared.BasePresenter;
import chat.rocket.android_ddp.DDPClient;
import chat.rocket.core.PublicSettingsConstants; import chat.rocket.core.PublicSettingsConstants;
import chat.rocket.core.interactors.CanCreateRoomInteractor; import chat.rocket.core.interactors.CanCreateRoomInteractor;
import chat.rocket.core.interactors.RoomInteractor; import chat.rocket.core.interactors.RoomInteractor;
...@@ -227,7 +228,9 @@ public class MainPresenter extends BasePresenter<MainContract.View> ...@@ -227,7 +228,9 @@ public class MainPresenter extends BasePresenter<MainContract.View>
view.showConnectionOk(); view.showConnectionOk();
view.refreshRoom(); view.refreshRoom();
} else if (connectivity.state == ServerConnectivity.STATE_DISCONNECTED) { } else if (connectivity.state == ServerConnectivity.STATE_DISCONNECTED) {
if (connectivity.code == DDPClient.REASON_NETWORK_ERROR) {
view.showConnectionError(); view.showConnectionError();
}
} else { } else {
view.showConnecting(); view.showConnecting();
} }
......
...@@ -16,6 +16,7 @@ import java.util.concurrent.TimeUnit; ...@@ -16,6 +16,7 @@ import java.util.concurrent.TimeUnit;
import chat.rocket.android.RocketChatCache; import chat.rocket.android.RocketChatCache;
import chat.rocket.android.helper.RxHelper; import chat.rocket.android.helper.RxHelper;
import chat.rocket.android.log.RCLog; import chat.rocket.android.log.RCLog;
import chat.rocket.android_ddp.DDPClient;
import chat.rocket.core.models.ServerInfo; import chat.rocket.core.models.ServerInfo;
import chat.rocket.persistence.realm.models.RealmBasedServerInfo; import chat.rocket.persistence.realm.models.RealmBasedServerInfo;
import hugo.weaving.DebugLog; import hugo.weaving.DebugLog;
...@@ -79,7 +80,7 @@ import rx.subjects.PublishSubject; ...@@ -79,7 +80,7 @@ import rx.subjects.PublishSubject;
.subscribe(_val -> { .subscribe(_val -> {
}, error -> { }, error -> {
RCLog.e(error); RCLog.e(error);
notifyConnectionLost(hostname, REASON_NETWORK_ERROR); notifyConnectionLost(hostname, DDPClient.REASON_NETWORK_ERROR);
}); });
} }
...@@ -138,10 +139,10 @@ import rx.subjects.PublishSubject; ...@@ -138,10 +139,10 @@ import rx.subjects.PublishSubject;
@DebugLog @DebugLog
@Override @Override
public void notifyConnectionLost(String hostname, int reason) { public void notifyConnectionLost(String hostname, int code) {
serverConnectivityList.put(hostname, ServerConnectivity.STATE_DISCONNECTED); serverConnectivityList.put(hostname, ServerConnectivity.STATE_DISCONNECTED);
connectivitySubject.onNext( connectivitySubject.onNext(
new ServerConnectivity(hostname, ServerConnectivity.STATE_DISCONNECTED)); new ServerConnectivity(hostname, ServerConnectivity.STATE_DISCONNECTED, code));
} }
@DebugLog @DebugLog
...@@ -197,7 +198,7 @@ import rx.subjects.PublishSubject; ...@@ -197,7 +198,7 @@ import rx.subjects.PublishSubject;
if (connectivity == ServerConnectivity.STATE_CONNECTING) { if (connectivity == ServerConnectivity.STATE_CONNECTING) {
return waitForConnected(hostname) return waitForConnected(hostname)
.doOnError(err -> notifyConnectionLost(hostname, REASON_NETWORK_ERROR)) .doOnError(err -> notifyConnectionLost(hostname, DDPClient.REASON_NETWORK_ERROR))
.flatMap(_val -> disconnectFromServerIfNeeded(hostname)); .flatMap(_val -> disconnectFromServerIfNeeded(hostname));
} }
......
...@@ -8,11 +8,11 @@ import android.os.Binder; ...@@ -8,11 +8,11 @@ import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import chat.rocket.android.helper.Logger; import chat.rocket.android.helper.Logger;
import chat.rocket.android.log.RCLog;
import chat.rocket.persistence.realm.RealmStore; import chat.rocket.persistence.realm.RealmStore;
import hugo.weaving.DebugLog; import hugo.weaving.DebugLog;
import rx.Observable; import rx.Observable;
...@@ -24,8 +24,8 @@ import rx.Single; ...@@ -24,8 +24,8 @@ import rx.Single;
public class RocketChatService extends Service implements ConnectivityServiceInterface { public class RocketChatService extends Service implements ConnectivityServiceInterface {
private ConnectivityManagerInternal connectivityManager; private ConnectivityManagerInternal connectivityManager;
private static volatile ConcurrentHashMap<String, RocketChatWebSocketThread> webSocketThreads;
private static volatile Semaphore webSocketThreadLock = new Semaphore(1); private static volatile Semaphore webSocketThreadLock = new Semaphore(1);
private static volatile RocketChatWebSocketThread currentWebSocketThread;
public class LocalBinder extends Binder { public class LocalBinder extends Binder {
ConnectivityServiceInterface getServiceInterface() { ConnectivityServiceInterface getServiceInterface() {
...@@ -57,7 +57,6 @@ public class RocketChatService extends Service implements ConnectivityServiceInt ...@@ -57,7 +57,6 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
super.onCreate(); super.onCreate();
connectivityManager = ConnectivityManager.getInstanceForInternal(getApplicationContext()); connectivityManager = ConnectivityManager.getInstanceForInternal(getApplicationContext());
connectivityManager.resetConnectivityStateList(); connectivityManager.resetConnectivityStateList();
webSocketThreads = new ConcurrentHashMap<>();
} }
@DebugLog @DebugLog
...@@ -72,7 +71,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt ...@@ -72,7 +71,7 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
return getOrCreateWebSocketThread(hostname) return getOrCreateWebSocketThread(hostname)
.doOnError(err -> { .doOnError(err -> {
err.printStackTrace(); err.printStackTrace();
webSocketThreads.remove(hostname); currentWebSocketThread = null;
// connectivityManager.notifyConnectionLost(hostname, ConnectivityManagerInternal.REASON_NETWORK_ERROR); // connectivityManager.notifyConnectionLost(hostname, ConnectivityManagerInternal.REASON_NETWORK_ERROR);
}) })
.flatMap(webSocketThreads -> webSocketThreads.keepAlive()); .flatMap(webSocketThreads -> webSocketThreads.keepAlive());
...@@ -81,17 +80,15 @@ public class RocketChatService extends Service implements ConnectivityServiceInt ...@@ -81,17 +80,15 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
@Override @Override
public Single<Boolean> disconnectFromServer(String hostname) { //called via binder. public Single<Boolean> disconnectFromServer(String hostname) { //called via binder.
return Single.defer(() -> { return Single.defer(() -> {
if (!webSocketThreads.containsKey(hostname)) { if (!threadCreatedForHostname(hostname)) {
return Single.just(true); return Single.just(true);
} }
RocketChatWebSocketThread thread = webSocketThreads.get(hostname); if (currentWebSocketThread != null) {
if (thread != null) { return currentWebSocketThread.terminate()
return thread.terminate()
// after disconnection from server // after disconnection from server
.doAfterTerminate(() -> { .doAfterTerminate(() -> {
// remove RCWebSocket key from HashMap currentWebSocketThread = null;
webSocketThreads.remove(hostname);
// remove RealmConfiguration key from HashMap // remove RealmConfiguration key from HashMap
RealmStore.sStore.remove(hostname); RealmStore.sStore.remove(hostname);
}); });
...@@ -108,24 +105,52 @@ public class RocketChatService extends Service implements ConnectivityServiceInt ...@@ -108,24 +105,52 @@ public class RocketChatService extends Service implements ConnectivityServiceInt
webSocketThreadLock.acquire(); webSocketThreadLock.acquire();
int connectivityState = ConnectivityManager.getInstance(getApplicationContext()).getConnectivityState(hostname); int connectivityState = ConnectivityManager.getInstance(getApplicationContext()).getConnectivityState(hostname);
boolean isConnected = connectivityState == ServerConnectivity.STATE_CONNECTED; boolean isConnected = connectivityState == ServerConnectivity.STATE_CONNECTED;
if (webSocketThreads.containsKey(hostname) && isConnected) { if (currentWebSocketThread != null && threadCreatedForHostname(hostname)) {
RocketChatWebSocketThread thread = webSocketThreads.get(hostname);
webSocketThreadLock.release(); webSocketThreadLock.release();
return Single.just(thread); return Single.just(currentWebSocketThread);
} }
connectivityManager.notifyConnecting(hostname); connectivityManager.notifyConnecting(hostname);
if (currentWebSocketThread != null) {
return currentWebSocketThread.terminate()
.doOnError(RCLog::e)
.flatMap(terminated ->
RocketChatWebSocketThread.getStarted(getApplicationContext(), hostname)
.doOnSuccess(thread -> {
currentWebSocketThread = thread;
webSocketThreadLock.release();
})
.doOnError(throwable -> {
currentWebSocketThread = null;
RCLog.e(throwable);
Logger.report(throwable);
webSocketThreadLock.release();
})
);
}
return RocketChatWebSocketThread.getStarted(getApplicationContext(), hostname) return RocketChatWebSocketThread.getStarted(getApplicationContext(), hostname)
.doOnSuccess(thread -> { .doOnSuccess(thread -> {
webSocketThreads.put(hostname, thread); currentWebSocketThread = thread;
webSocketThreadLock.release(); webSocketThreadLock.release();
}) })
.doOnError(throwable -> { .doOnError(throwable -> {
currentWebSocketThread = null;
RCLog.e(throwable);
Logger.report(throwable); Logger.report(throwable);
webSocketThreadLock.release(); webSocketThreadLock.release();
}); });
}); });
} }
private boolean threadCreatedForHostname(String hostname) {
if (hostname == null || currentWebSocketThread == null) {
return false;
}
return currentWebSocketThread.getName().equals("RC_thread_" + hostname);
}
@Nullable @Nullable
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
......
...@@ -35,6 +35,7 @@ import chat.rocket.android.service.observer.PushSettingsObserver; ...@@ -35,6 +35,7 @@ import chat.rocket.android.service.observer.PushSettingsObserver;
import chat.rocket.android.service.observer.SessionObserver; import chat.rocket.android.service.observer.SessionObserver;
import chat.rocket.android_ddp.DDPClient; import chat.rocket.android_ddp.DDPClient;
import chat.rocket.android_ddp.DDPClientCallback; import chat.rocket.android_ddp.DDPClientCallback;
import chat.rocket.android_ddp.rx.RxWebSocketCallback;
import chat.rocket.core.models.ServerInfo; import chat.rocket.core.models.ServerInfo;
import chat.rocket.persistence.realm.RealmHelper; import chat.rocket.persistence.realm.RealmHelper;
import chat.rocket.persistence.realm.RealmStore; import chat.rocket.persistence.realm.RealmStore;
...@@ -155,14 +156,14 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -155,14 +156,14 @@ public class RocketChatWebSocketThread extends HandlerThread {
RCLog.d("thread %s: terminated()", Thread.currentThread().getId()); RCLog.d("thread %s: terminated()", Thread.currentThread().getId());
unregisterListenersAndClose(); unregisterListenersAndClose();
connectivityManager.notifyConnectionLost(hostname, connectivityManager.notifyConnectionLost(hostname,
ConnectivityManagerInternal.REASON_CLOSED_BY_USER); DDPClient.REASON_CLOSED_BY_USER);
RocketChatWebSocketThread.super.quit(); RocketChatWebSocketThread.super.quit();
emitter.onSuccess(true); emitter.onSuccess(true);
}); });
}); });
} else { } else {
connectivityManager.notifyConnectionLost(hostname, connectivityManager.notifyConnectionLost(hostname,
ConnectivityManagerInternal.REASON_NETWORK_ERROR); DDPClient.REASON_NETWORK_ERROR);
super.quit(); super.quit();
return Single.just(true); return Single.just(true);
} }
...@@ -206,7 +207,7 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -206,7 +207,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
Exception error = task.getError(); Exception error = task.getError();
RCLog.e(error); RCLog.e(error);
connectivityManager.notifyConnectionLost( connectivityManager.notifyConnectionLost(
hostname, ConnectivityManagerInternal.REASON_NETWORK_ERROR); hostname, DDPClient.REASON_CLOSED_BY_USER);
emitter.onError(error); emitter.onError(error);
} else { } else {
keepAliveTimer.update(); keepAliveTimer.update();
...@@ -264,7 +265,8 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -264,7 +265,8 @@ public class RocketChatWebSocketThread extends HandlerThread {
// handling WebSocket#onClose() callback. // handling WebSocket#onClose() callback.
task.getResult().client.getOnCloseCallback().onSuccess(_task -> { task.getResult().client.getOnCloseCallback().onSuccess(_task -> {
if (_task.getResult().code != 1000) { RxWebSocketCallback.Close result = _task.getResult();
if (result.code == DDPClient.REASON_NETWORK_ERROR) {
reconnect(); reconnect();
} }
return null; return null;
...@@ -318,9 +320,11 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -318,9 +320,11 @@ public class RocketChatWebSocketThread extends HandlerThread {
error -> { error -> {
logErrorAndUnsubscribe(reconnectSubscription, error); logErrorAndUnsubscribe(reconnectSubscription, error);
connectivityManager.notifyConnectionLost(hostname, connectivityManager.notifyConnectionLost(hostname,
ConnectivityManagerInternal.REASON_NETWORK_ERROR); DDPClient.REASON_CLOSED_BY_USER);
if (isAlive()) {
new Handler(getLooper()).post(this::unregisterListeners); new Handler(getLooper()).post(this::unregisterListeners);
} }
}
) )
); );
} }
...@@ -446,10 +450,8 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -446,10 +450,8 @@ public class RocketChatWebSocketThread extends HandlerThread {
@DebugLog @DebugLog
private void unregisterListenersAndClose() { private void unregisterListenersAndClose() {
unregisterListeners(); unregisterListeners();
if (DDPClient.get() != null) {
DDPClient.get().close(); DDPClient.get().close();
} }
}
@DebugLog @DebugLog
private void unregisterListeners() { private void unregisterListeners() {
......
...@@ -11,10 +11,18 @@ public class ServerConnectivity { ...@@ -11,10 +11,18 @@ public class ServerConnectivity {
public final String hostname; public final String hostname;
public final int state; public final int state;
public final int code;
public ServerConnectivity(String hostname, int state) { ServerConnectivity(String hostname, int state) {
this.hostname = hostname; this.hostname = hostname;
this.state = state; this.state = state;
this.code = -1;
}
ServerConnectivity(String hostname, int state, int code) {
this.hostname = hostname;
this.state = state;
this.code = code;
} }
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment