Skip to content

Commit

Permalink
Better error handling (#102)
Browse files Browse the repository at this point in the history
* Better error handling in Session states.
* AutomaticSyncPolicy now tries to be smart about when to abort. Better Javadoc.
  • Loading branch information
cmelchior authored Sep 16, 2016
1 parent 0b812ea commit 604e67a
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 148 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import io.realm.objectserver.User;
import io.realm.objectserver.UserStore;

import static io.realm.objectserver.ErrorCode.UNKNOWN_ACCOUNT;

public class LoginActivity extends AppCompatActivity {

private UserStore userStore = MyApplication.USER_STORE;
Expand Down Expand Up @@ -91,7 +93,7 @@ public void onSuccess(User user) {
public void onError(ObjectServerError error) {
progressDialog.dismiss();
String errorMsg;
switch (error.errorCode()) {
switch (error.getErrorCode()) {
case UNKNOWN_ACCOUNT:
errorMsg = "Account does not exists.";
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,30 @@

package io.realm.objectserver;

/**
* This class enumerate all potential errors related to using the Object Server or synchronizing data.
*/
public enum ErrorCode {

// See https://github.com/realm/realm-sync/issues/585
// See https://github.com/realm/realm-sync/blob/master/doc/protocol.md

// Realm Java errors (0-49)

UNKNOWN(-1), // Catch-all
IO_EXCEPTION(0, Category.RECOVERABLE), // Some IO error while either contacting the server or reading the response
JSON_EXCEPTION(1), // JSON input could not be parsed correctly

// Realm Object Server errors (100 - 199)

// Connection level and protocol errors

CONNECTION_CLOSED(100, Category.INFO), // Connection closed (no error)
OTHER_ERROR(101, Category.INFO), // Other connection level error
UNKNOWN_MESSAGE(102, Category.INFO), // Unknown type of input message
BAD_SYNTAX(103, Category.INFO), // Bad syntax in input message head
LIMITS_EXCEEDED(104, Category.INFO), // Limits exceeded in input message
WRONG_PROTOCOL_VERSION(105, Category.INFO), // Wrong protocol version (CLIENT)
BAD_SESSION_IDENT(106, Category.INFO), // Bad session identifier in input message
REUSE_OF_SESSION_IDENT(107, Category.INFO), // Overlapping reuse of session identifier (BIND)
BOUND_IN_OTHER_SESSION(108, Category.INFO), // Client file bound in other session (IDENT)
BAD_MESSAGE_ORDER(109, Category.INFO), // Bad input message order
// Connection level and protocol errors.
CONNECTION_CLOSED(100), // Connection closed (no error)
OTHER_ERROR(101), // Other connection level error
UNKNOWN_MESSAGE(102), // Unknown type of input message
BAD_SYNTAX(103), // Bad syntax in input message head
LIMITS_EXCEEDED(104), // Limits exceeded in input message
WRONG_PROTOCOL_VERSION(105), // Wrong protocol version (CLIENT)
BAD_SESSION_IDENT(106), // Bad session identifier in input message
REUSE_OF_SESSION_IDENT(107), // Overlapping reuse of session identifier (BIND)
BOUND_IN_OTHER_SESSION(108), // Client file bound in other session (IDENT)
BAD_MESSAGE_ORDER(109), // Bad input message order

// Session level errors (200 - 299)
SESSION_CLOSED(200, Category.RECOVERABLE), // Session closed (no error)
Expand All @@ -53,18 +52,17 @@ public enum ErrorCode {
NO_SUCH_PATH(205), // No such Realm (BIND)
PERMISSION_DENIED(206), // Permission denied (BIND, REFRESH)

// Fatal: Wrong server/client versions. Trying to sync incompatible files or corrupted.
// Fatal: Wrong server/client versions. Trying to sync incompatible files or the file was corrupted.
BAD_SERVER_FILE_IDENT(207), // Bad server file identifier (IDENT)
BAD_CLIENT_FILE_IDENT(208), // Bad client file identifier (IDENT)
BAD_SERVER_VERSION(209), // Bad server version (IDENT, UPLOAD)
BAD_CLIENT_VERSION(210), // Bad client version (IDENT, UPLOAD)
DIVERGING_HISTORIES(211), // Diverging histories (IDENT)
BAD_CHANGESET(212), // Bad changeset (UPLOAD)

// 300 - 599 Standard HTTP error codes
// 300 - 599 Reserved for Standard HTTP error codes

// Realm Authentication Server response errors (600 - 699)

INVALID_PARAMETERS(601),
MISSING_PARAMETERS(602),
INVALID_CREDENTIALS(611),
Expand All @@ -90,23 +88,27 @@ public String toString() {
return super.toString() + "(" + code + ")";
}

public int errorCode() {
/**
* Returns the numerical value for this error code.
*
* @return the error code as an unique {@code int} value.
*/
public int intValue() {
return code;
}


/**
* Returns the category of the error.
* Returns the getCategory of the error.
* <p>
* Errors come in 3 categories: FATAL, RECOVERABLE, and INFO.
* Errors come in 2 categories: FATAL, RECOVERABLE
* <p>
* FATAL: The session cannot be recovered and needs to be re-created. A likely cause is that the User does not
* have access to this Realm. Check that the {@link SyncConfiguration} is correct.
* have access to this Realm. Check that the {@link SyncConfiguration} is correct. Any fatal error will cause
* the session to be become {@link SessionState#STOPPED}.
* <p>
* RECOVERABLE: The session is paused until given additional information. Most likely cause is an expired access
* token or similar.
* RECOVERABLE: Temporary error. The session becomes {@link SessionState#UNBOUND}, but will automatically try to
* recover as soon as possible.
* <p>
* INFO: The underlying sync client will automatically try to recover from this.
*
* @return the severity of the error.
*/
Expand All @@ -118,7 +120,7 @@ public static ErrorCode fromInt(int errorCode) {
ErrorCode[] errorCodes = values();
for (int i = 0; i < errorCodes.length; i++) {
ErrorCode error = errorCodes[i];
if (error.errorCode() == errorCode) {
if (error.intValue() == errorCode) {
return error;
}
}
Expand All @@ -127,7 +129,6 @@ public static ErrorCode fromInt(int errorCode) {

public enum Category {
FATAL, // Abort session as soon as possible
RECOVERABLE, // Still possible to recover the session by either rebinding or providing the required information.
INFO // Just FYI. The underlying network client will automatically try to recover.
RECOVERABLE // Still possible to recover the session by either rebinding or providing the required information.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
* This class is a wrapper for all errors happening when communicating with the Realm Object Server.
* This include both exceptions and protocol errors.
*
* Only {@link #errorCode()} is guaranteed to be set. If the error was caused by an underlying exception
* {@link #errorMessage()} is {@code null} and {@link #exception()} is set, while if the error was a protocol error
* {@link #errorMessage()} is set and {@link #exception()} is null.
* Only {@link #getErrorCode()} is guaranteed to contain a value. If the error was caused by an underlying exception
* {@link #getErrorMessage()} is {@code null} and {@link #getException()} is set, while if the error was a protocol error
* {@link #getErrorMessage()} is set and {@link #getException()} is null.
*
* @see io.realm.objectserver.ErrorCode for a list of possible errors.
*/
Expand All @@ -34,20 +34,32 @@ public class ObjectServerError extends RuntimeException {
private final String errorMessage;
private final Throwable exception;

/**
* Create an error caused by an error in the protocol when communicating with the Object Server.
*
* @param errorCode error code for this type of error.
* @param errorMessage detailed error message.
*/
public ObjectServerError(ErrorCode errorCode, String errorMessage) {
this(errorCode, errorMessage, null);
this(errorCode, errorMessage, (Throwable) null);
}

/**
* Create an error caused by an an exception when communicating with the Object Server.
*
* @param errorCode error code for this type of error.
* @param exception underlying exception causing this error.
*/
public ObjectServerError(ErrorCode errorCode, Throwable exception) {
this(errorCode, null, exception);
}

/**
* Generic error happening that could happen anywhere.
*
* @param errorCode
* @param errorMessage
* @param exception
* @param errorCode error code for this type of error.
* @param errorMessage detailed error message.
* @param exception underlying exception if the error was caused by this.
*/
public ObjectServerError(ErrorCode errorCode, String errorMessage, Throwable exception) {
this.error = errorCode;
Expand All @@ -58,34 +70,57 @@ public ObjectServerError(ErrorCode errorCode, String errorMessage, Throwable exc
/**
* Errors happening while trying to authenticate a user.
*
* @param errorCode
* @param title
* @param hint
* @param type
* @param errorCode error code for this type of error.
* @param title Title for this type of error.
* @param hint a hint for resolving the error.
*/
public ObjectServerError(ErrorCode errorCode, String title, String hint, String type) {
this(errorCode, String.format("%s : %s (%s)", title, hint, type), null);
public ObjectServerError(ErrorCode errorCode, String title, String hint) {
this(errorCode, (hint != null) ? title + " : " + hint : title, (Throwable) null);
}

public ErrorCode errorCode() {
/**
* Returns the error code uniquely identifying this type of error.
*
* @return the error code identifying the type of error.
* @see ErrorCode
*/
public ErrorCode getErrorCode() {
return error;
}

public String errorMessage() {
/**
* Returns a more detailed error message about the cause of this error.
*
* @return a detailed error message or {@code null} if one was not available.
*/
public String getErrorMessage() {
return errorMessage;
}

public Throwable exception() {
/**
* Returns the underlying exception causing this error, if any.
*
* @return the underlying exception causing this error, or {@code null} if not caused by an exception.
*/
public Throwable getException() {
return exception;
}

public ErrorCode.Category category() {
/**
* Returns the {@link io.realm.objectserver.ErrorCode.Category} category for this error.
* Errors that are {@link io.realm.objectserver.ErrorCode.Category#RECOVERABLE} mean that it is still possible for a
* given {@link Session} to resume synchronization. {@link io.realm.objectserver.ErrorCode.Category#FATAL} errors
* means that session has stopped and cannot be recovered.
*
* @return the error category.
*/
public ErrorCode.Category getCategory() {
return error.getCategory();
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder(errorCode().toString());
StringBuilder sb = new StringBuilder(getErrorCode().toString());
if (errorMessage != null) {
sb.append('\n');
sb.append(errorMessage);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,13 @@ public void onError(Session session, ObjectServerError error) {
String errorMsg = String.format("Session Error[%s]: %s",
session.getConfiguration().getServerUrl(),
error.toString());
switch (error.errorCode().getCategory()) {
switch (error.getErrorCode().getCategory()) {
case FATAL:
RealmLog.error(errorMsg);
break;
case RECOVERABLE:
RealmLog.info(errorMsg);
break;
case INFO:
RealmLog.debug(errorMsg);
break;
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,21 +79,6 @@ public void onExitState() {
}
}

private synchronized void authenticate(final SyncSession session) {
session.authenticateRealm(new Runnable() {
@Override
public void run() {
gotoNextState(SessionState.BINDING);
}
}, new io.realm.objectserver.Session.ErrorHandler() {
@Override
public void onError(io.realm.objectserver.Session session, ObjectServerError error) {
// FIXME For critical errors, got directly to STOPPED
gotoNextState(SessionState.UNBOUND);
}
});
}

@Override
public void onBind() {
gotoNextState(SessionState.BINDING); // Equivalent to forcing a retry
Expand All @@ -108,4 +93,18 @@ public void onUnbind() {
public void onStop() {
gotoNextState(SessionState.STOPPED);
}

private synchronized void authenticate(final SyncSession session) {
session.authenticateRealm(new Runnable() {
@Override
public void run() {
gotoNextState(SessionState.BINDING);
}
}, new Session.ErrorHandler() {
@Override
public void onError(Session s, ObjectServerError error) {
session.onError(error);
}
});
}
}
Loading

0 comments on commit 604e67a

Please sign in to comment.