Wednesday, 28 January 2015

Passing Errors to Async Threads

A common listener pattern is to have two methods: one for success and one for errors.
/**
 * Listener for an async event. 
 * Sometimes you will see many similar listeners rather than templates.
 */
interface AsyncListener<T> {
    /**
     * Everything went okay and you use the result.
     * You may not include the request object as a parameter, 
     * although I have found that it makes the API easier to use if you do.
     */
    onSuccess(Object request, T result);
    /**
     * The async call failed for some reason.
     * What type of class should ErrorInfo be?
     */
    onError(Object request, ErrorInfo error);
}
Typically you will add this as a parameter to an async call, or you will have addListener() method for some kind of request object.
// As a parameter to an async call
void execute(AsyncListener<Result> listener);

// Using an addListener() method
void addListener(AsyncListener<Result> listener);
void execute();
What kind of class should ErrorInfo be? You are going to use this class a lot so you are going to want to it to be robust and easy to understand. You are going to want to preserve all the information you need for logging and responding programmatically to different types of errors.

The following class is small and simple, yet has had a few iterations and is battle hardened from many projects.

A Favorite Story

/**
 * An error that occurred that has to be reported to a listener or on another thread.
 */
public class ErrorInfo implements Parcelable {

    private int mErrorCode = 0;
    private String mDebugMessage;
    private String mUserMessage;
    private Bundle mContextData;
    private Throwable mException;

    /**
     * Use Builder.
     */
    protected ErrorInfo() {
    }

    /**
     * @return Code that can be used for custom logic flow.
     *         0 means no code has been set.
     *         Should be from an id defined in XML to avoid clashes.
     */
    public int getErrorCode() {
        return mErrorCode;
    }

    public String getDebugMessage() {
        return mDebugMessage;
    }

    /**
     * @return Message to be displayed to the user. This might be a special message from the server via an HTTP error
     *         response.
     */
    public String getUserMessage() {
        return mUserMessage;
    }

    public Bundle getContextData() {
        return mContextData;
    }

    public Throwable getException() {
        return mException;
    }

    @Override
    public String toString() {
        // Note: do not use reflectionToString() because we want these names kept after obfuscation.
        ToStringBuilder builder = new ToStringBuilder(this, new ShortToStringStyle());
        if (mErrorCode != 0) {
            builder.append("errorCode", mErrorCode);
        }
        builder.append("debugMessage", mDebugMessage)
                .append("userMessage", mUserMessage);
        if (mContextData != null) {
            // output the contextData keys individually so even if there is a really long key we will get a chance to
            // see some of the other keys.
            for (String key : new TreeSet<String>(mContextData.keySet())) {
                String value = String.valueOf(mContextData.get(key));
                builder.append("contextData:"+key, StringUtils.abbreviate(value, 1000));
            }
        }
        if (mException != null) {
            builder.append("exception", ExceptionUtils.getStackTrace(mException));
        }
        return builder.build();
    }

    /**
     * Parcelable constructor.
     */
    private ErrorInfo(Parcel in) {
        mErrorCode = in.readInt();
        mDebugMessage = in.readString();
        mUserMessage = in.readString();
        mContextData = in.readBundle();
        mException = (Throwable) in.readSerializable();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(mErrorCode);
        out.writeString(mDebugMessage);
        out.writeString(mUserMessage);
        out.writeBundle(mContextData);
        out.writeSerializable(mException);
    }

    public static final Creator<ErrorInfo> CREATOR = new Creator<ErrorInfo>() {

        @Override
        public ErrorInfo createFromParcel(Parcel in) {
            return new ErrorInfo(in);
        }

        @Override
        public ErrorInfo[] newArray(int size) {
            return new ErrorInfo[size];
        }
    };

    public static class Builder {

        private ErrorInfo mError = new ErrorInfo();

        public Builder setErrorCode(int errorCode) {
            mError.mErrorCode = errorCode;
            return this;
        }

        public Builder setDebugMessage(String debugMessage) {
            mError.mDebugMessage = debugMessage;
            return this;
        }

        public Builder setUserMessage(String userMessage) {
            mError.mUserMessage = userMessage;
            return this;
        }

        public Builder setContextData(Bundle contextData) {
            mError.mContextData = contextData;
            return this;
        }

        /**
         * Convenience method for creating a bundle and adding a serializable to it.
         */
        public Builder setContextData(Serializable contextData) {
            if (mError.mContextData == null) {
                mError.mContextData = new Bundle();
            }
            mError.mContextData.putSerializable("data", contextData);
            return this;
        }

        /**
         * Convenience method for creating a bundle and adding a serializable to it.
         */
        public Builder setContextData(Parcelable contextData) {
            if (mError.mContextData == null) {
                mError.mContextData = new Bundle();
            }
            mError.mContextData.putParcelable("data", contextData);
            return this;
        }

        /**
         * Chains ErrorInfo using contextData. Copies debugMessage and userMessage if it has not been set yet.
         */
        public Builder setContextData(ErrorInfo contextData) {
            if (mError.mContextData == null) {
                mError.mContextData = new Bundle();
            }
            mError.mContextData.putParcelable("caused_by", contextData);
            if (mError.mErrorCode == 0) {
                mError.mErrorCode = contextData.mErrorCode;
            }
            if (mError.mDebugMessage == null) {
                mError.mDebugMessage = contextData.mDebugMessage;
            }
            if (mError.mUserMessage == null) {
                mError.mUserMessage = contextData.mUserMessage;
            }
            return this;
        }

        public Builder setException(Throwable exception) {
            mError.mException = exception;
            return this;
        }

        public ErrorInfo build() {
            if (mError.mException == null) {
                mError.mException = new Exception("ErrorInfo built here");
            }
            return mError;
        }
    }
}

Usage

// When catching an exception
try {
    Result result = calculateResult();
    listener.onSuccess(this, result);
} catch (Exception ex) {
    listener.onError(new ErrorInfo.Builder()
                          .setException(ex)
                          .build());
}

Wednesday, 21 January 2015

Sign Android APK with iOS Key

I was given what looked like an iOS signing key for our nice new Android app. Based on the instructions they asked for I was expecting a jks keystore file as created by keytool. Before I sent them a reply saying I could not use the iOS key, I figured I would try to make it work first.

The Magic


First we need to convert the key into something that keytool can import into our keystore. It turns out that keytool can import a private key by using a work around that uses two different features: using a PKCS12 formatted key as a keystore and merging keystores.

A Quick Read


# Convert PEM to PKCS12 format.
openssl pkcs12 -export -out certificate.pfx -in certificate.pem

# Import into our keystore
keytool -importkeystore -destkeystore my-keystore.jks -srckeystore certificate.pfx -srcstoretype PKCS12 -alias 1

# The openssl export does not preserve the alias name, it uses "1", so rename it.
keytool -changealias -alias "1" -destalias "my_signing_key" -keystore my-keystore.jks

Wednesday, 14 January 2015

Implementing Thread Yield in Javascript

I had a problem developing for the Samsung Smart TV where there was a lot of data coming back from an Ajax request. The time it took to process the data from the Ajax request was halting UI responsiveness. Basically the UI would stop reacting to the user while this data was being processed, which on the TVs limited CPU power took about 20 seconds.

Normally with a language that supports multithreaded processing you would just run the process in a background thread. What can we do in Javascript where we only have one thread?

The Magic


There is a concept in threading called Yield that halts the current thread and allows another threads to run. We could do the same thing by turning what is normally a loop with a Thread.Yield call into tail recursion using setTimeout.

The Novelty

var processWithYield = function(total, chunkSize, processData, onComplete) {

 var processNextChunk = function(begin) {
  
  if (begin < total) {
   
   var end = begin + chunkSize;
   if (end > total) {
    end = total;
   }
  
   console.log("begin "+begin+", end "+end);
   processData(begin, end);
   
   console.log("Done processing, now we are setting next timeout beginning at "+end);
   // Yield the thread by processing the next chunk in 100 milliseconds instead of right away.
   setTimeout(function() {
    processNextChunk(end);
   }, 100);
  } else {
   console.log("All processing completed");
   onComplete();
  }
 };
 processNextChunk(0);
};

Variability


I would tweak the timeout length and the chunkSize until I had the desired data processing time vs. UI responsiveness ratio I wanted.

Further Research


Rather than using a chunkSize and a timeout length the time could be used to determine when a yield should be done. The time could be checked each iteration, then the processData function would only take one row at a time. If checking the time itself was costly in terms of CPU it could only be done once every 2 or more iterations depending on the time it takes to perform one iteration.