Friday, September 26, 2008

generic method call timeouts using java.lang.reflect.Proxy

In a future post, I hope to share some code and details on a generic wrapper we built for generated Thrift clients. You pass in a generated Thrift class, and it will return an instance that layers connection pooling, fail-over, connection timeouts, and method call timeouts on top of the thrift code, all without changing the original thrift service API, at runtime.

It's pretty cool! Check back later for that.

In this post, I wanted to talk about a small part of that project, a wrapper we built to dynamically add timeouts to the method calls of an instance. I wanted to make this functionality available separately from the thrift wrapper code, so one could easily specify timeouts dynamically, per call.

Timeouts for tasks have been pretty easy to deal with using an ExecutorService since Java 1.5, and this is the timeout mechanism we used.

The interesting part is dynamically adding this behavior to an instance at runtime. I was unaware of it, but since JDK 1.3 there has been a mechanism to do this, the java.lang.reflect.Proxy class.

"Dynamic proxies provide an alternate, dynamic mechanism for implementing many common design patterns, including the Facade, Bridge, Interceptor, Decorator, Proxy (including remote and virtual proxies), and Adapter patterns. While all of these patterns can be easily implemented using ordinary classes instead of dynamic proxies, in many cases the dynamic proxy approach is more convenient and compact and can eliminate a lot of handwritten or generated classes."

From Brian Goetz's excellent article on the use of this class, Decorating with dynamic proxies.

Using this class, we can provide a generic wrapper that implements method call timeouts. Here are some examples:


TimeoutWrapper<Map> wrapper = new TimeoutWrapper(Map.class, new HashMap());
Map m = wrapper.withTimeout(10, TimeUnit.MILLISECONDS);
m.put("key", "value");
m.get("key");


We may want faster gets than puts:

TimeoutWrapper<Map<String>> wrapper = new TimeoutWrapper(Map.class, new HashMap());
wrapper.withTimeout(10, TimeUnit.MILLISECONDS).put("key", "value");
String v = wrapper.withTimeout(5, TimeUnit.MILLISECONDS).get("key");


TimeoutWrapper provides a small (configurable) cache, so that it will reuse the same proxy instances if you pass in the same timeout values when using the above form.

Here's the main code:

TimeoutWrapper.java
TimedInvocationHandler.java

If you want to compile, you'll need some small supporting classes: GenericProxyFactory.java, LRUCache.java.

No comments: