Skip to content

Commit 9c93c87

Browse files
Merge pull request #163 from nextcloud/fix-api-not-responding
WIP: add more debugging output / destroy api when reconnect fails
2 parents 493cfce + 2a9ca9f commit 9c93c87

File tree

3 files changed

+66
-27
lines changed

3 files changed

+66
-27
lines changed

src/main/java/com/nextcloud/android/sso/api/AidlNetworkRequest.java

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public class AidlNetworkRequest extends NetworkRequest {
5151
*/
5252
private ServiceConnection mConnection = new ServiceConnection() {
5353
public void onServiceConnected(ComponentName className, IBinder service) {
54-
Log.v(TAG, "Nextcloud Single sign-on: onServiceConnected [" + Thread.currentThread().getName() + "]");
54+
Log.d(TAG, "[onServiceConnected] called from Thread: [" + Thread.currentThread().getName() + "] with IBinder [" + className.toString() + "]: " + service);
5555

5656
mService = IInputStreamService.Stub.asInterface(service);
5757
mBound.set(true);
@@ -62,56 +62,75 @@ public void onServiceConnected(ComponentName className, IBinder service) {
6262
}
6363

6464
public void onServiceDisconnected(ComponentName className) {
65-
Log.e(TAG, "Nextcloud Single sign-on: ServiceDisconnected");
65+
Log.w(TAG, "[onServiceDisconnected] [" + className.toString() + "]");
6666
// This is called when the connection with the service has been
67-
// unexpectedly disconnected -- that is, its process crashed.
68-
mService = null;
69-
mBound.set(false);
67+
// unexpectedly disconnected -- that is, its process crashed or the service was
68+
// terminated due to an update (e.g. google play store)
7069

7170
if (!mDestroyed) {
72-
connectApiWithBackoff();
71+
// In case we're currently not reconnecting
72+
Log.d(TAG, "[onServiceDisconnected] Reconnecting lost service connection to component: [" + className.toString() + "]");
73+
reconnect();
74+
} else {
75+
// API was destroyed on purpose
76+
mService = null;
77+
mBound.set(false);
7378
}
7479
}
7580
};
7681

7782
public void connect(String type) {
78-
if (mDestroyed) {
79-
throw new IllegalStateException("API already stopped");
80-
}
81-
83+
Log.d(TAG, "[connect] Binding to AccountManagerService for type [" + type + "]");
8284
super.connect(type);
8385

8486
String componentName = Constants.PACKAGE_NAME_PROD;
8587
if (type.equals(Constants.ACCOUNT_TYPE_DEV)) {
8688
componentName = Constants.PACKAGE_NAME_DEV;
8789
}
8890

91+
Log.d(TAG, "[connect] Component name is: [" + componentName+ "]");
92+
8993
try {
9094
Intent intentService = new Intent();
9195
intentService.setComponent(new ComponentName(componentName,
9296
"com.owncloud.android.services.AccountManagerService"));
93-
if (!mContext.bindService(intentService, mConnection, Context.BIND_AUTO_CREATE)) {
94-
Log.d(TAG, "Binding to AccountManagerService returned false");
97+
// https://developer.android.com/reference/android/content/Context#BIND_ABOVE_CLIENT
98+
if (!mContext.bindService(intentService, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_ABOVE_CLIENT)) {
99+
Log.d(TAG, "[connect] Binding to AccountManagerService returned false");
95100
throw new IllegalStateException("Binding to AccountManagerService returned false");
101+
} else {
102+
Log.d(TAG, "[connect] Bound to AccountManagerService successfully");
96103
}
97104
} catch (SecurityException e) {
98-
Log.e(TAG, "can't bind to AccountManagerService, check permission in Manifest");
105+
Log.e(TAG, "[connect] can't bind to AccountManagerService, check permission in Manifest");
99106
mCallback.onError(e);
100107
}
101108
}
102109

110+
public void reconnect() {
111+
Log.d(TAG, "[reconnect] called");
112+
unbindService();
113+
connectApiWithBackoff();
114+
}
115+
103116
public void stop() {
104117
super.stop();
105118

119+
unbindService();
120+
mContext = null;
121+
}
122+
123+
private void unbindService() {
106124
// Unbind from the service
107125
if (mBound.get()) {
108126
if (mContext != null) {
127+
Log.d(TAG, "[unbindService] Unbinding AccountManagerService");
109128
mContext.unbindService(mConnection);
110129
} else {
111-
Log.e(TAG, "Context was null, cannot unbind nextcloud single sign-on service connection!");
130+
Log.e(TAG, "[unbindService] Context was null, cannot unbind nextcloud single sign-on service connection!");
112131
}
113132
mBound.set(false);
114-
mContext = null;
133+
mService = null;
115134
}
116135
}
117136

@@ -123,7 +142,7 @@ private void waitForApi() throws NextcloudApiNotRespondingException {
123142
try {
124143
mBound.wait(10000); // wait up to 10 seconds
125144

126-
// If api is still not bound after 10 seconds.. throw an exception
145+
// If api is still not bound after 10 seconds.. try reconnecting
127146
if(!mBound.get()) {
128147
throw new NextcloudApiNotRespondingException();
129148
}
@@ -143,8 +162,6 @@ private void waitForApi() throws NextcloudApiNotRespondingException {
143162
* @throws Exception or SSOException
144163
*/
145164
public Response performNetworkRequestV2(NextcloudRequest request, InputStream requestBodyInputStream) throws Exception {
146-
147-
148165
ParcelFileDescriptor output = performAidlNetworkRequestV2(request, requestBodyInputStream);
149166
InputStream os = new ParcelFileDescriptor.AutoCloseInputStream(output);
150167
ExceptionResponse response = deserializeObjectV2(os);
@@ -204,6 +221,10 @@ private ParcelFileDescriptor performAidlNetworkRequest(NextcloudRequest request,
204221
throw new NetworkOnMainThreadException();
205222
}
206223

224+
if(mDestroyed) {
225+
throw new IllegalStateException("Nextcloud API already destroyed. Please report this issue.");
226+
}
227+
207228
// Wait for api to be initialized
208229
waitForApi();
209230

src/main/java/com/nextcloud/android/sso/api/NetworkRequest.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
import android.os.Looper;
55
import android.util.Log;
66

7+
import androidx.annotation.NonNull;
8+
79
import com.nextcloud.android.sso.aidl.NextcloudRequest;
810
import com.nextcloud.android.sso.helper.ExponentialBackoff;
911
import com.nextcloud.android.sso.model.SingleSignOnAccount;
1012

1113
import java.io.InputStream;
1214

13-
import androidx.annotation.NonNull;
14-
1515
public abstract class NetworkRequest {
1616

1717
private static final String TAG = NetworkRequest.class.getCanonicalName();
@@ -30,7 +30,7 @@ protected NetworkRequest(@NonNull Context context, @NonNull SingleSignOnAccount
3030

3131

3232
protected void connect(String type) {
33-
Log.v(TAG, "Nextcloud Single sign-on connect() called [" + Thread.currentThread().getName() + "] Account-Type: [" + type + "]");
33+
Log.d(TAG, "[connect] connect() called [" + Thread.currentThread().getName() + "] Account-Type: [" + type + "]");
3434
if (mDestroyed) {
3535
throw new IllegalStateException("API already destroyed! You cannot reuse a stopped API instance");
3636
}
@@ -41,8 +41,13 @@ protected void connect(String type) {
4141
protected abstract Response performNetworkRequestV2(NextcloudRequest request, InputStream requestBodyInputStream) throws Exception;
4242

4343
protected void connectApiWithBackoff() {
44-
new ExponentialBackoff(1000, 10000, 2, 5, Looper.getMainLooper(), () -> {
44+
Log.d(TAG, "[connectApiWithBackoff] connectApiWithBackoff() called from Thread: [" + Thread.currentThread().getName() + "]");
45+
new ExponentialBackoff(1000, 5000, 2, 5, Looper.getMainLooper(), () -> {
46+
Log.d(TAG, "[connectApiWithBackoff] trying to connect..");
4547
connect(mAccount.type);
48+
}, () -> {
49+
Log.e(TAG, "Unable to recover API");
50+
stop();
4651
}).start();
4752
}
4853

src/main/java/com/nextcloud/android/sso/helper/ExponentialBackoff.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import android.os.Handler;
2020
import android.os.Looper;
21+
import android.util.Log;
22+
2123
import androidx.annotation.NonNull;
2224
import androidx.annotation.VisibleForTesting;
2325

@@ -27,13 +29,16 @@
2729
/** The implementation of exponential backoff with jitter applied. */
2830
public class ExponentialBackoff {
2931

32+
private static final String TAG = ExponentialBackoff.class.getCanonicalName();
33+
3034
private int mRetryCounter;
3135
private long mStartDelayMs;
3236
private long mMaximumDelayMs;
3337
private long mCurrentDelayMs;
3438
private int mMaxRetries;
3539
private int mMultiplier;
3640
private final Runnable mRunnable;
41+
private final Runnable mFailedCallback;
3742
private final Handler mHandler;
3843

3944
/**
@@ -64,8 +69,9 @@ public ExponentialBackoff(
6469
int multiplier,
6570
int maxRetries,
6671
@NonNull Looper looper,
67-
@NonNull Runnable runnable) {
68-
this(initialDelayMs, maximumDelayMs, multiplier, maxRetries, new Handler(looper), runnable);
72+
@NonNull Runnable runnable,
73+
@NonNull Runnable onErrorRunnable) {
74+
this(initialDelayMs, maximumDelayMs, multiplier, maxRetries, new Handler(looper), runnable, onErrorRunnable);
6975
}
7076

7177
private ExponentialBackoff(
@@ -74,14 +80,16 @@ private ExponentialBackoff(
7480
int multiplier,
7581
int maxRetries,
7682
@NonNull Handler handler,
77-
@NonNull Runnable runnable) {
83+
@NonNull Runnable runnable,
84+
@NonNull Runnable onErrorRunnable) {
7885
mRetryCounter = 0;
7986
mStartDelayMs = initialDelayMs;
8087
mMaximumDelayMs = maximumDelayMs;
8188
mMultiplier = multiplier;
8289
mMaxRetries = maxRetries;
8390
mHandler = handler;
8491
mRunnable = new WrapperRunnable(runnable);
92+
mFailedCallback = onErrorRunnable;
8593

8694
if(initialDelayMs <= 0) {
8795
throw new InvalidParameterException("initialDelayMs should not be less or equal to 0");
@@ -99,17 +107,22 @@ public void start() {
99107
public void stop() {
100108
mRetryCounter = 0;
101109
mHandlerAdapter.removeCallbacks(mRunnable);
110+
102111
}
103112

104113
/** Should call when the retry action has failed and we want to retry after a longer delay. */
105-
private void notifyFailed() {
114+
private void notifyFailed(Exception ex) {
115+
Log.d(TAG, "[notifyFailed] Error: [" + ex.getMessage() + "]");
106116
if(mRetryCounter > mMaxRetries) {
117+
Log.d(TAG, "[notifyFailed] Retries exceeded, ending now");
107118
stop();
119+
mFailedCallback.run();
108120
} else {
109121
mRetryCounter++;
110122
long temp = Math.min(
111123
mMaximumDelayMs, (long) (mStartDelayMs * Math.pow(mMultiplier, mRetryCounter)));
112124
mCurrentDelayMs = (long) (((1 + Math.random()) / 2) * temp);
125+
Log.d(TAG, "[notifyFailed] retrying in: [" + mCurrentDelayMs + "ms]");
113126
mHandlerAdapter.removeCallbacks(mRunnable);
114127
mHandlerAdapter.postDelayed(mRunnable, mCurrentDelayMs);
115128
}
@@ -128,7 +141,7 @@ public void run() {
128141
try {
129142
runnable.run();
130143
} catch (Exception ex) {
131-
notifyFailed();
144+
notifyFailed(ex);
132145
}
133146
}
134147
}

0 commit comments

Comments
 (0)