tag:blogger.com,1999:blog-112512612024-02-28T18:59:09.884-05:00See All. Hear All.nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.comBlogger17125tag:blogger.com,1999:blog-11251261.post-45657336084338884772016-04-05T06:30:00.002-04:002016-04-05T06:30:28.856-04:00A trial port from Netty 3 to Netty 4.1, Part 3 of ?<span style="font-family: "verdana" , sans-serif;">Fixed another stupid mistake in the pipeline factory. Noticed this testing data submission using bosun / scollector which uses gzipped JSON HTTP posts to submit data through OpenTSDB. Of course, the HttpContentDecompressor must be called after the HTTP decoding codec, because the HTTP wrapper is not compressed, just the body of the request. The corrected pipeline is:</span><br />
<script src="https://gist.github.com/nickman/44fafb1fe06546b40c2dd96f0ca0f53f.js"></script>
<span style="font-family: "verdana" , sans-serif;"><br /></span>
<span style="font-family: "verdana" , sans-serif;">Another issue popped up related to Netty 4's <b>HttpResponseStatus</b>. As mentioned before, many Netty 4 classes have been de-getter-izationed where immutable attributes don't have a <b><i>getX</i> </b>method, so what was formerly <b>getStatus()</b> is now <b>status()</b>. Fair enough. But beware.... Many of these classes do not have explicit Jackson [JSON] serializers, since Jackson has typically figured it out on the fly using..... yup. BeanInfo. And since </span><b style="font-family: verdana, sans-serif;">HttpResponseStatus</b><span style="font-family: verdana, sans-serif;"> no longer has a bean like signature, Jackson gripes like this:</span><br />
<span style="font-family: verdana, sans-serif;"><br /></span>
<span style="color: blue; font-family: Courier New, Courier, monospace; font-size: x-small;"><b>No serializer found for class io.netty.handler.codec.http.HttpResponseStatus and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: java.util.HashMap["httpResponse"])</b></span><br />
<span style="font-family: verdana, sans-serif;"><br /></span>
<span style="font-family: verdana, sans-serif;">Not a big deal. Just need to hunt all these cases down and define explicit serializers for each.</span><br />
<span style="font-family: verdana, sans-serif;"><br /></span>
<span style="font-family: verdana, sans-serif;">Anyways, both Telnet and JSON data import is now working through the <b>PutDataPointRpc</b>.</span><br />
<span style="font-family: verdana, sans-serif;"><br /></span>
<span style="font-family: verdana, sans-serif;">Also found a number of instances where I had <b>ByteBuf.retain()</b>ed, the incoming buffer to prevent them from being reclaimed before the pipeline is done with them. Well in these cases, the buffer also needs to be <b>ByteBuf.release()</b>d. This error tipped me off to that:</span><br />
<span style="font-family: verdana, sans-serif;"><br /></span>
<span style="color: blue; font-family: Courier New, Courier, monospace; font-size: x-small;"><b>2016-04-05 06:21:37,447 ERROR [EpollServerWorkerThread#1] ResourceLeakDetector: LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.</b></span><br />
<span style="color: blue; font-family: Courier New, Courier, monospace; font-size: x-small;"><b>WARNING: 4 leak records were discarded because the leak record count is limited to 4. Use system property io.netty.leakDetection.maxRecords to increase the limit.</b></span><br />
<br />
<span style="color: blue; font-family: Courier New, Courier, monospace; font-size: x-small;"><b>Recent access records: 5.</b></span><br />
<span style="font-family: verdana, sans-serif;"><br /></span>
<span style="font-family: verdana, sans-serif;">The leak detection makes it easy to find these cases and I've closed out all the ones I've come across so far. Netty 4 reports some fairly detailed stats on pooled buffers, and since I think Direct Pooled buffers will be a major improvement for OpenTSDB, I'm going to surface these through the stats collector.</span><br />
<br />nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com2tag:blogger.com,1999:blog-11251261.post-30127236603557219492016-04-03T16:22:00.000-04:002016-04-03T16:22:07.538-04:00 A trial port from Netty 3 to Netty 4.1, Part 2 of ?<span style="font-family: "verdana" , sans-serif;">Finally got the UI up and running. </span><br />
<span style="font-family: "verdana" , sans-serif;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2Z2795AhsYKaWH7tjqxoFQsShjgRVKDkreMcs-fvusffGcoPFWlTRSSaCWTfdgb7G1G-1DdAL9gxqVZW2PcgEXM8bb1PYM4tmg-4MWWh6vmZG8EpuDC2tV0Srf5C6eAbiFlKv/s1600/TSDB-New-UI.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="121" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2Z2795AhsYKaWH7tjqxoFQsShjgRVKDkreMcs-fvusffGcoPFWlTRSSaCWTfdgb7G1G-1DdAL9gxqVZW2PcgEXM8bb1PYM4tmg-4MWWh6vmZG8EpuDC2tV0Srf5C6eAbiFlKv/s320/TSDB-New-UI.png" width="320" /></a></div>
<span style="font-family: "verdana" , sans-serif;">I was actually surprised to see it since I have not seen the new look before....</span><br />
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><b><br /></b></span>
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><b>2016-04-03 15:49:50,254 INFO [EpollServerWorkerThread#4] RpcHandler: Handling message [AggregatedFullHttpRequest]</b></span><br />
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><b>2016-04-03 15:49:50,262 INFO [EpollServerWorkerThread#4] HttpQuery: [id: 0x4cc79a07, L:/127.0.0.1:4242 - R:/127.0.0.1:50033] HTTP /aggregators done in 7ms</b></span><br />
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><b>2016-04-03 15:49:50,282 INFO [EpollServerWorkerThread#4] RpcHandler: Handling message [AggregatedFullHttpRequest]</b></span><br />
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><b>2016-04-03 15:49:50,284 INFO [EpollServerWorkerThread#4] HttpQuery: [id: 0x4cc79a07, L:/127.0.0.1:4242 - R:/127.0.0.1:50033] HTTP /s/gwt/opentsdb/images/corner.png done in 1ms</b></span><br />
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><b>2016-04-03 15:49:50,285 INFO [EpollServerWorkerThread#2] RpcHandler: Handling message [AggregatedFullHttpRequest]</b></span><br />
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><b>2016-04-03 15:49:50,285 INFO [EpollServerWorkerThread#3] RpcHandler: Handling message [AggregatedFullHttpRequest]</b></span><br />
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><b>2016-04-03 15:49:50,286 INFO [EpollServerWorkerThread#2] HttpQuery: [id: 0xabbb13cb, L:/127.0.0.1:4242 - R:/127.0.0.1:50031] HTTP /s/gwt/opentsdb/images/hborder.png done in 1ms</b></span><br />
<br />
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><b>2016-04-03 15:49:50,287 INFO [EpollServerWorkerThread#3] HttpQuery: [id: 0x4857bc87, L:/127.0.0.1:4242 - R:/127.0.0.1:50032] HTTP /s/gwt/opentsdb/images/vborder.png done in 2ms</b></span><br />
<span style="font-family: "verdana" , sans-serif;"><br /></span>
<span style="font-family: "verdana" , sans-serif;">I had a bit of trouble getting the static content server to work properly. Not sure what's going on, but after the first serve, all remaining requests seemed to stall and the browser reported no content delivered. It seemed to have something to do with the use of <b>FileRegion</b>s, so I tried swapping the OpenTSDB file serving code with Netty's <a href="http://netty.io/4.1/xref/io/netty/example/http/file/HttpStaticFileServerHandler.html" target="_blank">HttpStaticFileServerHandler</a>, but I saw the same issues.</span><br />
<span style="font-family: "verdana" , sans-serif;"><br /></span>
<span style="font-family: "verdana" , sans-serif;">Eventually, I decided to bypass the problem by just loading the content up int ByteBufs and writing the bufs out. Not the most optimal, but I'll come back round to it soon.</span><br />
<script src="https://gist.github.com/nickman/8ad8f3294deba5f7a371b9bfb4cb7993.js"></script>
<span style="font-family: "verdana" , sans-serif;"><br /></span>
<span style="font-family: "verdana" , sans-serif;">Here's some more conversion steps:</span><br />
<span style="font-family: "verdana" , sans-serif;"><br /></span>
<span style="font-family: "verdana" , sans-serif;">A lot of these:</span><br />
<br />
<ul>
<li><span style="font-family: "courier new" , "courier" , monospace;">query.method().getName()</span><span style="font-family: "verdana" , sans-serif;"> ---> </span><span style="font-family: "courier new" , "courier" , monospace;">query.method().name()</span></li>
<li><span style="font-family: "courier new" , "courier" , monospace;">channel.isConnected()</span><span style="font-family: "verdana" , sans-serif;"> ---> </span><span style="font-family: "courier new" , "courier" , monospace;">channel.isOpen()</span></li>
<li><span style="font-family: "courier new" , "courier" , monospace;">HttpHeaders.Names.CONTENT_TYPE</span><span style="font-family: "verdana" , sans-serif;"> ---> </span><span style="font-family: "courier new" , "courier" , monospace;">HttpHeaderNames.CONTENT_TYPE</span></li>
</ul>
<div>
<span style="font-family: "verdana" , sans-serif;">A new class, <b>HttpUtil</b> handles the setting of some headers, but not all, so for example, this seems to be the most terse way of setting the content length in a response: </span><span style="font-family: "courier new" , "courier" , monospace;">HttpUtil.setContentLength(response, buf.readableBytes());</span></div>
<div>
<span style="font-family: "verdana" , sans-serif;"><br /></span></div>
<div>
<span style="font-family: "verdana" , sans-serif;">Most Netty 4.1 sample code I have read uses </span><span style="font-family: "courier new" , "courier" , monospace;">ChannelHandlerContext.write(...) </span><span style="font-family: "verdana" , sans-serif;">and </span><span style="font-family: "courier new" , "courier" , monospace;">ChannelHandlerContext.writeAndFlush(...)</span><span style="font-family: "verdana" , sans-serif;"> and not the same methods in Channel, although as far as I can tell, they perform the exact same function. Not sure if there is a case to be made to use one over the other, but I converted the OpenTSDB HttpQuery constructs to use ChannelHandlerContext.</span></div>
<div>
<span style="font-family: "verdana" , sans-serif;"><br /></span></div>
<div>
<span style="font-family: "verdana" , sans-serif;">The OpenTSDB PipelineFactory continues to need tweaking. Following up on the changes in Netty's HTTP codecs, specifically the new breakout of HttpRequest into seperate parts, I am no longer sure it makes sense for OpenTSDB to not <i>always</i> have an HTTP object aggregator. Right now, OpenTSDB does not accept chunked requests unless the default is overridden. Problem is, Netty 4 assumes that that either you or an HttpContentAggregator handler will piece the full request back together, so if there are no ill side effects, I propose request aggregation become a permanent fixture. The HTTP switch now looks like this:</span></div>
<div>
<span style="font-family: "verdana" , sans-serif;"><br /></span></div>
<script src="https://gist.github.com/nickman/44fafb1fe06546b40c2dd96f0ca0f53f.js"></script>
<div>
<span style="font-family: "verdana" , sans-serif;">Next up is testing all the RPCs.</span></div>
nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com0tag:blogger.com,1999:blog-11251261.post-41750498881554068662016-04-02T15:00:00.000-04:002016-04-02T15:08:37.569-04:00A trial port from Netty 3 to Netty 4.1, Part 1 of ?<span style="font-family: "verdana" , sans-serif; font-size: x-small;"><br /></span>
<span style="font-family: "verdana" , sans-serif; font-size: x-small;"><br /></span>
<span style="font-family: "verdana" , sans-serif; font-size: x-small;">Ci sono riuscito ! First boot up of OpenTSDB using Netty 4.1.</span><br />
<span style="font-family: "verdana" , sans-serif; font-size: x-small;"><br /></span>
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><b>2016-04-02 13:40:14,919 INFO [EpollServerBossThread#1] TSDTCPServer: [id: 0x4a16e893] REGISTERED</b></span><br />
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><b>2016-04-02 13:40:14,920 INFO [EpollServerBossThread#1] TSDTCPServer: [id: 0x4a16e893] BIND: /0.0.0.0:4242</b></span><br />
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><b>2016-04-02 13:40:14,921 INFO [main] <span style="background-color: yellow;">TSDTCPServer: Started [EpollServerSocketChannel] TCP server listening on [/0.0.0.0:4242]</span></b></span><br />
<span style="font-family: "verdana" , sans-serif; font-size: x-small;"><br /></span>
<span style="font-family: "verdana" , sans-serif; font-size: x-small;">Wasn't too bad. Is it stable ? No chance. Still working on it. What follows is a summary of the porting process so far.</span><br />
<span style="font-family: "verdana" , sans-serif; font-size: x-small;"><br /></span>
<br />
<ul>
<li><span style="font-family: "verdana" , sans-serif; font-size: x-small;">Search and replace </span><span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;">"import org.jboss.netty"</span><span style="font-family: "verdana" , sans-serif; font-size: x-small;"> to </span><span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;">"import io.netty"</span><span style="font-family: "verdana" , sans-serif; font-size: x-small;"> on the whole code base.</span></li>
</ul>
<div>
<span style="font-family: "verdana" , sans-serif; font-size: x-small;">That's it .... ha ha. If only ....</span></div>
<ul>
<li> <span style="font-family: "verdana" , sans-serif; font-size: x-small;">Search and replace </span><span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;">"ChannelBuffer"</span><span style="font-family: "verdana" , sans-serif; font-size: x-small;"> to </span><span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;">"ByteBuff"</span><span style="font-family: "verdana" , sans-serif; font-size: x-small;"> on the whole code base.</span></li>
<li><span style="font-family: "verdana" , sans-serif; font-size: x-small;">Many of the HTTP related Netty bits have getter methods un-gettified. As I understand it, this is to provide a more pure naming conventions where only attributes supporting a set will have a get. So, for example, <b>getName()</b> becomes <b>name()</b>. For example, I made this change a lot: </span><span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;">query.method().getName()</span><span style="font-family: "verdana" , sans-serif; font-size: x-small;"> to </span><span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;">query.method().name()</span><span style="font-family: "verdana" , sans-serif; font-size: x-small;">.</span></li>
<li><span style="font-family: "verdana" , sans-serif; font-size: x-small;">There's no <b>ChannelBuffers</b> utility class in Netty 4, so where ever OpenTSDB allocated an ad-hoc buffer, I delegated the call to a simple <b>ByteBuff</b> factory. The buffer options in Netty 4 are much more extensive than in 3, so I am not sure what to do with the factory, but for now it exists as a functional place-holder. The idea is to take advantage of direct and pooled buffers in OpenTSDB so as to reduce heap space utilization and GC.</span></li>
<li><span style="font-family: "verdana" , sans-serif; font-size: x-small;">Netty 4.1 enforces the channel handler shareability now, so I needed to mark some of the modified handlers as @ChannelHandler.Sharable. Otherwise, you get this:</span></li>
</ul>
<div>
<div>
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;">io.netty.channel.ChannelPipelineException: net.opentsdb.tsd.ConnectionManager is not a @Sharable handler, so can't be added or removed multiple times.</span></div>
</div>
<div>
<span style="font-family: "verdana" , sans-serif; font-size: x-small;"><br /></span></div>
<div>
<ul>
<li><span style="font-family: "verdana" , sans-serif; font-size: x-small;">So far, in OpenTSDB that means <b>ConnectionManager, RPCHandler, DetectHttpOrRpc</b>, and <b>PipelineFactory</b>. There's probably a few more I haven't hit yet. Oh, and in some cases, even when I applied the annotation, I got:</span></li>
</ul>
<div>
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;">Caused by: java.lang.IllegalStateException: @Sharable annotation is not allowed</span></div>
</div>
<div>
<span style="font-family: "verdana" , sans-serif; font-size: x-small;"><br /></span></div>
<div>
<ul>
<li><span style="font-family: "verdana" , sans-serif; font-size: x-small;">But that was a stupid mistake. <b>DetectHttpOrRpc</b> <i>was</i> a Netty 3 <b>FrameDecoder</b>. I switched to be a <b>ByteToMessageDecoder</b> (which suffers @Sharable not!) but the handler does not actually do any decoding so the more appropriate replacement was <b>SimpleChannelInboundHandler<bytebuf></bytebuf></b>.</span></li>
<li><span style="font-family: "verdana" , sans-serif; font-size: x-small;">Pooled Buffers come with some baggage. It's not Louis Vuitton, but it should fit comfortably under the seat in from of you. One bit of baggage is this:</span></li>
</ul>
</div>
<br />
<div>
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;">14:24:10,729 WARN [EpollServerWorkerThread#1] DefaultChannelPipeline: An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.</span></div>
<div>
<span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;">io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1</span></div>
<div>
<ul>
<li><span style="font-family: "verdana" , sans-serif; font-size: x-small;">The issue there was two fold. The Netty 3 version of <b>DetectHttpOrRpc</b> basically looks at the first byte of the incoming buffer to determine if the payload is HTTP or text (Telnet). (Yes, it should probably be called <b>DetectHttpOrTelnet</b>). It makes no relative reads from the buffer, so no bytes are consumed, and when it's done, it returns [a copy of] the full buffer which in Netty 3 FrameDecoder parlance means the buffer was passed to the next handler in the pipeline. However, in Netty 4, once you handle a <i>pooled</i> ByteBuf, you're either done with it, in which case it gets reclaimed by the pool, or <i>you must <b>ByteBuff.retain()</b> it</i>, meaning that it should not be reclaimed and it will be passed as is to the next handler in the pipeline. Phrase to live by..... "<b><i>retain it or lose it"</i></b></span></li>
<li><span style="font-family: "verdana" , sans-serif; font-size: x-small;">The other issue was.... Netty 4 handlers don't typically <b>return</b> stuff. They used to in Netty 3, but the pipelines are now less hole-ey. In short, to send the (retained) ByteBuff to the next handler in this case, you call </span><span style="color: blue; font-family: "courier new" , "courier" , monospace; font-size: x-small;">ChannelHandlerContext.fireChannelRead(byteBuff)</span><span style="font-family: "verdana" , sans-serif; font-size: x-small;">.</span></li>
<li><span style="font-family: "verdana" , sans-serif; font-size: x-small;">Right now I am trying to figure out what to do with the HTTP request handling. Netty 4 breaks up the old HttpRequest (iface) and DefaultHttpRequest (impl) into a vast hierarchy of types that signify different things depending on when you get them. I <i>think</i> there's an easy way to do this, and then there's the super optimized way of doing it, but it's possible that neither of those is true.</span></li>
<li><span style="font-family: "verdana" , sans-serif; font-size: x-small;">One last thing.... I initially removed all references to Netty 3 in my dev classpath (Eclipse) so that outdated references would be highlighted and to help with class lookups and what not. However, keep in mind that both ZooKeeper and AsyncHBase both use (at least in OpenTSDB) Netty 3 so without <i>a lot more</i> porting, Netty 3 and Netty 4 need to live side by side for a while. Thanks for changing the package name guys !!!</span></li>
</ul>
<div>
<span style="font-family: "verdana" , sans-serif; font-size: x-small;"><br /></span></div>
</div>
nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com0tag:blogger.com,1999:blog-11251261.post-48691280532787498632012-06-18T15:32:00.000-04:002013-06-05T13:27:45.294-04:00Netty Tutorial Part 1.5: On Channel Handlers and Channel Options<h3>
<span style="text-decoration: underline;"><b>Intro:</b></span></h3>
After some feedback on <a href="http://seeallhearall.blogspot.com/2012/05/netty-tutorial-part-1-introduction-to.html" target="_blank">Part 1</a>, and being prompted by some stackoverflow questions, I want to expand on and clarify some topics, so this is Part 1.5.<br />
<ul>
<li>Channel Handler Sharability & State</li>
<li>Channel Options</li>
</ul>
<h3>
<span style="text-decoration: underline;"><b>Channel Handlers</b></span></h3>
As discussed previously, most types of <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelHandler.html" target="_blank"><b>ChannelHandler</b></a>s have the job of either encoding objects into byte arrays (or specifically,<b> <a href="http://netty.io/docs/stable/api/org/jboss/netty/buffer/ChannelBuffer.html" target="_blank">ChannelBuffer</a></b>s) or decoding ChannelBuffers into objects. In most cases, handlers will be instantiated and placed into a <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelPipeline.html" target="_blank"><b>ChannelPipeline</b></a> by a defined <b><a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelPipelineFactory.html" target="_blank">ChannelPipelineFactory</a> </b>in code that looks something like this (borrowed from the <a href="http://netty.io/docs/stable/api/org/jboss/netty/bootstrap/ClientBootstrap.html" target="_blank"><b>ClientBootstrap</b></a> javadoc):<br />
<script src="https://gist.github.com/2917650.js">
</script><br />
The seeming complexity of the pipeline factory may not be obvious, but it's actually an elegant structure that flexibly handles some variability in the capabilities of different ChannelHandler implementations. First of all, many ChannelHandlers require some configuration, a detail not represented in the above example. Consider the <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/frame/DelimiterBasedFrameDecoder.html" target="_blank"><b>DelimiterBasedFrameDecoder</b></a> which decodes chunks of bytes in accordance with the constructor specified delimiter. Instances of this decoder require configuration, so the interface only ChannelPipelineFactory allows us to provide the <i>template</i> that defines how it should be created <i>every time</i> a new pipeline is created. An as a recap, every time a new channel is created, a pipeline is created for it, although in some cases, I suppose, you might end up with an empty pipeline with no channel handlers, which means you are using a snazzy framework for passing around ChannelBuffers. That, combined with the ordering and logical names of handlers in the pipeline, is why one needs some care and attention when creating a pipeline. Here's a better example of the use of some <i>parameterized</i> channel handlers in use (again, pilfered liberally from the <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/string/StringEncoder.html" target="_blank"><b>StringEncoder</b></a> javadoc):<br />
<script src="https://gist.github.com/2917590.js">
</script><br />
<h4>
Shared and Exclusive Channel Handlers</h4>
However, a crucial factor in the use of channel handlers is this: Some channel handlers are such good multitaskers, that you only ever need <i>one</i> instance and that single instance can be reused in every single pipeline created. That is to say, these types of channel handlers maintain no state so they are exhibit <b><i>channel safety</i></b>, a variation on the notion of <b><i>thread safety</i></b>. With no state to maintain, there's no reason to create more than one. On the other hand, other channel handlers keep state in instance variables and therefore assume that they are exclusive to one channel and one pipeline. Both the above examples create pipelines with <i>exclusive</i> channel handler instances, inasmuch as they create a new handler instance each time a new pipeline is created. The second example, creating a pipeline factory called <b>MyStringServerFactory</b> (which uses actual real-life channel handlers) contains an example of two channel handlers which could actually be <i><b>shared</b></i>, although they have been created in <b><i>exclusive</i></b> mode. They are the <b>StringEncoder</b> and <b>StringDecoder </b>(and I will explain the reasoning behind it shortly).<br />
Creating and registering a channel handler in shared mode is simply a matter of instantiating one instance and re-using it in all created pipelines. The following code reimplements <b>MyStringServerFactory</b> using a shared <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/string/StringDecoder.html" target="_blank">StringDecoder </a>and <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/string/StringEncoder.html" target="_blank">StringEncoder</a>:<br />
<script src="https://gist.github.com/2917629.js">
</script><br />
<div>
So how is one to know if a channel handler class is sharable or exclusive ? Netty defines an informational annotation called <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelHandler.Sharable.html" target="_blank"><b>@Sharable</b></a> and sharable channel handler classes are annotated as such. To quote from the @Sharable's javadoc: </div>
<div>
<blockquote>
Indicates that the same instance of the annotated <a href="file:///home/nwhitehead/libs/java/netty/netty-3.4.5.Final/doc/api/org/jboss/netty/channel/ChannelHandler.html" title="interface in org.jboss.netty.channel"><code>ChannelHandler</code></a> can be added to one or more <a href="file:///home/nwhitehead/libs/java/netty/netty-3.4.5.Final/doc/api/org/jboss/netty/channel/ChannelPipeline.html" title="interface in org.jboss.netty.channel"><code>ChannelPipeline</code></a>s multiple times without a race condition.<br />
If this annotation is not specified, you have to create a new handler instance every time you add it to a pipeline because it has unshared state such as member variables.<br />
This annotation is provided for documentation purpose, just like <a href="http://www.javaconcurrencyinpractice.com/annotations/doc/">the JCIP annotations</a>.</blockquote>
It's safe to say that the author summed it up with more brevity than I did, but also informs the reader that the annotation is informational only, so there is no <i>built-in</i> use of the annotation within the API implementation itself, although the <a href="http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/annotation/Retention.html" target="_blank"><b>@Retention</b></a> of the annotation is <a href="http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/annotation/RetentionPolicy.html#RUNTIME" target="_blank"><b>RUNTIME</b> </a>so it can be detected by your code, so using a bit of <a href="http://code.google.com/p/reflections/" target="_blank"><b>Reflection[s]</b></a>, I whipped up this table that categorizes all the channel handlers included in the Netty API into <b>Sharable</b>, <b>Exclusive</b>, <b>Upstream</b>, <b>Downstream</b>, and <b>Both</b>. Excluded from this table are abstract and inner classes as well as <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/SimpleChannelHandler.html" target="_blank"><b>SimpleChannelHandler</b></a>, <b><a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/SimpleChannelUpstreamHandler.html" target="_blank">SimpleChannelUpstreamHandler</a> </b>and <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/SimpleChannelDownstreamHandler.html" target="_blank"><b>SimpleChannelDownstreamHandler</b></a>. Those last three are concrete channel handlers and are not annotated as <b>@Sharable</b> but they are, what I refer to as, <i>logically abstract</i>, or <i>empty implementations</i>. By themselves, they do nothing and are intended for extending when implementing your own handler classes.</div>
<div>
<span style="font-size: xx-small;"><i>All packages are in <b>org.jboss.netty</b>.</i></span></div>
<table border="1" cellpadding="2" cellspacing="2"><tbody>
<tr><th><span style="font-size: large;">Direction</span></th> <th><span style="font-size: large;">Sharable</span></th> <th><span style="font-size: large;">Exclusive</span></th></tr>
<tr><td><span style="font-size: large;"><b>Upstream</b></span></td><td valign="top"><ul>
<li><b><span style="font-size: x-small;">handler.codec.string.StringDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.protobuf.ProtobufDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.timeout.IdleStateHandler</span></b></li>
<li><b><span style="font-size: x-small;">handler.timeout.ReadTimeoutHandler</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.base64.Base64Decoder</span></b></li>
</ul>
</td><td valign="top"><ul>
<li><b><span style="font-size: x-small;">handler.codec.http.HttpResponseDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.rtsp.RtspRequestDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.protobuf.ProtobufVarint32FrameDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.frame.LengthFieldBasedFrameDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.frame.DelimiterBasedFrameDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.rtsp.RtspResponseDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.spdy.SpdyHttpDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.serialization.ObjectDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.websocket.WebSocketFrameDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.timeout.IdleStateAwareChannelUpstreamHandler</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.websocketx.WebSocket08FrameDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.HttpChunkAggregator</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.serialization.CompatibleObjectDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.compression.ZlibDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.websocketx.WebSocket00FrameDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.spdy.SpdyFrameDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.HttpRequestDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.frame.FixedLengthFrameDecoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.queue.BlockingReadHandler</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.HttpContentDecompressor</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.websocketx.WebSocket13FrameDecoder</span></b></li>
</ul>
</td></tr>
<tr><td><span style="font-size: large;"><b>Downstream</b></span></td><td valign="top"><ul>
<li><b><span style="font-size: x-small;">handler.codec.base64.Base64Encoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.timeout.WriteTimeoutHandler</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.rtsp.RtspResponseEncoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.protobuf.ProtobufEncoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.websocket.WebSocketFrameEncoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.websocketx.WebSocket00FrameEncoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.rtsp.RtspRequestEncoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.serialization.ObjectEncoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.frame.LengthFieldPrepender</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.string.StringEncoder</span></b></li>
</ul>
</td><td valign="top"><ul>
<li><b><span style="font-size: x-small;">handler.codec.serialization.CompatibleObjectEncoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.HttpResponseEncoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.compression.ZlibEncoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.spdy.SpdyFrameEncoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.spdy.SpdyHttpEncoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.websocketx.WebSocket13FrameEncoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.websocketx.WebSocket08FrameEncoder</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.HttpRequestEncoder</span></b></li>
</ul>
</td></tr>
<tr><td><span style="font-size: large;"><b>Both</b></span></td><td valign="top"><ul>
<li><b><span style="font-size: x-small;">handler.logging.LoggingHandler</span></b></li>
<li><b><span style="font-size: x-small;">handler.execution.ExecutionHandler</span></b></li>
</ul>
</td><td valign="top"><ul>
<li><b><span style="font-size: x-small;">handler.timeout.IdleStateAwareChannelHandler</span></b></li>
<li><b><span style="font-size: x-small;">handler.queue.BufferedWriteHandler</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.spdy.SpdyFrameCodec</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.spdy.SpdySessionHandler</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.spdy.SpdyHttpCodec</span></b></li>
<li><b><span style="font-size: x-small;">handler.ssl.SslHandler</span></b></li>
<li><b><span style="font-size: x-small;">handler.stream.ChunkedWriteHandler</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.HttpClientCodec</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.HttpContentCompressor</span></b></li>
<li><b><span style="font-size: x-small;">handler.codec.http.HttpServerCodec</span></b></li>
</ul>
</td></tr>
</tbody></table>
<div>
<br />
<h4>
Decoders</h4>
You might notice a few instances of asymetry when it comes to sharability. For example, the <b>ObjectEncoder</b> is sharable, but the <b>ObjectDecoder</b> is not. This is revealing about the nature of handlers. An <b>ObjectEncoder</b>'s job is to convert an object into a byte array (ok, a ChannelBuffer), and it is passed the complete payload (the object to be encoded) so there is no need to retain any state. In contrast, the <b>ObjectDecoder</b> has to work with a bunch of bytes which may, or may not be complete for the purposes of reconstituting back into an object. Think about a serialized object being sent from a remote client. It has been serialized into a byte stream of possibly several thousand bytes. When the decoder is first called, the bytestream may be incomplete, with some portion of the byte stream still being transported.<br />
Don't even <b><i>think</i></b> about having the handler <b><i>wait</i></b> for the rest of the byte stream to arrive before proceeding. That sort of thing is just not done around these parts, because Netty is an asynchronous API and we're not going to allocate a pool of worker threads (remmeber, they do all real work) to sit around waiting for I/O to complete. Accordingly, when there are bytes available for processing, a worker thread will be allocated to process it through execution of the ObjectDecoder's <b>decode</b> method. This is the signature of that method:<br />
<br />
<pre><a href="http://java.sun.com/javase/6/docs/api/java/lang/Object.html?is-external=true" title="class or interface in java.lang">Object</a> decode(<a href="file:///home/nwhitehead/libs/java/netty/netty-3.4.5.Final/doc/api/org/jboss/netty/channel/ChannelHandlerContext.html" title="interface in org.jboss.netty.channel">ChannelHandlerContext</a> ctx, <a href="file:///home/nwhitehead/libs/java/netty/netty-3.4.5.Final/doc/api/org/jboss/netty/channel/Channel.html" title="interface in org.jboss.netty.channel">Channel</a> channel, <a href="file:///home/nwhitehead/libs/java/netty/netty-3.4.5.Final/doc/api/org/jboss/netty/buffer/ChannelBuffer.html" title="interface in org.jboss.netty.buffer">ChannelBuffer</a> buffer)</pre>
<pre style="font-family: inherit;"><span style="white-space: normal;"> </span></pre>
<pre><span style="font-family: Times, 'Times New Roman', serif; white-space: normal;">When this method is called, if the buffer contains the complete set of bytes required to decode into an object, then the decoded object will be returned and passed on to the next upstream handler. If not, the handler returns a null, in effect signaling a No Op. Either way, the worker thread doing the work is off onto doing something else once the invocation is complete. </span></pre>
<pre><span style="font-family: Times, 'Times New Roman', serif; white-space: normal;"> </span></pre>
<pre><span style="font-family: Times, 'Times New Roman', serif; white-space: normal;">That's a standard pattern for decoders. Examining <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/serialization/ObjectDecoder.html" target="_blank"><b>ObjectDecoder</b></a> specifically, we can see that it extends <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/frame/LengthFieldBasedFrameDecoder.html" target="_blank">LengthFieldBasedFrameDecoder</a>, and a brief glance at the <a href="http://netty.io/docs/stable/xref/org/jboss/netty/handler/codec/frame/LengthFieldBasedFrameDecoder.html" target="_blank">source </a>of that class shows the following non-final instance fields:</span></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;"> </span></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;">
</span></pre>
<pre style="font-family: inherit;"><b class="jxr_keyword" style="font-family: "Courier New",Courier,monospace; font-weight: normal;">private</b><span style="font-family: 'Courier New', Courier, monospace;"> </span><b class="jxr_keyword" style="font-family: "Courier New",Courier,monospace; font-weight: normal;">boolean</b><span style="font-family: 'Courier New', Courier, monospace;"> discardingTooLongFrame;</span></pre>
<pre style="font-family: inherit;"><a class="jxr_linenumber" href="http://netty.io/docs/stable/xref/org/jboss/netty/handler/codec/frame/LengthFieldBasedFrameDecoder.html#195" name="195" style="font-family: "Courier New",Courier,monospace;"></a><b class="jxr_keyword" style="font-family: "Courier New",Courier,monospace; font-weight: normal;">private</b><span style="font-family: 'Courier New', Courier, monospace;"> </span><b class="jxr_keyword" style="font-family: "Courier New",Courier,monospace; font-weight: normal;">long</b><span style="font-family: 'Courier New', Courier, monospace;"> tooLongFrameLength;</span></pre>
<pre style="font-family: inherit;"><a class="jxr_linenumber" href="http://netty.io/docs/stable/xref/org/jboss/netty/handler/codec/frame/LengthFieldBasedFrameDecoder.html#196" name="196" style="font-family: "Courier New",Courier,monospace;"></a><b class="jxr_keyword" style="font-family: "Courier New",Courier,monospace; font-weight: normal;">private</b><span style="font-family: 'Courier New', Courier, monospace;"> </span><b class="jxr_keyword" style="font-family: "Courier New",Courier,monospace; font-weight: normal;">long</b><span style="font-family: 'Courier New', Courier, monospace;"> bytesToDiscard;</span></pre>
<pre style="font-family: inherit;"><a class="jxr_linenumber" href="http://netty.io/docs/stable/xref/org/jboss/netty/handler/codec/frame/LengthFieldBasedFrameDecoder.html#197" name="197"></a></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;"> </span></pre>
<pre><span style="font-family: Times, 'Times New Roman', serif; white-space: normal;">These fields are the state being protected by making this handler exclusive. The actual byte stream being decoded is not stored in state, but rather accumulates in the same instance of the ChannelBuffer passed to the decode each time an invocation is made to decode the specific byte stream. This is an important conceptual point to understand: A decoder may be invoked more than once in order to decode a byte stream. Completion results in a returned object; in-completion results a returned null (and an assumption of more invocations to follow). </span></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;">
</span></pre>
<pre style="font-family: inherit;"><b><span style="white-space: normal;">Non-Blocking = Scalability ?</span></b></pre>
<pre style="font-family: inherit;"><span style="font-family: arial, helvetica, sans-serif;"><span style="white-space: normal;">
</span></span></pre>
<pre><span style="font-family: Times, 'Times New Roman', serif;"><span style="white-space: normal;">An important takeaway from this is the notion that worker threads only kick in when there is work to do, rather than blocking waiting for I/O to deliver bytes to process. Consider a simple and contrived scenario where a server listens for clients submitting serialized java objects. Implementing a blocking I/O reader, the thread spawned to process a given client's object submission might create a </span><b style="white-space: normal;">java.io.ObjectInputStream</b><span style="white-space: normal;"> to read from the </span><b><span style="white-space: normal;">SocketInputStream</span></b><span style="white-space: normal;">. Bytes are read in and objects are spit out the other side.</span><span style="white-space: normal;"> If the byte stream is constantly full, then we would have efficient use of worker threads, although the model still requires one thread to be dedicated to each connected client. Fair enough, since there is a constant supply of data to process, all things being equal.</span></span></pre>
<pre><span style="font-family: Times, 'Times New Roman', serif;"><span style="white-space: normal;">
</span></span></pre>
<pre style="font-family: inherit;"><span style="font-family: arial, helvetica, sans-serif;"><span style="white-space: normal;">
</span></span></pre>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0knvOWymPOOrN6Rk0eBKV4CvZNcsprTb8oXoKQ7HpWV7dW-_j2FXhyirOajBeQR8s30n8kL56C6mehPRWH-K433rAUYRunBIdbhog8RLth_yV07J660mrI2WeiBsnr-co_QIf/s1600/BusyInputStream.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0knvOWymPOOrN6Rk0eBKV4CvZNcsprTb8oXoKQ7HpWV7dW-_j2FXhyirOajBeQR8s30n8kL56C6mehPRWH-K433rAUYRunBIdbhog8RLth_yV07J660mrI2WeiBsnr-co_QIf/s1600/BusyInputStream.png" /></a></div>
<pre style="font-family: inherit;"><span style="white-space: normal;"><b>
</b></span></pre>
<pre style="font-family: inherit;"><span style="font-family: arial, helvetica, sans-serif; white-space: normal;">
</span></pre>
<pre><span style="font-family: Times, 'Times New Roman', serif; white-space: normal;">However, what happens if the supply of bytes to process is intermitent ? Perhaps the clients only supply data intermitently, or perhaps the network has limited bandwidth and/or is congested, delaying the object transmissions. Now the server has allocated threads to read (and block) from the input stream, but there are long periods of time where there is nothing to do but wait for work to arrive.</span></pre>
<pre style="font-family: inherit;"><span style="font-family: arial, helvetica, sans-serif; white-space: normal;">
</span></pre>
<pre style="font-family: inherit;"><span style="font-family: arial, helvetica, sans-serif; white-space: normal;">
</span></pre>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPRHme1j0aDObN5ch_gvsdH7lnFVLPzwIHVnFGk6gGVJ3wN3QqSKuPgc_hjVyJx1k887Ox4wQmda4CO0bd5uU54Fp4IcIKUQdKCqyd3vfGVKmfaJHdmbQYjnBFDF_uzYcK6GEE/s1600/IdleInputStream.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPRHme1j0aDObN5ch_gvsdH7lnFVLPzwIHVnFGk6gGVJ3wN3QqSKuPgc_hjVyJx1k887Ox4wQmda4CO0bd5uU54Fp4IcIKUQdKCqyd3vfGVKmfaJHdmbQYjnBFDF_uzYcK6GEE/s1600/IdleInputStream.png" /></a></div>
<pre><span style="font-family: arial, helvetica, sans-serif;"><span style="white-space: normal;"><b>
</b></span></span></pre>
<pre style="font-family: inherit;"><span style="font-family: arial, helvetica, sans-serif; white-space: normal;">
</span></pre>
<pre><span style="font-family: Times, 'Times New Roman', serif;"><span style="white-space: normal;">This is one of the reasons that non-blocking I/O tends to scale better than traditional blocking I/O. Note, it does not necessarilly make it <i>faster</i>, it allows the server to accept and process an escalating number of clients for a more controlled and reduced level of resource allocation. For smaller numbers of clients, it is quite likely that blocking I/O will be faster, since dedicated threads require less supervision. Of course, since Netty supports both blocking (OIO) and non-blocking (NIO) channels, a Netty developer can create all sorts of services that can be configured to use either. Which one is appropriate for any given application is so dependent on many application specific factors that it is rarely possible to state any hard rules about this. The Netty javadoc for the </span><span style="white-space: normal;">org.jboss.netty.channel.socket.oio package attempts to set a rule of thumb regarding the use of OIO:</span></span></pre>
<blockquote>
<pre style="font-family: inherit;"><span style="font-family: arial, helvetica, sans-serif;"><span style="white-space: normal;">Old blocking I/O based socket channel API implementation - recommended for a small number of connections (< 1000).</span></span></pre>
</blockquote>
<pre><span style="font-family: Times, 'Times New Roman', serif; white-space: normal;">That seems high to me, but hardware, network, traffic types and other factors will certainly influence the magic number.</span></pre>
<pre style="font-family: inherit;"><span style="font-family: arial, helvetica, sans-serif;"><span style="white-space: normal;">
</span></span></pre>
<h4 style="font-family: inherit;">
<span style="white-space: normal;"> Framing (buffers, not pictures)</span></h4>
<pre><span style="font-family: Times, 'Times New Roman', serif; white-space: normal;"> ObjectDecoder extends <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/frame/LengthFieldBasedFrameDecoder.html" target="_blank">LengthFieldBasedFrameDecoder</a> because the latter performs a critical function necessary in many decoders. Without a frame decoder, the object decoder would be faced with an entire unsegmented byte stream, not knowing where one serialized object ended and the next one started. </span></pre>
<pre><span style="font-family: Times, 'Times New Roman', serif; white-space: normal;">
</span></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;">
</span></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;">
</span></pre>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihv_uhS1cxyTgHM2rcuwHqjR1cB1YOOJlUTayG3OZAn8Q6blAj0FireuOUHMIZHAHkjzn49WcntwwzfbMu0Sn5p20-se3Ci_9eSA8hw04JGzDmtFCS39zsg2p_DO5HeNKl8r_w/s1600/NoDecoder.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihv_uhS1cxyTgHM2rcuwHqjR1cB1YOOJlUTayG3OZAn8Q6blAj0FireuOUHMIZHAHkjzn49WcntwwzfbMu0Sn5p20-se3Ci_9eSA8hw04JGzDmtFCS39zsg2p_DO5HeNKl8r_w/s1600/NoDecoder.png" /></a></div>
<pre style="font-family: inherit;"><span style="white-space: normal;">
</span></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;">
</span></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;">
</span></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;">
</span></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;">
</span></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;">
</span></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;"> </span></pre>
<pre><span style="font-family: Times, 'Times New Roman', serif; white-space: normal;">Of course, the <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/serialization/CompatibleObjectDecoder.html" target="_blank">CompatibleObjectDecoder</a> can do this because it expects a standard java serialization stream, but there are several downsides to this, not the least of which is that they payloads will be significantly larger. </span><span style="font-family: Times, 'Times New Roman', serif; white-space: normal;">In order to help the ObjectDecoder figure out which bytes belong to which serialized object, the frame decoder part of the class knows how to segment the byte stream into discrete chunks that the ObjectDecoder can assume are the contents of one object.</span></pre>
<pre><span style="font-family: Times, 'Times New Roman', serif; white-space: normal;">
</span></pre>
<pre><span style="font-family: Times, 'Times New Roman', serif; white-space: normal;">
</span></pre>
<pre><span style="font-family: Times, 'Times New Roman', serif; white-space: normal;">
</span></pre>
<div class="separator" style="clear: both; text-align: center;">
</div>
<pre><span style="font-family: Times, 'Times New Roman', serif; white-space: normal;">
</span></pre>
<pre><span style="white-space: normal;">
</span></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;">
</span></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;">
</span></pre>
<pre style="font-family: inherit;"><span style="white-space: normal;">
</span></pre>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFb1c9wjmyiHqjEfHyMt9tr-rdZsxb4ptC7sVl1qB1mnH_GcyFI4Ib5LoL3uEAciQuOioFe-4RPVPw14HRPkq6HMuX06ntheFUUndRL6KU5KNtiaAXXeivx_el58R6KpWr8BX5/s1600/UsingDecoder.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFb1c9wjmyiHqjEfHyMt9tr-rdZsxb4ptC7sVl1qB1mnH_GcyFI4Ib5LoL3uEAciQuOioFe-4RPVPw14HRPkq6HMuX06ntheFUUndRL6KU5KNtiaAXXeivx_el58R6KpWr8BX5/s1600/UsingDecoder.png" /></a></div>
<br />
<br />
The frame decoder knows how to segment the byte stream because it works in concert with the sender's <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/serialization/ObjectEncoder.html" target="_blank">ObjectEncoder</a>, which has conveniently placed length indicators into the downstream byte stream, specifically so the upstream decoder can use them to parse. When implementing your own custom decoders, if you wanted to implement this technique, your client can use the <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/frame/LengthFieldPrepender.html" target="_blank">LengthFieldPrepender</a> encoder.<br />
<br />
There are two points to note from this:<br />
<br />
<ul>
<li>Most non-trivial decoders are either implementing a known protocol specification (like HTTP for example) or are expecting the payload to have been "massaged" by the encoding counterpart (like the ObjectEncoder).</li>
<li>The opportunity for optimization with the Netty encoder/decoder pairs is a good (though not the only) reason to use Netty even if you're using the traditional blocking IO ( OIO ) model.</li>
</ul>
<div>
<br /></div>
<div>
In the case of the ObjectDecoder, it has it's own frame decoding built in, but in many decoder classes, there is no frame decoding built in and framing support needs to be provided. Fortunately, the modular architecture of the ChannelPipeline allows us to insert a frame decoder <i>before</i> instances of decoders that need them. A good example of this is the <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/string/StringDecoder.html" target="_blank">StringDecoder</a>. The javadoc emphasizes that this decoder should be used in conjunction with a frame decoder:</div>
<div>
<br /></div>
<blockquote class="tr_bq">
<span style="background-color: white; color: #353833; font-family: Arial, Helvetica, sans-serif; font-size: 12px;">Please note that this decoder must be used with a proper </span><a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/frame/FrameDecoder.html" style="background-color: white; color: #4c6b87; font-family: Arial, Helvetica, sans-serif; font-size: 12px; text-decoration: none;" title="class in org.jboss.netty.handler.codec.frame"><code style="font-size: 1.2em;">FrameDecoder</code></a><span style="background-color: white; color: #353833; font-family: Arial, Helvetica, sans-serif; font-size: 12px;"> such as </span><a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/frame/DelimiterBasedFrameDecoder.html" style="background-color: white; color: #4c6b87; font-family: Arial, Helvetica, sans-serif; font-size: 12px; text-decoration: none;" title="class in org.jboss.netty.handler.codec.frame"><code style="font-size: 1.2em;">DelimiterBasedFrameDecoder</code></a><span style="background-color: white; color: #353833; font-family: Arial, Helvetica, sans-serif; font-size: 12px;"> if you are using a stream-based transport such as TCP/IP.</span></blockquote>
<div>
<br /></div>
<div>
There are a few ways that transmitted strings might be represented and the corresponding frame encoder and decoder that can be used:</div>
<div>
<ol>
<li>Fixed Length: No specific encoder, just use fixed length strings / <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/frame/FixedLengthFrameDecoder.html" target="_blank">FixedLengthFrameDecoder</a></li>
<li>Length Field Prepend: <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/frame/LengthFieldPrepender.html" target="_blank">LengthFieldPrepender</a> / <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/frame/LengthFieldBasedFrameDecoder.html" target="_blank">LengthFieldBasedFrameDecoder</a></li>
<li>String delimiter: No specific encoder, but strings should be delimited by the specified delimiters / <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/frame/DelimiterBasedFrameDecoder.html" target="_blank">DelimiterBasedFrameDecoder</a>. </li>
</ol>
<div>
The DelimiterBasedFrameDecoder can be configured with more than one delimeter and the presence of either in the stream will trigger a frame segmentation. Typical string delimiters are <b>end-of-line</b> characters or null characters. Delimiters are specified not as strings or even characters, but as ChannelBuffers so they are expressed using the same type that the decoder's input and output will be in.</div>
<div>
<br /></div>
<div>
The class <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/frame/Delimiters.html" target="_blank">Delimiters</a> contains a few pre-defined and commonly used delimiters, and it's simple enough to define your own:</div>
<div>
<br /></div>
</div>
<div>
<span style="font-family: 'Courier New', Courier, monospace;">ChannelBuffer delimiter = <a href="http://netty.io/docs/stable/api/org/jboss/netty/buffer/ChannelBuffers.html" target="_blank">ChannelBuffers</a>.wrappedBuffer("THEBIG-D".getBytes())</span></div>
<br />
<br />
To frame-up this discussion on frames, channel handlers are often put to work in conjunction with each other. This can be within one pipleline, such as a frame decoder before a string decoder. In addition, a decoder's decoding algorithm may rely on a counterpart encoder's encoding.<br />
<br />
<h4>
ChannelHandler State</h4>
<div>
<br /></div>
Although the most common (and recommended) way of handling state in a ChannelHandler is to use instance fields and then make sure the handler is used exclusively ( a new instance per pipeline inserted into ), there are a couple of ways of keeping state safely in a shared ChannelHandler.<br />
<br />
The <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelHandlerContext.html" target="_blank">ChannelHandlerContext</a> class provides the notion of an <b>attachment</b> which is a store that is specific to a channel and handler instance. It is accessed using the getter and setter methods:<br />
<br />
<span style="font-family: 'Courier New', Courier, monospace;">void channelHandlerContext.setAttachment(Object obj)</span><br />
<span style="font-family: 'Courier New', Courier, monospace;">Object channelHandlerContext.getAttachment()</span><br />
<br />
Typically, if you only need to keep state within a handler across calls to the handler, the attachment model is suitable. However, if you need to access state specific to a channel outside [and/or inside] the handler, Netty provides an analog to <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html" target="_blank">ThreadLocal</a> called a <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelLocal.html" target="_blank">ChannelLocal</a>. Obviously, since this state is channel specific, but may be accessed by multiple different threads, a ThreadLocal does not serve this purpose. While a ThreadLocal is essentially a bit of data keyed in part by the owning thread, a ChannelLocal is similar in that it is a bit of data keyed by the owning channel. Accordingly, you can define a static field and using the channel as the key, access the value from pretty much anywhere in your code. The primary methods are:<br />
<br />
<br />
<span style="font-family: 'Courier New', Courier, monospace;">void channelLocal.set(Channel channel, T value)</span><br />
<span style="font-family: 'Courier New', Courier, monospace;">T channelLocal.get(Channel channel)</span><br />
<div>
<span style="font-family: 'Courier New', Courier, monospace;"><br /></span></div>
<br />
This concludes the discussion specific to handlers. It seems to me that a missing piece here is a walk-through of building a read world encoder/decoder pair that really does something useful, and I will endeavor to do this in a future entry.<br />
<br />
<h3>
Channel Options</h3>
<br />
Channel options are configuration name/value pairs that are used as a loosely typed way of configuring various bits-and-pieces in Netty. They fall into four categories:<br />
<br />
<br />
<ul>
<li>General Netty Configuration</li>
<li>Client Socket Configuration</li>
<li>Server Socket Configuration </li>
<li>Server Child Socket Configuration </li>
</ul>
<div>
<br />
In essence, channel options are placed in a Map<string object=""> and then passed to the <a href="http://netty.io/docs/stable/api/org/jboss/netty/bootstrap/Bootstrap.html" target="_blank">Bootstrap</a>; the options provided are applied where applicable, so this is an abstracted way of configuring the stack.</string></div>
<div>
<br /></div>
<h4>
General Netty Channel Options</h4>
<div>
There are a number of option keys that can be used in place of specific API calls to configure how channels are created. The following is a partial summary of the keys and what the types are just to give you an idea what they are. </div>
<div>
<br /></div>
<div>
<ul>
<li><b>bufferFactory</b>: An instance of a <a href="http://netty.io/docs/stable/api/org/jboss/netty/buffer/ChannelBufferFactory.html" target="_blank">ChannelBufferFactory</a> that will be called to create new ChannelBuffers. This might be useful in order to customize the byte order of created channels, or to create direct (off heap) buffers.</li>
<li><b>connectTimeoutMillis</b>: The number of milliseconds before a connection request times out. Ignored if the channel factory creates connectionless channels.</li>
<li><b>pipelineFactory</b>: An instance of a ChannelPipelineFactory</li>
<li><b>receiveBufferSizePredictor</b>: UDP packets are received into pre-created byte arrays, which is an unresolvable dilema in that you don't know how much space to allocate until you have received the datagram, but to receive the datagram, you have to allocate a buffer to receive it. <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ReceiveBufferSizePredictor.html" target="_blank">BufferSizePredictor</a>s provide a way to customize the size of those arrays based on some intelligence you might have about the expected size.</li>
<li><b>receiveBufferSizePredictorFactory</b>: A factory for creating buffer size predictors.</li>
</ul>
<div>
<br /></div>
</div>
<h4>
Socket Channel Options</h4>
<div>
Most of the supported channel options are for configuring sockets. By and large, Netty does not really expose the Java <a href="http://docs.oracle.com/javase/6/docs/api/java/net/Socket.html" target="_blank">Socket</a> API that much, which might seem odd for a networking API. However, all implementations of SocketChannel use sockets, and sockets have various low level configuration items which can be set through channel options. In general, these are the types of sockets we are working with:</div>
<div>
<br /></div>
<div>
<ul>
<li><b>Client Socket</b>: A socket on the client side of a TCP connection to a server.</li>
<li><b>Datagram Socket</b>: A socket on the client and/or server side of a unicast UDP conversation.</li>
<li><b>Multicast Socket</b>: A socket on the client and/or server side of a multicast UDP conversation. </li>
<li><b>Server Socket</b>: A specialized socket on the server side of a TCP server that brokers connection requests from client sockets and spins up a new Socket dedicated to that connection in an action referred to as an <a href="http://docs.oracle.com/javase/6/docs/api/java/net/ServerSocket.html#accept()" target="_blank">accept</a>. Aside from the initial connection, a client socket does not communicate with the server socket.</li>
<li><b>Child Socket</b>: The socket created on the server, by the server socket, to communicate with a client socket.</li>
</ul>
</div>
<br />
All of these socket types support options outlined in <a href="http://java.net.socketoptions/">java.net.SocketOptions</a> and many of them can have a significant impact on performance. The options are listed below with the <b>SocketOption</b> constant name and the Netty channel option equivalent.<br />
<br />
<br />
<br />
<ul>
<li><b>IP_MULTICAST_IF / networkInterface</b>: The name of the interface on which outgoing multicast packets should be sent. When a host has multiple network interfaces, this tends to be quite important.</li>
<li><b>IP_MULTICAST_IF2</b><b> / networkInterface</b>: The same as <b>IP_MULTICAST_IF</b> but defined again for good measure.</li>
<li><b>IP_MULTICAST_LOOP / loopbackModeDisabled</b>: Defines if multicast packets should be received by the sender of the same.</li>
<li><b>IP_TOS / trafficClass</b>: Sets the type of service or traffic class in the headers of packets.</li>
<li><b>SO_BINDADDR / <none></none></b>: A read only option representing the bind interface of a local socket.</li>
<li><b>SO_BROADCAST / broadcast</b>: Enables or disables a DataGramSocket's ability to send broadcast messages,</li>
<li><b>SO_KEEPALIVE / keepAlive</b>: A flag indicating that probes should be periodically sent across the network to the oposing socket to keep the connection alive.</li>
<li><b>SO_LINGER / soLinger</b>: Allows the close of a socket to be delayed for a period of time to allow completion of I/O.</li>
<li><b>SO_OOBINLINE / <not implemented=""></not></b>: Allows <a href="http://en.wikipedia.org/wiki/Out-of-band" target="_blank">Out of Band TCP Urgent data</a> transmissions to be received through the normal data channel rather than discarded.</li>
<li><b>SO_RCVBUF / receiveBufferSize</b>: The size in bytes of the socket's data receive buffer.</li>
<li><b>SO_REUSEADDR / reuseAddress</b>: When set to true, allows multiple DatagramSockets to be bound to the same socket address if the option is enabled prior to binding the socket.</li>
<li><b>SO_SNDBUF / send</b><b>BufferSize</b>: The size in bytes of the socket's data send buffer.</li>
<li><b>SO_TIMEOUT / connectTimeoutMillis</b>: Sets a timeout in ms. on blocking socket operations. </li>
<li><b>TCP_NODELAY / tcpNoDelay</b>: Disables <a href="http://en.wikipedia.org/wiki/Nagle%27s_algorithm" target="_blank">Nagle's algorithm</a> on the socket which delays the transmission of data until a certain volume of pending data has accumulated.</li>
</ul>
<div>
<br /></div>
<div>
<h4>
SO_Timeout vs. ChannelFuture Await</h4>
</div>
The <b>connectTimeoutMillis</b> (or Socket <b>SO_TIMEOUT</b>) is sometimes confused with the timeout that can be set on a <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelFuture.html" target="_blank">ChannelFuture</a> using <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelFuture.html#await(long,%20java.util.concurrent.TimeUnit)" target="_blank">await</a> or one of its equivalents. The SO_TIMEOUT is strictly the time limit (in ms.) that an I/O operation on the socket must complete in. If a <a href="http://docs.oracle.com/javase/6/docs/api/java/net/Socket.html#connect(java.net.SocketAddress)" target="_blank">connect</a> I/O operation is not complete within that time, the socket instance will throw a <b><a href="http://docs.oracle.com/javase/6/docs/api/java/net/ConnectException.html" target="_blank">java.net.ConnectException</a>: connection timed out exception</b>. On the other hand, the ChannelFuture's await timeout is simply the amount of time the calling thread will wait <i>for the operation to complete</i>. The timeout itself does not impact the operation and the operation is considered complete when it concludes, whether it was successful or not.<br />
<br />
Having said that, if you wanted any asynchronous operation executed on a channel to be cancelled if it did not complete within a specific period of time, a ChannelFuture await timeout is useful because when the timeout elapses and the operation is found incomplete, it can be cancelled. This might be applicable, for example, in a mobile device scenario where you might want to save battery consumption by ensuring that network operations did not run an overly long time. Just because your application threads are not running, does not mean the device would not be consuming power attempting to complete a long running network operation that the application has long since forgotten about.<br />
<br />
The following example will attempt to illustrate the difference and how they can be usefully implemented. This code sample is based on simple testing framework class called <a href="https://github.com/nickman/netty-ajax-server/blob/master/src/main/java/org/helios/netty/examples/state/SimpleNIOClient.java" target="_blank">SimpleNIOClient</a>. The code for the example is <a href="https://github.com/nickman/netty-ajax-server/blob/master/src/main/java/org/helios/netty/examples/timeout/TimeoutTest.java" target="_blank">TimeoutTest</a>. The test performed is very simple and just attempts to connect to an internet address using different values for the ChannelFuture await timeout and the client socket's SO_TIMEOUT.<br />
<br />
The salient part of this test is the connect code and the timers that are placed around it.<br />
<br />
<pre class="brush:java"> public static void timeoutTest(long ioTimeout, long futureTimeout) {
// Create a map with the channel options
Map<String, Object> options = new HashMap<String, Object>();
options.put("connectTimeoutMillis", ioTimeout);
// Create the client
// Not providing any handlers since we're not using any for this test
SimpleNIOClient client = new SimpleNIOClient(options);
// Issue a connect operation
ChannelFuture cf = client.connect("heliosapm.com", 80);
// Add a completion listener
cf.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()) {
clog("F: Connected:" + future.isDone());
} else {
if(future.isCancelled()) {
clog("Request Cancelled");
} else {
clog("Connect Exception:Success: " + future.isSuccess() + " Done: " + future.isDone() + " Cause: "+ future.getCause());
}
}
}
});
// Wait at least futureTimeout for the operation to complete
cf.awaitUninterruptibly(futureTimeout);
// If the operation is not complete, cancel it.
if(!cf.isDone()) {
clog("Channel Future Still Waiting. Cancelled:" + cf.cancel());
}
}
</pre>
<br />
I run the test using arbitrarily low timeouts since I am trying to trigger connect timeouts. The invocation code looks like this:<br />
<pre class="brush:java"> // Timeout on I/O
timeoutTest(10, 500);
// Wait a bit so output is clear
try { Thread.sleep(2000); } catch (Exception e) {}
System.out.println("===============================");
// Timeout on future
timeoutTest(500, 10);
try { Thread.currentThread().join(5000); } catch (Exception e) {}
</pre>
<br />
In the first case, on line 2, I want the <b>SO_TIMEOUT</b> timeout to occur, so I invoke with a low I/O timeout and a higher ChannelFuture timeout. On line 7, I reverse the values for a higher timeout on the socket, but a low timeout on the ChannelFuture. The output is as follows:<br />
<br />
<br />
<span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"><b>[Client][Thread[ClientBossPoolThread#1,5,ClientBossPoolThreadGroup]]:Socket Timeout Exception:Success: false Done: true Cause: java.net.ConnectException: connection timed out</b></span><br />
<span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"><b>===============================</b></span><br />
<span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"><b>[Client][Thread[main,5,main]]:Request Cancelled</b></span><br />
<span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"><b>[Client][Thread[main,5,main]]:Channel Future Still Waiting. Cancelled:true</b></span><br />
<br />
<br />
In the first case, the socket timeout is so low that the operation completes quite quickly (although it does not succeed) so the future listener reports that the operation:<br />
<br />
<ol>
<li>Was not successful</li>
<li>Is Complete</li>
<li>Resulted in a <b>ConnectException</b> on account of a timed out connection.</li>
</ol>
<div>
In the second call, the higher socket timeout means the future timeout expires first, and when it does, the operation is still running, so the thread cancels it.<br />
<br />
<h4>
Socket Buffer Sizes</h4>
</div>
<div>
<br /></div>
<div>
Another critical performance affecting channel option are the socket buffer sizes, which are the sizes of the send and receive native platform buffers allocated by the networking kernel to buffer I/O. It is possible to simply set these values arbitrarily high and leave it at that, but in situations where there are a large number of sockets in use, this may waste native memory resources.<br />
<br />
The next example code I want to demonstrate uses another test harness called <a href="https://github.com/nickman/netty-ajax-server/blob/master/src/main/java/org/helios/netty/examples/state/SimpleNIOServer.java" target="_blank">SimpleNIOServer</a>. The test class is called <a href="https://github.com/nickman/netty-ajax-server/blob/master/src/main/java/org/helios/netty/examples/socketbuffer/AdjustableSocketBufferSizeServer.java" target="_blank">AdjustableSocketBufferSizeServer</a>. It requires a little explanation around some of the customizations, so please bear with....<br />
<br />
<h4>
<a href="https://github.com/nickman/netty-ajax-server/blob/master/src/main/java/org/helios/netty/examples/state/ChannelHandlerProviderFactory.java" target="_blank">ChannelHandlerProviderFactory</a></h4>
</div>
This is a channel handler factory that understands whether a channel handler class is sharable or exclusive, so when an instance of a <a href="https://github.com/nickman/netty-ajax-server/blob/master/src/main/java/org/helios/netty/examples/state/ChannelHandlerProvider.java" target="_blank">ChannelHandlerProvider</a> is given to the <a href="https://github.com/nickman/netty-ajax-server/blob/master/src/main/java/org/helios/netty/examples/state/ChannelPipelineFactoryImpl.java" target="_blank">ChannelPipelineFactoryImpl</a> and it creates a new pipeline, it will either create a brand new handler instance (if exclusive) or provide the "singleton" instance already created. This is not critical to the example, but to avoid confusion, I am mentioning their use.<br />
<br />
<h4>
<a href="https://github.com/nickman/netty-ajax-server/blob/master/src/main/java/org/helios/netty/examples/state/CustomServerBootstrap.java" target="_blank">CustomServerBootstrap</a></h4>
This is a customized server bootstrap that allows me to change the channel options on the fly. In the native version, once a bootstrap has been created, the channel options are committed and I want to to be able to change the socket buffer sizes of created child channels on the fly. (Once a change is made, it affects only new connections from that point on.... it does not change existing socket buffer sizes). I also created a management interface called <a href="https://github.com/nickman/netty-ajax-server/blob/master/src/main/java/org/helios/netty/examples/jmx/BootstrapJMXManager.java" target="_blank">BootstrapJMXManager</a> that allows me to make these changes through JMX.<br />
<br />
Alright, the test class, <a href="https://github.com/nickman/netty-ajax-server/blob/master/src/main/java/org/helios/netty/examples/socketbuffer/AdjustableSocketBufferSizeServer.java" target="_blank">AdjustableSocketBufferSizeServer</a>, creates a new server using <b>SimpleNIOServer</b>. It also creates a <a href="http://docs.oracle.com/javase/6/docs/api/javax/management/remote/JMXConnectorServer.html" target="_blank">JMXConnectorServer</a> and <a href="http://docs.oracle.com/javase/6/docs/api/java/rmi/registry/Registry.html" target="_blank">RMI Registry</a> so the <a href="http://docs.oracle.com/javase/6/docs/api/javax/management/MBeanServer.html" target="_blank">MBeanServer</a> can easily be connected to so the child socket receive buffer sizes can be adjusted programatically, so that in turn the test case can be run automatically end to end.<br />
<br />
The server's pipeline factory creates pipelines with the following handlers:<br />
<br />
<ul>
<li>Upstream: </li>
<ul>
<li>InstrumentedDelimiterBasedFrameDecoder with a "|" delimiter (exclusive)</li>
<li>StringDecoder (shared)</li>
<li>StringReporter (shared)</li>
</ul>
<li>Downstream:</li>
<ul>
<li>CompatibleObjectEncoder (shared) </li>
</ul>
</ul>
<br />
The test is fairly straightforward:<br />
<ol>
<li>The client makes a JMX call to the server to specify the size of the server allocated child channel's socket receive buffer size.</li>
<li>The client makes a new connection and sends a [longish] string to the server.</li>
<li>The FrameDecoder splits the string content and sends the fragments upstream to the StringDecoder.</li>
<li>The StringDecoder decodes the byte stream into a String and sends the strings upstream to the StringReporter.</li>
<li>The StringReporter measures the length of the String, creates an int array with the length of the passed string and writes the array downstream back to the client.</li>
<li>On its way downstream, the<span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"><b> int[]</b></span> is encoded using standard Java serialization in the CompatibleObjectEncoder.</li>
<li>The client records the elapsed time to get a response from the server.</li>
<li>The client increases the requested server allocated child channel's socket receive buffer size.</li>
<li>Rinse, Repeat, (Go to #1.) for each subsequent defined buffer size</li>
</ol>
The script looping has an outer loop and an inner loop:<br />
<br />
<ul>
<li>Outer Loop: Iterates each socket receive buffer size defined in an array, sets this size on the server's bootstrap and executes the inner loops. <b>Size</b>: 5</li>
<li>Inner Loop: Submits the configured string payload and captures the elapsed time to response. <b>Size</b>: 100</li>
</ul>
<br />
<br />
I am going to run the client side of the test from a Groovy script using the raw TCP Socket API, so the server pipeline uses a CompatibleObjectEncoder since the client cannot decode the optimized ObjectDecoder's payload. (It could, but I am sticking to a generic client).<br />
<div>
<br /></div>
<br />
<h4>
<a href="https://github.com/nickman/netty-ajax-server/blob/master/src/main/java/org/helios/netty/examples/state/InstrumentedDelimiterBasedFrameDecoder.java" target="_blank">InstrumentedDelimiterBasedFrameDecoder</a></h4>
<br />
Remember that for each logical request, server side decoders may need to be invoked several times in order to complete the task of reading the full bytestream and decoding it. When it is invoked, if the bytestream is not complete, it returns a null. Eventually, the bytestream will be complete and the decoder can complete the task and return the (non-null) decoded payload. This being the case, my theory was that the smaller the receiving socket's receive buffer size, the more times the decoder would need to be called. Or in contrast, the larger the receiving buffer size, the more likely that the full bytestream would be transmitted in one shot, reducing the number of required frame decoder calls to one.<br />
<br />
<br />
To test this theory, I made a simple extension of the DelimiterBasedFrameDecoder that counts the number of times it is invoked by incrementing a counter in an instance variable. When the decode completes, the highwater counter value is written to a ChannelLocal and the counter is reset back to zero. Upstream, the StringReporter, which determines the length of the passed string and returns it to the client, also returns the count of frame decoder invocations by reading it from the ChannelLocal and adding it to the int[] payload returned to the client. In addition, it occurred to me that the socket buffer size might not actually be set to what the client requested. (Will it really set the buffer size to 2 bytes ??!!??) So I added a read of that value and appended it to the result sent back to the client. It is useful to note that when reading or writing channel options from a live (socket based) channel, they are read from and applied to the actual socket instance. With that, the client can report:<br />
<br />
<ul>
<li>The length of the submitted string reported by the server. (Not useful except as a validating assertion)</li>
<li>The number of times the frame decoder was called for the most recently submitted string.</li>
<li>The elapsed time of the request execution.</li>
<li>The actual server socket receive buffer size.</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD0JN7JuMb1GNz6o2yRQ8Qtd6VOPaTv1hPMH2b3k3XfqjNQZan1UcKplQwX3UiffynyhonbxHPoVrzehagtnL1DNT3hW4B4l8w6JcR6boTQKLzzjcwOtDTogg7qk92a06sbTmL/s1600/RetryingDecoder.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="274" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgD0JN7JuMb1GNz6o2yRQ8Qtd6VOPaTv1hPMH2b3k3XfqjNQZan1UcKplQwX3UiffynyhonbxHPoVrzehagtnL1DNT3hW4B4l8w6JcR6boTQKLzzjcwOtDTogg7qk92a06sbTmL/s640/RetryingDecoder.png" width="640" /></a></div>
<br />
<br />
The server pipeline uses a <b>CompatibleObjectEncoder </b>since we're sending back an <b>int[]</b> and since the client is a simple socket client, not a Netty based client. It <i>is </i>a bit of overkill though. Whenever one is sending primitives, or arrays of primitives, it is possible to bypass modular encoding and simply write directly to a ChannelBuffer. Here's how we could remove the <b>CompatibleObjectEncoder</b> from the pipeline and rewrite the StringReporter's write of the int[].<br />
<br />
<pre class="brush:java">
// =====================================================================
// Uncomment next section to ditch the CompatibleObjectEncoder from the pipeline
// =====================================================================
ChannelBuffer intBuffer = ChannelBuffers.buffer(12);
for(int i = 0; i < 3; i++) {
intBuffer.writeInt(ret[i]);
}
ctx.sendDownstream(new DownstreamMessageEvent(e.getChannel(), Channels.future(e.getChannel()), intBuffer, e.getChannel().getRemoteAddress()));
// =====================================================================
// =====================================================================
// Comment the next line to ditch the CompatibleObjectEncoder from the pipeline
// =====================================================================
// ctx.sendDownstream(new DownstreamMessageEvent(e.getChannel(), Channels.future(e.getChannel()), ret, e.getChannel().getRemoteAddress()));
</pre>
<br />
Having done that, the groovy script client (coming up next) which normally uses an <a href="http://docs.oracle.com/javase/6/docs/api/java/io/ObjectInputStream.html" target="_blank">ObjectInputStream </a>to read objects from the SocketInputStream would be replaced with a <a href="http://docs.oracle.com/javase/6/docs/api/java/io/DataInputStream.html" target="_blank">DataInputStream </a>and the code would look like this:<br />
<br />
<pre class="brush:java"> /*
* The prior code
if(ois==null) {
ois = new ObjectInputStream(socket.getInputStream());
}
result = ois.readObject();
*/
if(ois==null) {
ois = new DataInputStream(socket.getInputStream());
}
result = new int[3];
for(index in 0..2) {
result[index] = ois.readInt();
}
</pre>
<br />
<br />
<h4>
Groovy Client Script and Network Kernel Tuning </h4>
<br />
<a href="https://github.com/nickman/netty-ajax-server/blob/master/src/test/resources/groovy/SocketBufferSizeClient.groovy" target="_blank">This</a> is the groovy test client I used to test the server. I admit that this script is a bit messy, but it does the job. I won't go over it in too much detail, excepting drawing your attention to the following:<br />
<br />
<br />
<ul>
<li>The client sends the same string each time, since the varying value being adjusted to measure the effect is the server's socket receive buffer size. </li>
<li>The string content is fairly arbitrary and generated by appending all the JVM's system property key/value pairs a couple of times. Once generated, the same string is used in each call. </li>
<li>The socket receive buffer sizes tested are defined in this int array: <span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"><b>[1144, 2288, 4576, 9152, 18304, 36608, 73216, 146432, 292864, 585728]</b></span> </li>
</ul>
<br />
<br />
Since the script assertively validates that the socket receive buffer size is actually what the client set it to, I found that when I requested a buffer size of 146,432, I got an assertion failure
with the actual reported buffer size being only 131,071. As it happens, my Linux network kernel configuration had limited the maximum size of of socket receive buffers to 131,071.
In order to raise this limit, running in a sudo'ed shell, I did this:<br />
<br />
<br />
<ul>
<li><b>sysctl -a | grep mem > ~/sysctl-mem.txt</b> (Saved the current settings for rollback purposes) </li>
<li><b>sysctl -w net.core.rmem_max=8388608</b> (Set the maximum buffer size to 8.3 MB) </li>
<li><b>sysctl net.core.rmem_max</b> (Verified the value was set. Output was "<b>net.core.rmem_max = 8388608</b>") </li>
<li><b>sysctl -w net.ipv4.route.flush=1</b> (Flushed the settings so they took effect immediately) </li>
</ul>
<br />
<br />
<a href="http://wwwx.cs.unc.edu/~sparkst/howto/network_tuning.php" target="_blank">This</a> is a useful reference for tuning the network kernel in Linux. <a href="http://fasterdata.es.net/host-tuning/" target="_blank">Here</a> is a guide for TCP tuning on different operating systems. I should also note that the string I generated as the sample test payload was 8,088 bytes before being delimited with a single "|".<br />
<br />
Here are the charted results of the test, with the socket receive buffer size on the x-axis and the average elapsed time of each request in ms. on the first y-axis. The second y-axis represents the average number of times the Frame Decoder was called for each request.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWHOKFv9haxOdJemMCKv3Fgj-Q8AoSTJMLxKHKK8VKrRbsptBj_e4x9RJQTcis2jNNmCoDk1seMBtaE2ltxWEhH0lrWql8M8kbcvu8iGRb7LUzIBX9_zd78dyiKGOFtQbNdutN/s1600/SocketTimings.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="262" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWHOKFv9haxOdJemMCKv3Fgj-Q8AoSTJMLxKHKK8VKrRbsptBj_e4x9RJQTcis2jNNmCoDk1seMBtaE2ltxWEhH0lrWql8M8kbcvu8iGRb7LUzIBX9_zd78dyiKGOFtQbNdutN/s640/SocketTimings.png" width="640" /></a></div>
<br />
<br />
The results are pretty much what I expected and I think quite intuitive:<br />
<br />
<ul>
<li>The average elapsed time of the request executions is inversely proportional to the server child socket's receive buffer size until the buffer size is large enough to contain the whole request payload.</li>
<li>The average number of frame decoder invocations per request execution is inversely proportional to the server child socket's receive buffer size until the buffer size is large enough to contain the whole request payload.</li>
</ul>
<div>
One might be tempted to simply set the socket buffer size to the max, but keep in mind that on a busy system, many sockets may be allocated and even though the buffer memory does not come out of your Java heap space, it all adds up in your native memory and could be quite wasteful. Furthermore, it is quite obvious from the chart that for a predictable payload size, there is a sweet spot for buffer sizes, so in concert with Netty's <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ReceiveBufferSizePredictor.html" target="_blank">ReceiveBufferSizePredictor</a>, it would be interesting to create an auto-tuning socket buffer sizing algorithm, but that's for another day.</div>
<div>
<br /></div>
<div>
The spreadsheet I used for this test is <a href="https://github.com/nickman/netty-ajax-server/blob/master/src/test/resources/socketTimings2.ods" target="_blank">here</a>, and is simple to use. The groovy test script generates a CSV file with all the tabulated results, so you can just import it into OpenOffice (et. al.) and then paste the numbers in.<br />
<br />
That's it for entry 1.5. I hope it has been helpful, if not outright entertaining. I am delighted to get any feedback, and genuinely pleased to answer any questions. As promised, in entry 2.0, I will be discussing implementing Ajax push in Netty (with no further decimal releases in the 1 range).<br />
<br />
<span style="color: blue;"><b>Part 1 </b></span>of this series: <a href="http://seeallhearall.blogspot.com/2012/05/netty-tutorial-part-1-introduction-to.html" target="_blank">Netty Tutorial Part 1: Introduction to Netty</a><br />
<br />
<br class="Apple-interchange-newline" /></div>
</div>
nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com19tag:blogger.com,1999:blog-11251261.post-91856228558722740232012-05-27T16:57:00.002-04:002012-06-18T15:45:44.128-04:00Netty Tutorial Part 1: Introduction to Netty<h3>
Netty Tutorial, Part 1: Introduction to Netty</h3>
<b><span style="color: blue;"><br /></span></b><br />
<b><span style="color: blue;">Update: </span></b> Part 1.5 Has Been Published: <a href="http://seeallhearall.blogspot.com/2012/06/netty-tutorial-part-15-on-channel.html" target="_blank">Netty Tutorial Part 1.5: On Channel Handlers and Channel Options</a><br />
<br />
From the <a href="http://netty.io/" target="_blank">Netty</a> web site:<br />
<br />
<b><i>"Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server."</i></b><br />
<br />
<br />
<br />
<br />
Netty is a Java library and API primarilly aimed at writing highly concurrent networked and networking applications and services. One aspect of Netty you may find different from standard Java APIs is that it is predominantly an <a href="http://en.wikipedia.org/wiki/Asynchronous_I/O" target="_blank">asynchronous</a> API. That term implies differnet things to different people and may overlap with the terms non-blocking and <a href="http://en.wikipedia.org/wiki/Event-driven_programming" target="_blank">event-driven</a>. Regardless, if you have never used an asynchronous API before, it takes a little bit of a mind shift to implement Netty if you are accustomed to writing linear software. Here's how I would boil it down.<br />
You build a Netty stack and start it. Issuing requests is easy and much the same as it is in any Java API. The mind shift comes in processing responses because there are none. Almost <i>every single </i>method invocation of substance is asynchronous, which means that there is no return value and invocation is usually instantaneous. The results (if there are any) will be delivered back in another thread. This is the fundamental difference between a standard API and an asynchronous one. Consider a client API that supplies a method to acquire the number of widgets from the server.<br />
<br />
<h4>
A Standard API</h4>
<pre class="brush:java"> public int getWidgetCount();</pre>
When a thread calls <b>getWidgetCount()</b>, some period of time will elapse and an <b>int</b> will be returned.<br />
<br />
<h4>
Asynchronous API</h4>
<pre class="brush: java"> public WidgetCountListener myListener = new WidgetCountListener() {
public void onWidgetCount(int widgetCount) {
...... do your thing with the widget count
}
};</pre>
<br />
In my fabricated asynchronous version of the same API, the <b>getWidgetCount</b> call does not return anything and could conceivably execute instantly. However, it accepts a response handler as an argument and that listener will be called back when the widget count has been acquired and the listener can then do whatever is usefully defined with that result.<br />
It might seem the additional layer[s] of complexity are unwarranted, but this is a crucial aspect of high performance applications and services written with Netty and the like. Your client need not waste resources having threads stuck in waiting or timed waiting mode, waiting for the server to respond. They can be notified when the result is available. For one thread, issuing one call, this may seem overkill, but consider hundreds of threads executing this method millions of times. Moreover, a fundamental benefit of NIO is that <b><a href="http://docs.oracle.com/javase/6/docs/api/java/nio/channels/Selector.html" target="_blank">Selectors</a> </b>can be used to delegate the notification of events we are interested in to the underlying operating system and at the root of it all, to the hardware we're running on. Being informed through callbacks from the OS that 16 bytes have been successfully written out across the network to a server, or that 14 bytes were just read in from the same is obviously a very low level and granular way to work, but through a chain of abstractions, a developer can implement the Java NIO and Netty APIs to enable handling things at much less granular and abstracted level.<br />
In this introduction, I want to start with some basic concepts and name some of the core building blocks and then slide into some actual code examples.<br />
<br />
<h3>
How Does All That Work ?</h3>
<br />
The basic currency of Netty is a <a href="http://netty.io/docs/stable/api/org/jboss/netty/buffer/ChannelBuffer.html" target="_blank">ChannelBuffer</a>. From the Netty <a href="http://netty.io/docs/stable/api/" target="_blank">JavaDocs</a>:<br />
<blockquote class="tr_bq">
A random and sequential accessible sequence of zero or more bytes (octets). This interface provides an abstract view for one or more primitive byte arrays (<code>byte[]</code>) and <a href="http://java.sun.com/javase/6/docs/api/java/nio/ByteBuffer.html?is-external=true" title="class or interface in java.nio">NIO buffers</a>.</blockquote>
That's how data is passed around in Netty. If your application only deals with byte arrays and byte buffers, you're in luck ! However, if you need to take a higher order data representation, such as a Java object, and send it to a remote server, and then receive another object back again, those byte collections need to be converted. Or, if you need to issue a request to an HTTP server, your request might start life as a simple string in the form of a URL, but then it needs to be wrapped into a request that the HTTP server will understand and then decomposed into some raw bytes and sent off across the network. The HTTP server needs to accept the bytes that are sent and compose them back into an HTTP request that it can interpret and fulfil. Once fulfilled, the reverse must occur where the response (perhaps the response is a JPEG or a JavaScript file) must be wrapped in an HTTP response, converted into bytes and sent back to the calling client.<br />
The basic abstraction of the network through which these bytes are transported is the Netty <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/Channel.html" target="_blank">Channel</a>. Once more to the Netty JavaDocs:<br />
<blockquote class="tr_bq">
A <a href="http://dictionary.reference.com/browse/nexus?s=t" target="_blank">nexus</a> to a network socket or a component which is capable of I/O operations such as read, write, connect, and bind.</blockquote>
Nexus is an apt name to describe a channel: (from dictionary.com)<br />
<blockquote class="tr_bq">
1. <span id="hotword" style="color: #333333; cursor: default;">a</span> <span id="hotword" style="color: #333333; cursor: default;">means</span> <span id="hotword" style="color: #333333; cursor: default;">of</span> <span id="hotword" style="color: #333333; cursor: default;">connection;</span> <span id="hotword" style="color: #333333; cursor: default;">tie;</span> <span id="hotword" style="color: #333333; cursor: default;">link</span><br />
<span id="hotword" style="color: #333333; cursor: default;">2. </span><span id="hotword" style="color: #333333; cursor: default;">a</span> <span id="hotword" style="color: #333333; cursor: default;">connected</span> <span id="hotword" style="color: #333333; cursor: default;">series</span> <span id="hotword">or</span> <span id="hotword">group</span><br />
<span id="hotword">3. </span><span id="hotword">the</span> <span id="hotword" style="color: #333333; cursor: default;">core</span> <span id="hotword">or</span> <span id="hotword">center,</span> <span id="hotword">as</span> <span id="hotword" style="color: #333333; cursor: default;">of</span> <span id="hotword" style="color: #333333; cursor: default;">a</span> <span id="hotword" style="color: #333333; cursor: default;">matter</span> <span id="hotword" style="color: #333333; cursor: default;">or</span> <span id="hotword" style="color: #333333; cursor: default;">situation</span></blockquote>
A channel is the externally exposed API through which you interact with Netty. A more grounded way of saying it, a Channel is an abstracted representation of a socket. But.... it's not necessarily a socket, it could be a file, or something even more abstract, so <b>Nexus</b> is a good way to describe it. Suffice it so say, a Channel provides the interface to <i>connect<b> </b></i>and <i>write</i> to the destination represented by the Channel. No read ? you might ask ? Nope. Remember, it's like the asynchronous <b>getWidgetCount</b> method mentioned above. There's no return value.<br />
All methods on a Channel fall into two categories:<br />
<ol>
<li>A simple attribute method that (synchronously) provides information about the channel itself.</li>
<li>I/O operations like <b>bind</b>, <b>disconnect</b>, or <b>write</b>.</li>
</ol>
All methods in category #2 are asynchronous* and they all return a <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelFuture.html" target="_blank">ChannelFuture</a> which is a <i>deferred result</i>, meaning that it is a container for a result that is not known yet, but through which the result will be delivered when it is available. It's a bit like our contrived <b>WidgetCountListener</b>, except that you register your listener with the ChannelFuture instead of passing the listener with the original invocation. (Makes for a cleaner API, no ?) The interface that describes the listener you can implement is the <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelFutureListener.html" target="_blank">ChannelFutureListener</a> which has one method, called when the operation is complete: <b><span style="font-family: 'Courier New', Courier, monospace;">public void operationComplete(ChannelFuture future)</span></b>. When this method is called, the operation is complete, but it may not have succeeded, so the passed future can be interrogated to determine the outcome of the operation.<br />
* Not completely true. Netty is predominantly used for NIO, but it also has channel implementations for <b>OIO</b> which refers to the <b>Old IO</b> which is completely synchronous. OIO has some benefits and the Netty implementation is consistent with NIO implementation so components can be interchangeable and reusable.<br />
<br />
<h3>
Creating a Connected Channel</h3>
<br />
Channels are not created directly. They are created by a <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelFactory.html" target="_blank">ChannelFactory</a>. There are two variations for ChannelFactories, one for client channels and one for server channels. For each of those two variations, there are several implementations to account for the I/O type as well as the transport protocol:<br />
<ul>
<li><b>TCP NIO Channels</b>: <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/socket/nio/NioClientSocketChannelFactory.html" target="_blank">NioClientSocketChannelFactory</a> and <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/socket/nio/NioServerSocketChannelFactory.html" target="_blank">NioServerSocketChannelFactory</a></li>
<li><b>UDP NIO Channels</b>: <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/socket/nio/NioDatagramChannelFactory.html" title="class in org.jboss.netty.channel.socket.nio">NioDatagramChannelFactory</a></li>
<li><b>TCP OIO Channels</b>: <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/socket/oio/OioClientSocketChannelFactory.html" target="_blank">OioClientSocketChannelFactory</a> and <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/socket/oio/OioServerSocketChannelFactory.html" target="_blank">OioServerSocketChannelFactory</a></li>
<li><b>UDP OIO Channels</b>: <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/socket/oio/OioDatagramChannelFactory.html" title="class in org.jboss.netty.channel.socket.oio">OioDatagramChannelFactory</a> </li>
</ul>
UDP based channel factories are the same for clients and servers as they are considered <i>connectionless</i>. There are two additional types:<br />
<ul>
<li><b>HTTP Client</b>: <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/socket/http/HttpTunnelingClientSocketChannelFactory.html" title="class in org.jboss.netty.channel.socket.http">HttpTunnelingClientSocketChannelFactory</a>: This is a convenience factory that generates channels equipped to tunnel to a Netty server through a special <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/socket/http/HttpTunnelingServlet.html" target="_blank">Netty Servlet</a>. </li>
<li><b>Local Channels</b>: <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/local/DefaultLocalClientChannelFactory.html" title="class in org.jboss.netty.channel.local">DefaultLocalClientChannelFactory</a> and <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/local/DefaultLocalServerChannelFactory.html" target="_blank">DefaultLocalServerChannelFactory</a> are the client and server components of <b>In-VM</b> channels which behave just like the real networked channels but handle invocations within the same JVM. This allows for requests to be dispatched or handled through the same abstracted Channel interface but in some cases, a request can be handled or served locally. </li>
</ul>
I will be focusing on the TCP NIO channels in this blog, but be aware there are slight differences in creating different types of channel factories.<br />
The constructors for the TCP NIO ChannelFactories have the same signatures and while there are a few overloads, the basics are that the factory needs two thread pools, or <a href="http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Executor.html" target="_blank">Executors</a> for:<br />
<ul>
<li><b>The Boss Threads</b>: Threads provided by this pool are <b>boss</b> threads. Boss threads create and connect/bind sockets and then pass them off to the <b>worker </b>threads. In the server, there is one boss thread allocated per listening socket. In the client, there is only one <b>boss </b>thread*</li>
<li><b>The Worker Threads</b>: Worker threads perform all the asynchronous I/O. They <i>are not</i> general purpose threads and developers should take precautions not to assign unrelated tasks to threads in this pool which may cause the threads to block, be rendered unable to perform their real work which in turn may cause deadlocks and an untold number of performance issues.</li>
</ul>
* So if there is only one boss thread in the client, why does the <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/socket/nio/NioClientSocketChannelFactory.html" target="_blank">NioClientSocketChannelFactory</a> require an Executor ?<br />
<ol>
<li>The boss thread can be released when there is no work to do and is created lazily, but it may be more efficient to pool a small number of threads than create a new one when required and destroying it when idle. </li>
<li>It is possible that one might want to create several different channel factories and rather than giving each one their own boss pool, they can all share one.</li>
</ol>
NIO channel factories are the <i>only</i> type that use a <b>boss</b> pool since they are the only ones that can asynchronously connect to sockets or bind to server sockets. The others either have virtual connections (Local), only synchronously connect (OIO) or are connectionless (UDP). The <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/socket/http/HttpTunnelingClientSocketChannelFactory.html" target="_blank">HttpTunelingClientSocketChannelFactory</a> is simply a wrapper for another client socket channel factory, so it may or may not be using a boss thread but it not configured with one.<br />
<br />
Keep this in mind about ChannelFactories: in the course of conducting business with Netty, the factories will allocate resources, including the thread pools. Once you're done with a ChannelFactory, be sure to call <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelFactory.html#releaseExternalResources%28%29" target="_blank">releaseExternalResources()</a> on the factory. This will ensure that all its resources are released. <br />
<br />
In a nutshell, to send something to a listening server:<br />
<ol>
<li>Create a channel</li>
<li>Connect the channel to the remote listening socket</li>
<li>Call <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/Channel.html#write%28java.lang.Object%29" target="_blank">write(Object message)</a> on the channel.</li>
</ol>
Passing <b>Object</b> to the channel seems fairly flexible, no ? So what happens if I do this ?<br />
<br />
<pre class="brush:java;"> channel.write(new Date());</pre>
<br />
Netty will issue this exception:<br />
<br />
<div style="color: red; font-family: "Courier New",Courier,monospace;">
<b>java.lang.IllegalArgumentException: unsupported message type: class java.util.Date</b></div>
<br />
So what is supported ? <a href="http://netty.io/docs/stable/api/org/jboss/netty/buffer/ChannelBuffer.html" target="_blank">ChannelBuffer</a>s. That's it. However, Channels have a construct called Pipelines (or more specifically, <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelPipeline.html" target="_blank"><b>ChannelPipeline</b></a>s). A pipeline is a stack of interceptors that can manipulate or transform the values that are passed to them. Then, when they're done, the pass the value on to the next interceptor. These interceptors are referred to as <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelHandler.html" target="_blank"><b>ChannelHandler</b></a>s. The pipeline maintains strict ordering of the ChannelHandler instances that it contains, and typically, the <i>first</i> channel handler will accept a raw ChannelBuffer and the <i>last</i> chanel handler (referred to as the <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelSink.html" target="_blank"><b>Sink</b></a>) will discard whatever payload has been passed to it. Somewhere in the pipeline, you want to implement a handler that does something useful. The ChannelHandler itself is only a marker interface with no methods, so handlers have a lot of flexibility, but for a handler to do anything useful, it must be able to respond and/or forward <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelEvent.html" target="_blank"><b>ChannelEvent</b></a>s. (There's a lot of terminology here.....)<br />
What the heck is a ChannelEvent ? For the purposes of these two paragraphs, consider a ChannelEvent to be a package containing a ChannelBuffer, which as we already know, is the singular currency of Channels. Skip the next paragraphs if you're satisfied with this simplification.<br />
<br />
<span style="font-size: large;">Channel Events -For Real ?</span><br />
<br />
Ok, ChannelEvents are not just packets containing ChannelBuffers.<span style="font-family: Helvetica, Arial, sans-serif; font-size: 14px;">Since almost </span><i style="font-family: Helvetica, Arial, sans-serif; font-size: 14px;">everything</i><span style="font-family: Helvetica, Arial, sans-serif; font-size: 14px;"> in Netty occurs asynchronously, the driving code patterns consist of bits of code that generate events (like Connect, Disconnect and Write) and bits of code that handle those events once they've been executed by one of the ChannelFactory's thread pools. All these events in the Netty API are instances of the interface </span><b style="font-family: Helvetica, Arial, sans-serif; font-size: 14px;">ChannelEvent</b><span style="font-family: Helvetica, Arial, sans-serif; font-size: 14px;">. The javadoc for ChannelEvent has a comprehensive list of the different types of events and explanations of each. For a good example of what an event handler looks like, see the javadoc for the class </span><b style="font-family: Helvetica, Arial, sans-serif; font-size: 14px;">SimpleChannelHandler</b><span style="font-family: Helvetica, Arial, sans-serif; font-size: 14px;">. This class implements handler methods for </span><i style="font-family: Helvetica, Arial, sans-serif; font-size: 14px;">almost</i><span style="font-family: Helvetica, Arial, sans-serif; font-size: 14px;"> every type of event. There is even an event called </span><b style="font-family: Helvetica, Arial, sans-serif; font-size: 14px;">IdleStateEvent</b><span style="font-family: Helvetica, Arial, sans-serif; font-size: 14px;"> which can be fired when ....... nothing. It fires when a channel has been idle, which can be useful.</span><br />
In order to implement anything other than a simple ChannelBuffer sender or receiver, we need to add ChannelHandlers to the pipeline. Most of the time, ChannelHandlers in use are probably encoders and decoders, meaning:<br />
<ul>
<li> <b>Encoder</b>: Converts a non ChannelBuffer object into a ChannelBuffer, suitable for transmission to somewhere else. It might not encode directly to a ChannelBuffer, rather, it might do a partial conversion, implicitly relying on another handler[s] in the pipeline to complete the conversion. One way or the other, if you're not sending straight ChannelBuffers, one or more of the handlers in concert in the pipeline must convert the payload to a ChannelBuffer before it goes out the door. An example of a encoder is <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/serialization/ObjectEncoder.html" target="_blank">ObjectEncoder</a> which converts regular [serializable] java objects into ChannelBuffers containing the byte representation of the written object. </li>
</ul>
<ul>
<li> <b>Decoder</b>: The reverse of an encoder where a ChannelBuffer's contents are converted into something more useful. The counterpart of the ObjectEncoder mentioned above, is the <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/serialization/ObjectDecoder.html" target="_blank">ObjectDecoder</a> and it does exactly this.</li>
</ul>
The Netty SDK supplies various codecs (contraction of Coder/Decoder[s]) such as Google ProtoBufs, Compression, HTTP Request/Response and Base64.<br />
Not all ChannelHandlers are Encoders/Decoders (although the majority in the core API are). A ChannelHandler can be put to work doing all sorts of useful things. For example:<br />
<ul>
<li> <b>ExecutionHandler</b>: Forwards channel events to another thread pool. Intended to push events out of the Worker (I/O processing) thread pool in order to make sure it stays lively.</li>
</ul>
<ul>
<li> <b>BlockingReadHandler</b>: Allows your code to treat a Channel as though it was not asynchronous for the purposes of reading incoming data.</li>
</ul>
<ul>
<li> <b>LoggingHandler</b>:Simply logs what events are passing through the handler. A highly useful debugging tool......</li>
</ul>
So how do I get these ChannelHandlers to help me with my java.util.Date problem ? The answer is that you need to add an ObjectEncoder* to the channel's pipeline so it can translate the Date to a ChannelBuffer. Here's a visual, and I'll get to the code in a minute:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh89Dn61rGeJv8vBO-o1Fcnvlx-NbYuO_JhRerra0RREo9G6HLDqKf6ROD0YSBYUfE6QVHnsdWKPGfOQ5e1J8auCryisn8Xb8BnoQglO8nh8gEaSZOeyM_IqEqAZdN1Oj8xVyUR/s1600/SendingDate.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh89Dn61rGeJv8vBO-o1Fcnvlx-NbYuO_JhRerra0RREo9G6HLDqKf6ROD0YSBYUfE6QVHnsdWKPGfOQ5e1J8auCryisn8Xb8BnoQglO8nh8gEaSZOeyM_IqEqAZdN1Oj8xVyUR/s1600/SendingDate.png" /></a><br />
<br />
<span style="font-size: x-small;">* <b>ObjectEncoder</b> is actually an optimized Netty special. So long as you have a Netty <b>ObjectDecoder</b> on the other end, you're good to go. Otherwise, you need to use a <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/serialization/CompatibleObjectEncoder.html" target="_blank"><b>CompatibleObjectEncoder</b></a> which uses standard Java serialization.</span><br />
<span style="font-size: x-small;"><br /></span><br />
Alright, checkpoint: We need a Channel, which we get from a ChannelFactory, but we also need to define a ChannelPipeline. There's a few ways you can do this, but Netty provides a construct called a <b><a href="http://netty.io/docs/stable/api/org/jboss/netty/bootstrap/Bootstrap.html" target="_blank">Bootstrap</a> </b>that wraps all these things together quite nicely, so here's how all this stuff fits together in order to create a Netty client that can send a <b>Date</b>. This example will implement the <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/socket/nio/NioClientSocketChannelFactory.html" target="_blank"><b>NioClientSocketChannelFactory</b></a> which will create an instance of an <a href="file:///home/nwhitehead/libs/java/netty/netty-3.4.5.Final/doc/xref/org/jboss/netty/channel/socket/nio/NioClientSocketChannel.html" target="_blank"><b>NioClientSocketChannel</b></a>. It bears mentioning, though, that for the most part, the Netty public API simply returns a <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/socket/SocketChannel.html" target="_blank"><b>SocketChannel</b></a>, an abstract parent class, and you can just consider this to be a plain <b>Channel</b> as the implementations themselves exhibit identical behavior and exposed functionality. This code sample is a little more simplified that what you might see in other examples, but I doeth it for clarity.<br />
<br />
<pre class="brush: java"> Executor bossPool = Executors.newCachedThreadPool();
Executor workerPool = Executors.newCachedThreadPool();
ChannelFactory channelFactory = new NioClientSocketChannelFactory(bossPool, workerPool);
ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(
new ObjectEncoder()
}
};
Bootstrap boostrap = new ClientBootstrap(channelFactory);
boostrap.setPipelineFactory(pipelineFactory);
// Phew. Ok. We built all that. Now what ?
InetSocketAddress addressToConnectTo = new InetSocketAddress(remoteHost, remotePort);
ChannelFuture cf = bootstrap.connect(addressToConnectTo);</pre>
<br />
That's how you get a Channel..... wait up ! That's not a Channel. It's a <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelFuture.html" target="_blank">ChannelFuture</a>. What's that ? Remember that [almost] everything is asynchronous in Netty, so when you request a connection, the actual process of connecting is asynchronous. For that reason, the bootstrap returns a ChannelFuture which is a "handle" to a forthcoming completion event. The ChannelFuture provides the state of the requested operation, and assuming the connect completes successfully, will also provide the Channel. There are (as always....) several options for exactly how the calling thread can wait for the connection to complete, and the following outline of these options applies equally to <i>all</i> asynchronous invocations against channels (disconnects, writes etc.) since all these operations return ChannelFutures.<br />
<br />
<h3>
[A]Waiting on Godot (where Godot is a Channel Invocation)</h3>
<br />
Regardless of the strategy implemented to wait for a Channel operation to complete, completion itself does not necessarilly indicate success, simply that the operation concluded. The result of completion could be one of the following:<br />
<ul>
<li><b>Success !</b>: The operation completed successfully.</li>
<li><b>Failed</b>: The operation failed and the reason (a Throwable) for the failure is available from <b>ChannelFuture.getCause()</b>.</li>
<li><b>Timed Out</b>: Also a failure as far as we're concerned.</li>
<li><b>Cancelled</b>: A channel invocation can be cancelled by calling <b>ChannelFuture.cancel()</b>.</li>
</ul>
Having said that, here's how you can wait for completion:<br />
<ul>
<li><b>Await</b>: Await is a fancy way of saying <b>wait</b>, (<i>perhaps used to avoid confusing with Object.wait() ?</i>) The ChannelFuture provides a number of methods that will cause the connecting thread to wait until the connect operation is complete. Note that this does not mean the thread will wait until it <i>connects</i>, rather, it waits until the final conclusion of the call which might be a successful connect, a failed connect, a connect timeout or the connect request could be cancelled. If there are too many options here to keep you attention, skip to the next paragraph, but the asynchronous nature of these operations requires a few different ways of awaiting, but basically stated: <ul>
<li><b>Interruptibility</b>: The waiting thread may be interrupted. This is a fact of life with Threads and the core Java thread API always throws a checked exception (<b>InterruptedException</b>) whenever a thread is directed to wait (sleep, join etc.). However, the ChannelFuture gives you the option of supressing calls to <b>interrupt()</b> which basically means you don't need to put a <i>try/catch</i> block around the call which is <b>awaitUninterruptibly</b>. Otherwise, the interruptible option can be used which is <b>await</b>.</li>
<li><b>Timeout</b>: For various reasons, operations may be indefinitely blocked so it may be wise to apply a timeout to channel operations which limits the time that a thread will wait before getting a timeout exception.</li>
</ul>
</li>
<li><b>Don't Wait</b>: The connecting thread can optionally <i>fire-and-forget</i> by registering a listener on the ChannelFuture which will be fired when the operation completes.This listener is an implementation of a <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelFutureListener.html" target="_blank">ChannelFutureListener</a> and it receives a callback when the operation completes, passing a reference of the ChannelFuture in question.</li>
</ul>
In summary, this code outlines examples of the completion waiting options, using a <i>connect</i> operation as the asynchronous event we're waiting for, but applicable for most operations:<br />
<pre class="brush: java"> // Waiting on a connect. (Pick one)
ChannelFuture cf = bootstrap.connect(addressToConnectTo);
// A. wait interruptibly
cf.await();
// B. wait interruptibly with a timeout of 2000 ms.
cf.await(2000, TimeUnit.MILLISECONDS);
// C. wait uninterruptibly
cf.awaitUninterruptibly();
// D. wait uninterruptibly with a timeout of 2000 ms.
cf.awaitUninterruptibly(2000, TimeUnit.MILLISECONDS);
// E. add a ChannelFutureListener that writes the Date when the connect is complete
cf.addListener(new ChannelFutureListener(){
public void operationComplete(ChannelFuture future) throws Exception {
// chek to see if we succeeded
if(future.isSuccess()) {
Channel channel = future.getChannel();
channel.write(new Date());
// remember, the write is asynchronous too !
}
}
});
// if a wait option was selected and the connect did not fail,
// the Date can now be sent.
Channel channel = cf.getChannel();
channel.write(new Date());</pre>
Typically, in a client side application, the <b>awaitXXX</b> would be appropriate, since your client thread may not have much to do while it waits anyways. In a server code stack (perhaps some sort of proxy server), the connect event callback might be a better choice since there is more likely to be a backlog of work to be done and threads should not be sitting around waiting on I/O events to complete.<br />
The Netty documentation makes this fairly clear, but it is worth emphasising here, you want your Worker threads working, not waiting, so avoid calling any awaits in a worker thread.<br />
One last item on connects, or more to the point, triggering some useful activity when a connect completes. In the example above, one of the options registered a ChannelFutureEvent and the write was executed in the body of the listener when the connection completed successfully. What may not be obvious here is that the <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelFutureListener.html#operationComplete%28org.jboss.netty.channel.ChannelFuture%29" target="_blank">operationComplete</a> method is called-back on as a result of a broadcast <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelStateEvent.html" target="_blank">ChannelStateEvent</a>. This is an instance of a ChannelEvent which is broadcast when a Channel changes state. The ChannelFuture handled this event by invoking the defined callback, but ChannelEvents are passed to all handlers in the pipeline, so you might implement your post-connection action in one of the handlers. I point this out because much of the Netty sample code implements this slightly more non-linear style. For example, see <a href="file:///home/nwhitehead/libs/java/netty/netty-3.4.5.Final/doc/xref/org/jboss/netty/example/objectecho/ObjectEchoClient.html" target="_blank">ObjectEchoClient</a> where the client class does all the work of establishing a connected channel, but never actually writes anything. The write of the Object to be echoed actually occurs in the last handler in the pipeline (the <a href="file:///home/nwhitehead/libs/java/netty/netty-3.4.5.Final/doc/xref/org/jboss/netty/example/objectecho/ObjectEchoClientHandler.html" target="_blank">ObjectEchoClientHandler</a>) and it is executed when it receives the ChannelStateEvent indicating the Channel connected.<br />
<br />
<h3>
What about the server ?</h3>
<br />
Following up on the DateSender, we only got as far as the server socket which is listening for data being sent by client sockets. The server side is much like the <i>reverse</i> of the client side.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhE1TatHs8tUmYyraf665zySZD2UFB_HnUO6lgMlIgz50-GWRvb17jwzzxrnRgVe0xMQP6S1sVZ5fL8VXYYcZyPg2QPXmixUl6Aq2COu2Jfn6nGElB_LygeZ-kehTI6THadjU8o/s1600/ReceivingDate.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhE1TatHs8tUmYyraf665zySZD2UFB_HnUO6lgMlIgz50-GWRvb17jwzzxrnRgVe0xMQP6S1sVZ5fL8VXYYcZyPg2QPXmixUl6Aq2COu2Jfn6nGElB_LygeZ-kehTI6THadjU8o/s1600/ReceivingDate.png" /></a></div>
<br />
The server socket sends ChannelEvents loaded with the byte stream it is receiving from the client into the pipeline by passing them to the sink. The pipeline needs to be configured to do the reverse so the next ChannelHandler is an <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/serialization/ObjectDecoder.html" target="_blank">ObjectDecoder</a> which knows how to convert an array of bytes back into a Java Object, or in this case, the Date. Once the Date is decoded, it is handed to the next handler in the pipeline which is a custom handler I invented called a DateHandler. You may have noticed that although Channels have a <b>write</b> method, they don't have an equivalent <b>read</b> method. That would be more of a synchronous API where a thread would read from an OutputStream and wait until data was available. That's why the diagram above does not have an arrow back to the ServerChannel. Rather, the last handler in the pipeline receives the fully decoded message from the client and does something useful with it.<br />
Think of server channel pipelines as being your actual business service invokers. Our actual business service is the <b>DateReceiver</b> and the pipeline feeds it properly unmarshalled data. Of course, you could create a pipeline that contains just one handler that does everything, but chaining together simple components provides much more flexibility. For example, if I was sending not just a Date, but perhaps an array of 300 Dates, I might want to add <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/compression/ZlibEncoder.html" target="_blank">Compression</a>/<a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/compression/ZlibDecoder.html" target="_blank">Decompression</a> handlers into the client and server pipelines to shrink the size of the transmitted payload, and I can do this by simply modifying the pipeline configurations, rather than have to add additional code to my rhetorical single-do-everything handler. Or I might want to add some authentication so not just any old client can send Dates to my server..... Or what if I needed to shift the Date in transit to account for Time Zone changes..... Or...... it makes more sense to componentize.<br />
The Server side of the DateSender is fairly simple. We create a channel factory, a pipeline factory and put the ObjectDecoder and our custom DateHandler into the pipeline. We'll use a <a href="http://netty.io/docs/stable/api/org/jboss/netty/bootstrap/ServerBootstrap.html" target="_blank">ServerBootstrap</a> instead of a <a href="http://netty.io/docs/stable/api/org/jboss/netty/bootstrap/ClientBootstrap.html" target="_blank">ClientBootstrap</a> and rather than connecting, as we did in the client, we're going to bind to a server socket so we can listen for requests from clients.<br />
<pre class="brush:java;"> public static void bootServer() {
// More terse code to setup the server
ServerBootstrap bootstrap = new ServerBootstrap(
new NioServerSocketChannelFactory(
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool()));
// Set up the pipeline factory.
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(
new ObjectDecoder(ClassResolvers.cacheDisabled(getClass().getClassLoader())),
new DateHandler()
);
};
});
// Bind and start to accept incoming connections.
bootstrap.bind(new InetSocketAddress("0.0.0.0", 8080));
slog("Listening on 8080");
}
static class DateHandler extends SimpleChannelHandler {
public void messageReceived(ChannelHandlerContext ctx,MessageEvent e) throws Exception {
Date date = (Date)e.getMessage();
// Here's the REALLY important business service at the end of the pipeline
slog("Hey Guys ! I got a date ! [" + date + "]");
// Huh ?
super.messageReceived(ctx, e);
}
}</pre>
Starting at the bottom, the DateHandler extends <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/SimpleChannelHandler.html" target="_blank">SimpleChannelHandler</a> which tends to be a good way to go when you're not doing anything out of the ordinary because it's callbacks are strongly typed. In other words, you do not have to listen on all ChannelEvents and test the type of the event for the ones you want. In this case, the only callback that is overridden is <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/SimpleChannelHandler.html#messageReceived%28org.jboss.netty.channel.ChannelHandlerContext,%20org.jboss.netty.channel.MessageEvent%29" target="_blank">messageReceived</a> which means the handler has received some actual payload.<br />
<b>slog</b> and <b>clog</b> are simple wrappers for <i>System.out.println</i> but the former prefixes with <b>[Server]</b> and the latter with <b>[Client]</b> so's we can differentiate the output.<br />
<br />
<h4>
A Quick Note on messageReceived</h4>
<br />
As I mentioned, the <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/SimpleChannelHandler.html#messageReceived%28org.jboss.netty.channel.ChannelHandlerContext,%20org.jboss.netty.channel.MessageEvent%29" target="_blank">messageReceived</a> method is a callback when some actual payload is being received. That is, the method handles a special subtype of the ChannelEvent called a <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/MessageEvent.html" target="_blank">MessageEvent</a>. To access the payload in the MessageEvent, we simply call its <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/MessageEvent.html#getMessage%28%29" target="_blank">getMessage()</a> method which returns a <b>java.lang.Object</b> which can then be cast to the type expected. <b>Recap: </b>If there are no prior handlers before this handler to convert the payload, the type of the message will be..... a ChannelBuffer. In this case, however, the ObjectDecoder already converted the bytes in the incoming ChannelBuffer to a Date, so the type is <b>java.util.Date</b>. As for the other guy, the <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelHandlerContext.html" target="_blank">ChannelHandlerContext</a>, we'll get to that shortly.<br />
Once we issue the critical business event of logging the received date, the handler is technically done, so what's the score with that code on line 30 ? Since there <i>might</i> be additional handlers further up the pipeline, it's a good idea to always send the payload on its way once it has been handled. If there are no additional handlers, the pipeline will discard the message.<br />
The source code for <a href="https://github.com/nickman/netty-ajax-server/blob/master/src/main/java/org/helios/netty/examples/DateSender.java" target="_blank">DateSender</a> is in the GitHub repository if you want to view the whole thing, and here's what the output looks like:<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;">
<span style="font-size: x-small;"><b>[Server]:Listening on 8080<br />[Client]:DateSender Example<br />[Client]:Issuing Channel Connect...<br />[Client]:Waiting for Channel Connect...<br />[Client]:Connected. Sending Date<br />[Server]:Hey Guys ! I got a date ! [Sat May 19 14:00:58 EDT 2012]</b></span></div>
<br />
Note that this example is purely one way. We only send a Date up to the server and nothing is sent back. If it were to, the pipelines on both sides would need their counterparts,which I will discuss next.<br />
<br />
<h3>
The Return of the <span style="text-decoration: line-through;">King</span> Date</h3>
<br />
Both clients and servers will read and write in a bidirectional scenario. Consider the DateSender example. What if the server was to increment the date by some arbitrary value and return the modified date ? There another example of almost the same code as DateSender called <a href="https://github.com/nickman/netty-ajax-server/blob/master/src/main/java/org/helios/netty/examples/DateModifier.java" target="_blank">DateModifier</a> but which has a server that modifies the Date and returns it to the client. In order to do this, these modifications are required:<br />
<ol>
<li>The Server will return a Date back to the client, so its pipeline will need an ObjectEncoder.</li>
<li>The Client will receive a Date back from the Server so its pipeline will need an ObjectDecoder.</li>
<li>The Client needs an extra handler to do something with the returned Date from the Server.</li>
</ol>
Here's the code that creates the new Client pipeline:<br />
<pre class="brush:java;"> ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(
new ObjectEncoder(),
// Next 2 Lines are new
new ObjectDecoder(ClassResolvers.cacheDisabled(getClass().getClassLoader())),
new ClientDateHandler()
);
}
};</pre>
The code for the client DateHandler is very simple and it is invoked when the client receives a Date back from the server and it has been decoded:<br />
<pre class="brush:java;"> static class ClientDateHandler extends SimpleChannelHandler {
public void messageReceived(ChannelHandlerContext ctx,MessageEvent e) throws Exception {
Date date = (Date)e.getMessage();
clog("Hey Guys ! I got back a modified date ! [" + date + "]");
}
}</pre>
The server pipeline is almost the mirror of the client now:<br />
<pre class="brush:java;"> bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
return Channels.pipeline(
new ObjectDecoder(ClassResolvers.cacheDisabled(getClass().getClassLoader())),
// Next 2 Lines are new
new ObjectEncoder(),
new ServerDateHandler()
);
};
});</pre>
This is the code for the new Server DateHandler which has a few new concepts.<br />
<pre class="brush:java;"> static class ServerDateHandler extends SimpleChannelHandler {
Random random = new Random(System.nanoTime());
public void messageReceived(ChannelHandlerContext ctx,MessageEvent e) throws Exception {
Date date = (Date)e.getMessage();
// Here's the REALLY important business service at the end of the pipeline
long newTime = (date.getTime() + random.nextInt());
Date newDate = new Date(newTime);
slog("Hey Guys ! I got a date ! [" + date + "] and I modified it to [" + newDate + "]");
// Send back the reponse
Channel channel = e.getChannel();
ChannelFuture channelFuture = Channels.future(e.getChannel());
ChannelEvent responseEvent = new DownstreamMessageEvent(channel, channelFuture, newDate, channel.getRemoteAddress());
ctx.sendDownstream(responseEvent);
// But still send it upstream because there might be another handler
super.messageReceived(ctx, e);
}
}</pre>
There's some new stuff on lines 10-13 dedicated specifically to returning the modified Date to the calling client and a subtle concept.<br />
<ul>
<li>On line 10, we get a reference to the Channel from the <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/MessageEvent.html" target="_blank">MessageEvent</a>.</li>
<li>The Date is going to be written back to the client, which (at the risk of repeating myself) will be an asynchronous call, so there's always a ChannelFuture involved, so in this case, on line 11, we're going to create one using the <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/Channels.html" target="_blank">Channels</a> class, a class stuffed full of useful static methods like <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/Channels.html#future%28org.jboss.netty.channel.Channel%29" target="_blank">future</a>.</li>
<li>On line 12, a new MessageEvent is created by constructing a <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/DownstreamMessageEvent.html" target="_blank">DownstreamMessageEvent</a>. Basically, the return value is being packaged up to be sent back down the client.</li>
<li>On line 13, the new MessageEvent is sent back down to the client by calling <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelHandlerContext.html#sendDownstream%28org.jboss.netty.channel.ChannelEvent%29" target="_blank">sendDownstream</a> on the <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelHandlerContext.html" target="_blank">ChannelHandlerContext</a>.</li>
</ul>
Say what ? Ok, the ChannelHandlerContext is basically a functional reference to the pipeline. It provides access to all the handlers in the pipeline as well as to the pipeline itself, and most importantly, in this case, it provides a means of sending a message back <i>through the "same" path that the incoming message arrived on</i>. The important points here are:<br />
<ol>
<li>Keeping in mind that there may be additional handlers in the pipeline beyond the ServerDateHandler, I want the handler to respond to the client by sending the modified Date back and then forward the current payload to the next handler in the pipeline.</li>
<li>If we wrote to the Channel directly, it would start from the "top" of the pipeline and might be pushed through handlers that are not indented to be called with the return Date.</li>
</ol>
All this begs some additional details on how exactly handlers work inside of a pipeline and an explanation of <b>Upstream</b> and <b>Downstream</b>.<br />
<br />
<h3>
Upstream and Downstream</h3>
<br />
You might wonder why things don't get confused when <i>both </i>the ObjectEncoder and the ObjectDecoder are called within one pipeline invocation, after all, they're both in the pipeline, wouldn't they both get called ? They're not because handlers declare themselves as Upstream handlers, Downsteam handlers or both.Basically Downstream is when a pipeline is sending something to a remote. Upstream is when it is reading something from a remote. If the terminology is not immediately intuitive to you, think of this mnemonic sentence:<br />
<blockquote class="tr_bq">
If you want to learn something, you might <i><b>read up</b></i> on it and then <i><b>write down</b></i> some notes.</blockquote>
... anyways, for handlers to participate in a pipeline, they will directly or indirectly implement one or both of these interfaces:<br />
<ul>
<li><a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelDownstreamHandler.html" target="_blank">org.jboss.netty.channel.ChannelDownstreamHandler</a>: Invoked on all downstream calls</li>
<li><a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelUpstreamHandler.html" target="_blank">org.jboss.netty.channel.ChannelUpstreamHandler</a>: Invoked on all upstream calls</li>
</ul>
Therefore, on a downstream write, the pipeline orchestrates the payload to be passed to all channel handlers that implement <b>ChannelDownstreamHandler</b> and vice-versa. Keep in mind that a channel handler can implement both interfaces and participate in upstream <i>and</i> downstream events.<br />
<br />
<img alt="" height="463" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhY1fiTJrut68P7i41udeP7rmuL6kFv01YFZEr1JQYy8aj_b46mosC_Ag6hzt-J88XWXSfJrwkJmomIY6lK6O96yg9uHmK7zxse7dpvTHPh9ZM1reyalm5po9DEuVOFZjPKHK6b/" style="display: block; margin-left: auto; margin-right: auto;" width="467" /><br />
<br />
<span style="font-size: medium;">Notice that the notion of Upstream vs. Downstream is specific to the client and the server, and for a conversation between a client and a server, both sides will need <b>Encoder/Decoder</b> pairs defined in the correct order. </span><b><span style="font-size: medium;"> </span></b><span style="font-size: medium;">The next diagram illustrates this in the form of a simplified HTTP server serving a file to a requesting client.</span><br />
<span style="font-size: medium;"><br /></span><br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWJ2C2C_MOcYjf3Rd2KBH3Ix-dHd2MGhGXt3EsNq5SxDRusqaVYQYwiWTQud7diON6Hk9dQEcDTDDTsEpnYyM_qgwCwS_7vWOjxUouolNOGMSB0B9Z2v8mWQcTda5DFDE8NPTf/s1600/Streams.png" style="margin-left: 1em; margin-right: 1em;"><img alt="" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWJ2C2C_MOcYjf3Rd2KBH3Ix-dHd2MGhGXt3EsNq5SxDRusqaVYQYwiWTQud7diON6Hk9dQEcDTDDTsEpnYyM_qgwCwS_7vWOjxUouolNOGMSB0B9Z2v8mWQcTda5DFDE8NPTf/s1600/Streams.png" /></a><br />
<br />
<b><span style="font-size: medium;">Sequence of ChannelHandlers in the Pipeline</span></b><br />
<br />
In order to achieve the proper sequence of modifications of the payload, the pipeline keeps strict ordering of the handlers in the pipeline. Consider a pipeline that is supposed to look like this:<br />
<ol>
<li><b>JSON Encoder</b>: <ul>
<li>Receives: A <i><b>widget</b></i> object</li>
<li>Produces: A JSON string representing the <b><i>widget</i></b> object.</li>
</ul>
</li>
<li><b>String Encoder</b>:<ul>
<li>Receives: A string</li>
<li>Produces: A ChannelBuffer with the encoded string</li>
</ul>
</li>
</ol>
If the order of these encoders were accidentally reversed, the String Encoder would receive a <b><i>widget</i></b> Object when it expects a string and a class cast exception would occur.<br />
There are a few constructs that allow for specifying the order of handlers in the pipeline. The simplest is probably the static method <b>pipeline </b>in the <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/Channels.html" target="_blank">org.jboss.netty.channel.Channels</a> class which creates a pipeline placing handlers in the created pipeline in the same order they are specified in the passed array. The signature is:<br />
<br />
<a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/Channels.html#pipeline%28org.jboss.netty.channel.ChannelHandler...%29" target="_blank"><span style="font-family: 'courier new', courier;">static ChannelPipeline Channels.pipeline(ChannelHandler...handlers)</span></a><br />
<br />
In the <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelPipeline.html" target="_blank">ChannelPipeline</a> interface itself provides a number of location specifying methods to add handlers, and herein we can also assign handlers specific logical names to uniquely identify each handler in the pipeline. The name itself is purely arbitrary, but as we will see, it is sometimes necessary to be able to reference a handler directly by its name, or determine its positional notation via the assigned name. The handler names are actually mandatory and assigned automatically if you do not specify them yourself. The pipeline methods are as follows:<br />
<ul>
<li><span style="font-family: 'courier new', courier;"><a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelPipeline.html#addAfter%28java.lang.String,%20java.lang.String,%20org.jboss.netty.channel.ChannelHandler%29" target="_blank">addAfter(String baseName, String name, ChannelHandler handler)</a></span>: Adds a handler to the pipeline name <i>handler</i> and it is placed directly after the handler named <i>baseName</i>.</li>
<li><span style="font-family: 'courier new', courier;"><a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelPipeline.html#addBefore%28java.lang.String,%20java.lang.String,%20org.jboss.netty.channel.ChannelHandler%29" target="_blank">addBefore(String baseName, String name, ChannelHandler handler)</a></span>: Adds a handler to the pipeline name <i>handler</i> and it is placed directly before the handler named <i>baseName</i>.</li>
<li><span style="font-family: 'courier new', courier;"><a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelPipeline.html#addFirst%28java.lang.String,%20org.jboss.netty.channel.ChannelHandler%29" target="_blank">addFirst(String name, ChannelHandler handler)</a></span>: Adds a handler named <i>name</i> to the <i><b>start</b></i> of the pipeline (it becomes the <i>first</i> handler in the pipeline)</li>
<li><span style="font-family: 'courier new', courier;"><a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelPipeline.html#addLast%28java.lang.String,%20org.jboss.netty.channel.ChannelHandler%29" target="_blank">addLast(String name, ChannelHandler handler)</a></span>: Adds a handler named <i>name</i> to the <b><i>end</i></b> of the pipeline (it becomes the <i>last</i> handler in the pipeline)</li>
</ul>
<br />
<b><span style="font-size: medium;">Dynamically Modifying ChannelHandlers in the Pipeline</span></b><br />
<br />
Another aspect of pipelines is that they are not immutable*, so it is possible to add and remove handlers at runtime. (There will be a very salient example of this in Part 2). Netty advertises the pipeline as thread-safe for this exact purpose, the four methods above can be called at runtime in order to "fine-tune" the pipeline for a specific call or to switch state.<br />
<br />
<span style="font-size: x-small;"><b>*</b>Actually there is an immutable implementation called a <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/StaticChannelPipeline.html" target="_blank">StaticChannelPipeline</a>. Since it is immutable once created, it cannot be modified and may provide better performance since the pipeline execution does not have to be guarded against runtime changes of the handlers.</span><br />
<br />
Why might we want to suddenly change the handlers in a pipeline that we went to all the bother of creating <i>just-so</i> in the first place ? Here's a contrived example: Say you have a bunch of encoders in a client pipeline, with the last encoder being a <a href="http://netty.io/docs/stable/api/org/jboss/netty/handler/codec/compression/ZlibEncoder.html" target="_blank">ZlibEncoder</a> which will compress the encoded payload before sending it out the door. However, let's say you have determined that if the payload is less than 1024 bytes, the compression step is actually a hindrance to performance, not a benefit. This means that you want to conditionally route the payload through the compression handler. Hmmmm.... since you won't actually know the size of the payload until it has passed through the preceding handlers, you don't even have the option of creating a compression enabled pipeline and a compressionless pipeline because your payload is most of the way down the pipeline before you know which one you want. To address this, we can create a content interrogating handler that examines the size of the payload to be compressed [or not] and add or remove the compression handler accordingly. We'll add this handler after all the other handlers, but before the compression handler.<br />
<br />
At this point, you might be thinking there are several alternatives here, but I did say it was contrived, so bear with me.<br />
<br />
First, we will create the pipeline <i>with</i> the compression handler enabled, then we'll look at the <b>ConditionalCompressionHandler</b>. Here's what the pipeline might look like:<br />
<pre class="brush:java;"> public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
/// Add all the pre-amble handlers
//pipeline.addLast("Foo", new SomeHandler());
//pipeline.addLast("Bar", new SomeOtherHandler());
// etc.
pipeline.addLast("IAmTheDecider", new ConditionalCompressionHandler(1024, "MyCompressionHandler"));
pipeline.addLast("MyCompressionHandler", new ZlibEncoder());
return pipeline;
}
</pre>
<br />
<br />
The ConditionalCompressionHandler must examine the size of the ChannelBuffer it is passed and decide if the compression handler should be called or not. However, once removed, if the next payload that comes along requires compression, we need to add it back in again.<br />
<br />
<pre class="brush:java;">
public class ConditionalCompressionHandler extends SimpleChannelDownstreamHandler {
/** The minimum size of a payload to be compressed */
protected final int sizeThreshold;
/** The name of the handler to remove if the payload is smaller than specified sizeThreshold */
protected final String nameOfCompressionHandler;
/** The compression handler */
protected volatile ChannelHandler compressionHandler = null;
/**
* Creates a new ConditionalCompressionHandler
* @param sizeThreshold The minimum size of a payload to be compressed
* @param nameOfCompressionHandler The name of the handler to remove if the payload is smaller than specified sizeThreshold
*/
public ConditionalCompressionHandler(int sizeThreshold, String nameOfCompressionHandler) {
this.sizeThreshold = sizeThreshold;
this.nameOfCompressionHandler = nameOfCompressionHandler;
}
/**
* see org.jboss.netty.channel.SimpleChannelDownstreamHandler
*/
public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) {
// If the message is not a ChannelBuffer, hello ClassCastException !
ChannelBuffer cb = (ChannelBuffer)e.getMessage();
// Check to see if we already removed the handler
boolean pipelineContainsCompressor = ctx.getPipeline().getContext(nameOfCompressionHandler)!=null;
if(cb.readableBytes() < sizeThreshold) {
if(pipelineContainsCompressor) {
// The payload is too small to be compressed but the pipeline contains the compression handler
// so we need to remove it.
compressionHandler = ctx.getPipeline().remove(nameOfCompressionHandler);
}
} else {
// We want to compress the payload, let's make sure the compressor is there
if(!pipelineContainsCompressor) {
// Oops, it's not there, so lets put it in
ctx.getPipeline().addAfter(ctx.getName(), nameOfCompressionHandler , compressionHandler);
}
}
}
}
</pre>
<div>
</div>
<div>
</div>
<br />
<br />
<h4>
ChannelHandlerContext</h4>
Note that in pretty much every handler event, the handler is passed a <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelHandlerContext.html" target="_blank">ChannelHandlerContext</a>. This class is an adapter between a ChannelHandler and the ChannelPipeline that contains it, allowing a handler to interact with the pipeline, a crucial aspect of the preceding code sample. In addition, it allows a handler to inquire about other handlers and even interact with other handlers (though more indirectly). <br />
<br />
<ul>
<li>On line 26 we determine if the compression handler is installed by requesting the pipeline from the ChannelHandlerContext and then requesting the ChannelHandlerContext of the compression handler. If the compression handler is absent, the call will return null.</li>
<li>On line 31, the handler requests the pipeline and removes the compression handler from the pipeline by name.</li>
<li>On line 37, the handler requests the pipeline and adds the compression handler back into the pipeline. We also use the <a href="http://netty.io/docs/stable/api/org/jboss/netty/channel/ChannelHandlerContext.html#getName%28%29" target="_blank">getName()</a> method of the ChannelHandlerContext to get the name of the current handler.</li>
</ul>
<br />
<br />
That's it for this entry. Part 2 will demonstrate some more specific (and useful) examples by way of an Ajax server built with Netty that implements pushed events from the server to a browser. Please feel free to send any feedback or questions through the comments. Thanks !<br />
<br />
<b><span style="color: blue;">Update: </span></b> Part 1.5 Has Been Published: <a href="http://seeallhearall.blogspot.com/2012/06/netty-tutorial-part-15-on-channel.html" target="_blank">Netty Tutorial Part 1.5: On Channel Handlers and Channel Options</a>nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com51tag:blogger.com,1999:blog-11251261.post-25151451427637213602012-01-17T10:08:00.002-05:002012-01-17T15:44:19.859-05:00GroovyMX: New Remote JVM Test CasesI just completed a check in to <a href="https://github.com/nickman/GroovyMX" target="_blank">gmx </a>with a small number of critical test cases.<br />
I am attempting to make gmx a Groovy oriented but Java usable API, so for the most part, each logical test case has a Java and a Groovy version. Here's an example of a Groovy test:<br />
<br />
<blockquote class="tr_bq">
<b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> public void testRemoteClosureForMBeanCountAndDomains() throws Exception {</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> def port = 18900;</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> def gmx = null;</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> def jvmProcess = null;</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> try {</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> jvmProcess = JVMLauncher.newJVMLauncher().timeout(120000).basicPortJmx(port).start();</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> gmx = Gmx.remote(jmxUrl(port));</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> def remoteDomains = gmx.exec({ return it.getDomains();});</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> def domains = gmx.getDomains();</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> Assert.assertArrayEquals("Domain array", domains, remoteDomains);</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> def remoteMBeanCount = gmx.exec({ return it.getMBeanCount();});</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> def mbeanCount = gmx.getMBeanCount();</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> Assert.assertEquals("MBean Count", mbeanCount, remoteMBeanCount);</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> } finally {</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> if(gmx!=null) try { gmx.close(); } catch (Exception e) {}</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> if(jvmProcess!=null) try { jvmProcess.destroy(); } catch (Exception e) {} </span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> }</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> }</span></span></b></blockquote>
In a nutshell, this test:<br />
<ol>
<li>Starts a new JVM with the JMX management agent enabled.</li>
<li value="2">Retrieves the MBeanServer's MBean domains using a remote JMX call and then using a remoted closure. Verifies they're equal.</li>
<li value="2">Retrieves the MBeanServer's MBean count using a remote JMX call and then using a remoted closure. Verifies they're equal.<br />
</li>
</ol>
<br />
<br />
The latest snapshot is available here:<br />
<ul>
<li><a href="http://repository-helios.forge.cloudbees.com/snapshot/org/helios/gmx/1.0-SNAPSHOT/gmx-1.0-SNAPSHOT.jar" target="_blank">Gmx Jar</a></li>
<li><a href="http://repository-helios.forge.cloudbees.com/snapshot/org/helios/gmx/1.0-SNAPSHOT/gmx-1.0-SNAPSHOT-jar-with-dependencies.jar" target="_blank">Gmx Jar with Dependencies</a></li>
</ul>
Of course, these tests raised a bunch of issues which I am in arrears entering into Github. If you encounter any issues, please leave me some feedback.<br />
<br />
//Nicholasnickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com0tag:blogger.com,1999:blog-11251261.post-64163469873247132892012-01-13T17:14:00.000-05:002012-01-17T15:44:44.271-05:00Remoting Groovy with Generated ClosuresOn attempting to implement remote closure execution in <a href="https://github.com/nickman/GroovyMX" target="_blank">Gmx</a>, I envisioned simply generating a closure on the fly and passing it over the wire to a remote Gmx counterpart.<br />
In my mind, it looked a bit like this very simple example that would print <b><span style="font-family: "Courier New",Courier,monospace;">"Hello Jupiter"</span></b> to the standard out on the target remote server:<br />
<script src="https://gist.github.com/1606791.js">
</script>
<br />
<br />
This is the simplest version of the idea, and so serves as a good proof of concept.<br />
<br />
Gmx already has a lot of the plumbing in place, namely:<br />
<ul>
<li>The ReverseClassLoader service spins up an HTTP server to provide remote class loading to foreign MBeanServers and performs an acceptable job of locating and serving the bytecode (in the form of a byte[]) to requesting classloaders.</li>
<li>Gmx automatically detects when remoting will be required and starts the ReverseClassLoader and installs the remote counterpart (RemotableMBeanServer) on the foreign MBeanServer. (This means that some of the code in this <a href="https://gist.github.com/1572517" target="_blank">example </a>is redundant).</li>
</ul>
The major barrier I ran into was discovering that it is quite difficult to acquire the bytecode (the class representation in the form of bytes) of a closure compiled on the fly. Excuse my use of an imprecise term like "on the fly". What I mean is that if a closure undergoes a formal compilation process, such as using <a href="http://groovy.codehaus.org/The+groovyc+Ant+Task" target="_blank">groovyc </a>or a <a href="http://www.jarvana.com/jarvana/view/org/codehaus/groovy/groovy/1.6.0/groovy-1.6.0-javadoc.jar%21/org/codehaus/groovy/control/CompilationUnit.html" target="_blank">CompilationUnit</a>, then the class itself is accessible from the java code source URL in the form of a stream of bytes. However, if you define an inline closure in an inline script and execute it (such as you might in <a href="http://groovy.codehaus.org/Groovy+Console" target="_blank">GroovyConsole</a>), the class appears to have a "fake" code source URL that cannot be read from. I am not that familiar with the internals of Groovy so this needs further explanation by way of an example:<br />
<script src="https://gist.github.com/1606935.js">
</script>
<br />
<br />
The output of this script, when run in GroovyConsole, is as follows:<br />
<br />
<blockquote class="tr_bq">
<div style="font-family: "Courier New",Courier,monospace;">
<span style="font-size: x-small;"><b>Class [Name:ClosureFactory$_getClosure_closure1] Bytes:1192 Interfaces: [interface org.codehaus.groovy.runtime.GeneratedClosure] </b></span></div>
<div style="font-family: "Courier New",Courier,monospace;">
<span style="font-size: x-small;"><b>Hello Venus </b></span></div>
<div style="font-family: "Courier New",Courier,monospace;">
<span style="font-size: x-small;"><b>Exception:/groovy/shell (The system cannot find the file specified) </b></span></div>
<div style="font-family: "Courier New",Courier,monospace;">
<span style="font-size: x-small;"><b>Class [Name:ConsoleScript5$_run_closure1] Bytes:[] Interfaces: [interface org.codehaus.groovy.runtime.GeneratedClosure] </b></span></div>
<div style="font-family: "Courier New",Courier,monospace;">
<b><span style="font-size: x-small;">Hello Jupiter</span> </b></div>
</blockquote>
<br />
<br />
In lines 4..6, the example defines a class that creates a closure and returns it from a call to <i>getClosure()</i>. Using the <i>CompilationUnit </i>is the equivalent of using <b>groovyc</b>.
When the byte code of the closure is read using <span style="font-size: x-small;"><b><span style="font-family: "Courier New",Courier,monospace;">clozure.getClass().getProtectionDomain().getCodeSource().getLocation().getBytes()</span></b></span>. In the first instance, this is successful.
However, when script defines an "<i>on the fly</i>" closure on line 16 (the closure works fine, as can be seen on line 26), the same method does not work. In support of this,
the bytecode for the "compiled" closure is writen to disk. The value of the URL returned by <span style="font-size: x-small;"><b><span style="font-family: "Courier New",Courier,monospace;">clozureClass.getProtectionDomain().getCodeSource().getLocation()</span></b></span> is <b>file:/home/nwhitehe/groovy/groovy-1.8.5/./</b>
and the following can be seen in that directory:<br />
<br />
<blockquote class="tr_bq">
<div style="font-family: "Courier New",Courier,monospace;">
<b><span style="font-size: x-small;">-rw-r--r-- 1 nwhitehe nwhitehe 5893 2012-01-13 11:54 ClosureFactory.class </span></b></div>
<div style="font-family: "Courier New",Courier,monospace;">
<b><span style="font-size: x-small;">-rw-r--r-- 1 nwhitehe nwhitehe 2454 2012-01-13 11:54 ClosureFactory$_getClosure_closure1.class </span></b></div>
</blockquote>
<br />
For the "<i>on the fly</i>" closure on the other hand, the script gets an exception when attempting to read the bytes from the code source URL pointing to a file <b>/groovy/shell</b> which does not exist. The bytecode disappears off into the aether.<br />
<br />
There are a few hints on this challenge in a <a href="http://jira.codehaus.org/browse/GROOVY-4583" target="_blank">Jira ticket</a> filed at the end of 2010 titled <b>Ability to get class bytes of closures at runtime, including nested closures (for remote control)</b>. <a href="http://groovy.codehaus.org/modules/remote/" target="_blank">RemoteControl</a> is a groovy package for groovy closure remoting, so this seemed like a good place to start, but the documentation [more concisely than I did above] alerts the reader to the same problem:<br />
<blockquote class="tr_bq">
<i>The remote execution mechanism works by sending the definition of the
closure class to the server. It does this by finding the corresponding <code>.class</code> file for the closure on the class path. This means that there must be a <code>.class</code>
file for the closure on the class path for the closure to be able to be
remotely executed. Closure's whose class has been generated dynamically
at runtime are currently not supported.</i></blockquote>
The Jira issue also points to a <a href="http://groovyconsole.appspot.com/script/430001" target="_blank">script by Guillaume Laforge</a> that outlines a method of finding nested closures with an advisory that this might be useful in resolving the problem, but the script uses a <i>CompilationUnit</i> to acquire the bytecode so it was not clear to me if that strategy would work.<br />
<br />
Aside from using the <b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">Class/ProtectionDomain/CodeSource/Location.getBytes</span></span></b> method to get class bytecode, another path I have used is <a href="http://www.csg.is.titech.ac.jp/%7Echiba/javassist/" target="_blank">Javassist </a>which did not work, returning a null <a href="http://www.csg.is.titech.ac.jp/%7Echiba/javassist/html/javassist/CtClass.html" target="_blank">CtClass </a>when requested from the <a href="http://www.csg.is.titech.ac.jp/%7Echiba/javassist/html/javassist/ClassPool.html" target="_blank">ClassPool</a>.<br />
<br />
The solution that ended up working was using a <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/instrument/ClassFileTransformer.html" target="_blank">ClassFileTransformer</a>. This interface is defined in the <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html" target="_blank">java.lang.instrument</a> package and instance of it can be registered with the JVM's <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/instrument/Instrumentation.html" target="_blank">Instrumentation</a> <br />
instance. It has a single method:<br />
<br />
<b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) </span></span></b><br />
<br />
Accordingly, a ClassFileTransformer can provide the bytecode of a class. For our purposes, it needs to:<br />
<ul>
<li>Be registered before the class loads or be invoked by a request to retransform the target class.</li>
<li>Filters out the class that has been targeted.</li>
</ul>
The following is a contrived example that retrieves a flat (i.e. no nested closures) closure's class byte code. The instrumentation instance is provided by a Gmx utility class called <a href="https://github.com/nickman/GroovyMX/blob/master/gmx/src/main/java/org/helios/gmx/classloading/ByteCodeRepository.java" target="_blank">ByteCodeRepository</a>. (More on that later).<br />
<br />
<script src="https://gist.github.com/1608660.js">
</script>
<br />
In short, the class file transformer captures the bytecode of the class it is configured to capture. Once the bytecode has been captured, the transformer is unregistered. In order to trigger
a call to the transformer, the instrumentation instance requests a retransform on the closure's class.
Acquiring an instrumentation can be simplified, but the underlying mechanics are complicated. The JVM's instrumentation instance is not automatically created or available. It is typically created
when the JVM is started with a <b>-javaagent</b> JVM startup option.<br />
<br />
Alternatively, it is possible to use the<a href="http://docs.oracle.com/javase/6/docs/jdk/api/attach/spec/index.html" target="_blank"> Java Attach API</a> to load a Java Agent into a running JVM which will trigger the creation of an
instrumentation instance. This step is implemented by the Gmx utility class <a href="https://github.com/nickman/GroovyMX/blob/master/gmx/src/main/java/org/helios/vm/agent/LocalAgentInstaller.java" target="_blank">LocalAgentInstaller</a>'s static method <span style="font-size: x-small;"><b><span style="font-family: "Courier New",Courier,monospace;">getInstrumentation()</span></b></span>.<br />
<br />
That's pretty much all that is required to get a reference to the instrumentation with some caveats:<br />
<ul>
<li>The Attach API is only supported in Java 1.6+</li>
<li>The Attach API is variably implemented by different JVMs. You may have trouble with IBM JVMs, for example.</li>
<li>The Attach API is contained in the JDK's tools.jar so if you're using the JRE, you need to specifically add tools.jar to the classpath.</li>
</ul>
Gmx uses a reflective wrapper around the Attach API to avoid sticky compilation sores, but the details can be seen in the eponymous classes in the <a href="https://github.com/nickman/GroovyMX/tree/master/gmx/src/main/java/org/helios/vm">org.helios.vm</a> package.<br />
<br />
The next piece of Gmx is the <a href="https://github.com/nickman/GroovyMX/blob/master/gmx/src/main/java/org/helios/gmx/classloading/ByteCodeRepository.java">ByteCodeRepository </a>class. It is a singleton and its functionality is as follows:<br />
<ul>
<li>It is a ClassFileTransformer that targets classes that implement <a href="http://www.jarvana.com/jarvana/view/org/codehaus/groovy/groovy/1.8.3/groovy-1.8.3-javadoc.jar%21/org/codehaus/groovy/runtime/GeneratedClosure.html" target="_blank">org.codehaus.groovy.runtime.GeneratedClosure</a>. These are typically the closures we're looking for. In my parlance, <i>GeneratedClosure</i> means "compiled on the fly". See <a href="https://gist.github.com/1608424" target="_blank">this gist</a> for the transformer basics.</li>
<li>It is a caching repository and cross-indexer for closure classes, bytecode and class names (binary and resource).</li>
<li>It automatically loads an instrumentation instance, so one can ignore the LocalAgentInstaller when using the ByteCodeRepository.</li>
</ul>
<br />
Capturing a closure's bytecode is now siplified to:
<br />
<script src="https://gist.github.com/1608381.js">
</script>
<br />
<br />
There's one subtlety left [that will be discussed here] in the quest to get bytecode for remoting closures and that's the rider on the Groovy Jira issue I mentioned, namely "<i>including nested closures</i>".
Consider a closure like this that prints the declared methos signatures methods for each class in an array:<br />
<br />
<blockquote class="tr_bq">
<div style="font-family: "Courier New",Courier,monospace;">
<span style="font-size: x-small;"><b>def Class[] classes = .....; </b></span></div>
<div style="font-family: "Courier New",Courier,monospace;">
<span style="font-size: x-small;"><b>classes.each() { </b></span></div>
<div style="font-family: "Courier New",Courier,monospace;">
<span style="font-size: x-small;"><b> it.getDeclaredMethods().each() { </b></span></div>
<div style="font-family: "Courier New",Courier,monospace;">
<span style="font-size: x-small;"><b> println it.toGenericString(); </b></span></div>
<div style="font-family: "Courier New",Courier,monospace;">
<span style="font-size: x-small;"><b> } </b></span></div>
<div style="font-family: "Courier New",Courier,monospace;">
<span style="font-size: x-small;"><b>} </b></span></div>
</blockquote>
<br />
That's a closure within a closure, and they're two different classes. The bytecode for the outer closure does not contain the bytecode for the inner, so the earlier examples have hidden this problem.
The class file transformer will still see the inner closure, and even though the name of the inner closure class is not as obvious (we could derive or guess it), here's why you don't need it:<br />
<ol>
<li>For the pruposes of remoting, so long at the ByteCodeRepository is installed, it will have captured all closures and indexed them by class name. When the remote class loader gets a serialized
closure to invoke, it will surely know and will request each class from the ReverseClassLoader, which in turn looks them up in the ByteCodeRepository. </li>
<li>The following code prints (among other things that my optimizing editor has elided) the names of the loaded generated closures in this modified example. </li>
</ol>
<br />
<script src="https://gist.github.com/1608875.js">
</script>
<br />
The output is:<br />
<br />
<blockquote class="tr_bq">
<b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">Generated Closure: ClosureFactory3$_getClosure_closure1</span></span></b><br style="font-family: "Courier New",Courier,monospace;" /><b><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">Generated Closure: ClosureFactory3$_getClosure_closure1_closure2</span></span></b></blockquote>
All this nonsense was dedicated to getting <i>all </i>the bytecode necessary to remote closures, which I have not addressed at all, but I will in the next post.<br />
<br />
Cheers.<br />
<br />
<br />
<br />nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com0tag:blogger.com,1999:blog-11251261.post-17848228017827755762012-01-06T13:55:00.000-05:002012-01-17T15:44:44.267-05:00GroovyMX: A Groovy JMX Client<div class="separator" style="clear: both; text-align: center;">
<a href="https://github.com/nickman/GroovyMX/blob/master/content/img/gmx-160-X-160.png?raw=true" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://github.com/nickman/GroovyMX/blob/master/content/img/gmx-160-X-160.png?raw=true" /></a></div>
<br />
I bootstrapped a new project on <a href="https://github.com/" target="_blank">Github </a>called <a href="https://github.com/nickman/GroovyMX" target="_blank">GroovyMX</a> (or just <b>gmx</b>). It is a monitoring oriented API but you may find it useful for various things. In the spirit of <a href="http://docs.codehaus.org/display/GROOVY/Tutorial+6+-+Groovy+SQL" target="_blank">Groovy SQL</a>, I am attempting to provide a JMX client API that is rich in functionality, terse in code and that extends the natural abilities of the native Java client.<br />
<br />
This is a quick example which demonstrates how to connect to a remote MBeanServer and list the committed memory in bytes of each of the JVM's Memory Pools:<br />
<br />
<script src="https://gist.github.com/1571105.js">
</script>
<br />
The output of this script is:<br />
<br />
<blockquote class="tr_bq">
<pre><b><code>java.lang:type=MemoryPool,name=PS Eden Space: 402653184
java.lang:type=MemoryPool,name=PS Survivor Space: 16777216
java.lang:type=MemoryPool,name=Code Cache: 3407872
java.lang:type=MemoryPool,name=PS Perm Gen: 84738048
java.lang:type=MemoryPool,name=PS Old Gen: 268435456</code></b></pre>
</blockquote>
<pre><code> </code></pre>
Briefly,what this code is doing:<br />
<ol>
<li>This is usually the only import you will need.</li>
<li>The <a href="https://github.com/nickman/GroovyMX/blob/master/gmx/src/main/java/org/helios/gmx/Gmx.java" target="_blank">Gmx </a>class represents an <a href="http://docs.oracle.com/javase/1.5.0/docs/api/javax/management/MBeanServer.html" target="_blank">MBeanServer</a>, or more specifically, an <a href="http://docs.oracle.com/javase/1.5.0/docs/api/javax/management/MBeanServerConnection.html" target="_blank">MBeanServerConnection</a>. There are a few ways to acquire one, depending on the situation, but in this case, I am connecting to a remote MBeanServer using its <a href="http://docs.oracle.com/javase/1.5.0/docs/api/javax/management/remote/JMXServiceURL.html" target="_blank">JMXServiceURL</a>.</li>
<li>The <b>mbeans</b> method has several overloads. In this case, I am providing an <a href="http://docs.oracle.com/javase/1.5.0/docs/api/javax/management/ObjectName.html" target="_blank">ObjectName </a>pattern that will match to all the JVM's <a href="http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/management/MemoryPoolMXBean.html" target="_blank">MemoryPool MXBeans</a>, and a closure which executes for each ObjectName returned. </li>
<li>The closure is passed an instance of a <a href="https://github.com/nickman/GroovyMX/blob/master/gmx/src/main/java/org/helios/gmx/MetaMBean.java" target="_blank">MetaMBean </a>which is notionally a proxy that combines the ObjectName of the MBean that it represents, and a MBeanServerConnection to the MBeanServer where the MBean is registered (the Gmx). In as much as possible, MetaMBeans act just like regular Pojos (or Pogos) so the MBean attributes are accessed as simple properties and MBean operations are invoked like regular methods.</li>
<li>The MetaMBean also has various <i>local</i> properties which are also accessed as simple properties in a Pogo. An example of this is <b>objectName</b> in line 4.</li>
<li>The MemoryPool MBeans publish an attribute called <b>Usage</b> which is an instance of <a href="http://docs.oracle.com/javase/1.5.0/docs/api/javax/management/openmbean/CompositeData.html" target="_blank">CompositeData</a>. Fortunately, Groovy allows the simple reference of the composite sub-values by simple dot notation so the expression <b>it.Usage.committed</b> retrieves the nested value from the <b>Usage</b> composite structure that is keyed by the key <b>committed</b>. </li>
<li>Keen observers might wonder why <b>Usage</b> is cased that way. This is because the Groovy property specifier <i>Usage</i> is directly mapped to the MemoryPool MBean attribute <i>Usage</i>. Since it is perfectly legal for an MBean to have two separate attributes <i>Foo</i> and <i>foo</i>, the MetaMBean only honors the correctly specified case.</li>
</ol>
One of the tricky parts of making MetaMBean behave like a regular Pojo is that MBean operation invocations <i>require</i> the exact signature of the operation to be passed as an argument, which is very exacting and JMX can be quite fussy in this regard. Unlike regular Java and Groovy, there is no implicit [un/]boxing or inheritance easing done on your behalf. Consider these signature pairs:<br />
<br />
<blockquote class="tr_bq">
<div style="font-family: "Courier New",Courier,monospace;">
<b>void foo(int) void foo(Integer)</b></div>
<div style="font-family: "Courier New",Courier,monospace;">
<b>void log(CharSequence) void log(String)</b></div>
</blockquote>
<br />
If these were <a href="http://docs.oracle.com/javase/1.5.0/docs/api/javax/management/MBeanOperationInfo.html" target="_blank">MBeanOperation</a>s, they would represent two different signatures, so the tricky part is inspecting the MetaMBean invocation name and arguments and executing a multidimensional pattern match against the MBean provided <a href="http://docs.oracle.com/javase/1.5.0/docs/api/javax/management/MBeanOperationInfo.html" target="_blank">MBeanOperationInfo</a>s. <br />
<br />
Gmx is also integrated with the Java Attach API so another way of acquiring a <b>Gmx</b> instance is to specify the PID of the JVM you want to attach to. The following example illustrates a script that discovers all (Attach API compatible) JVMs on the local machine and then prints the MBeanServerID attribute for each from the <a href="http://docs.oracle.com/javase/1.5.0/docs/api/javax/management/MBeanServerDelegate.html" target="_blank">MBeanServerDelegate </a>MBean.<br />
<br />
<script src="https://gist.github.com/1571900.js">
</script>
<br />
The output of this script is:<br />
<br />
<blockquote class="tr_bq">
<div style="font-family: "Courier New",Courier,monospace;">
<b>helios104_1324051453215<br />helios104_1325860886519<br />helios104_1325876067431<br />helios104_1325782180150</b></div>
</blockquote>
<blockquote>
<br style="font-family: "Courier New",Courier,monospace;" /></blockquote>
Not <i>super</i> interesting, but I think the brevity is great.
<br />
This last example demonstrates some of the powerful optimizations of the Groovy remoting implemented by Gmx. To contrive an example, consider determining the total number of thread blocks across all threads in a JVM. Rather than retrieving every <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/management/ThreadInfo.html" target="_blank">ThreadInfo</a> from the <a href="http://docs.oracle.com/javase/6/docs/api/java/lang/management/ThreadMXBean.html" target="_blank">ThreadMXBean</a> and computing the total locally, I install the Gmx remoting on the remote MBeanServer and pass a script to perform the computation on the remote JVM and then return the result.
<br />
<script src="https://gist.github.com/1572517.js">
</script>
<br />
The raw script passing is a bit awkward,<strike> and I'm working on implementing seamless remote closure invocation (See <a href="https://github.com/nickman/GroovyMX/issues/15" target="_blank">Issue #15</a>).</strike> and native closures are now supported. See <a href="http://seeallhearall.blogspot.com/2012/01/remoting-groovy-with-generated-closures.html" target="_blank">here </a>and <a href="http://seeallhearall.blogspot.com/2012/01/groovymx-new-remote-jvm-test-cases.html" target="_blank">here</a>.<br />
<br />
There are several more features which are complete, defective, in the roadmap/documentation or just in my imagination, but if you are interested, please check out the Gmx GitHub Site's <a href="https://github.com/nickman/GroovyMX" target="_blank">Source Code</a>, <a href="https://github.com/nickman/GroovyMX/issues" target="_blank">Issues </a>and <a href="https://github.com/nickman/GroovyMX/wiki/_pages" target="_blank">Wiki</a>. I just started this recently so its not ready for a release, but you can download a snapshot from my Cloudbees snapshot repository.<br />
<br />
Snapshots:<br />
<ul>
<li><a href="http://repository-helios.forge.cloudbees.com/snapshot/org/helios/gmx/1.0-SNAPSHOT/gmx-1.0-SNAPSHOT.jar"> Standalone</a> (no dependencies)</li>
<li><a href="http://repository-helios.forge.cloudbees.com/snapshot/org/helios/gmx/1.0-SNAPSHOT/gmx-1.0-SNAPSHOT-jar-with-dependencies.jar">Full </a>(includes dependencies)</li>
</ul>
The dependencies can be viewed in the Gmx <a href="https://github.com/nickman/GroovyMX/blob/master/gmx/pom.xml" target="_blank">Maven Pom</a>. <br />
<br />
There are some additional examples in the project unit tests:<br />
<ul>
<li> <a href="https://github.com/nickman/GroovyMX/blob/master/gmx/src/test/java/org/helios/gmx/GmxTestCase.java" target="_blank">Java</a></li>
<li><a href="https://github.com/nickman/GroovyMX/blob/master/gmx/src/test/groovy/core/GmxTestCase.groovy" target="_blank">Groovy</a></li>
</ul>
If you have any feedback, please drop a comment on this blog.<br />
<br />
Cheers.<br />
<ul>
</ul>nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com0tag:blogger.com,1999:blog-11251261.post-18514947569902482922011-08-15T14:22:00.000-04:002011-08-15T14:22:38.948-04:00Java 7 for .Net - IKVM Not SlouchingI've always been impressed with <a href="http://www.ikvm.net/">IKVM</a>, a compiler that converts Java Byte Code into .NET CLR byte code in the form of assemblies. It is astonishing how much core (and a lot of non-core) Java re-compiles into .Net with relatively little effort.<br />
<br />
Shortly after the release of Java 7, the IKVM team has already released a <a href="http://weblog.ikvm.net/PermaLink.aspx?guid=897f3cb2-9c9b-40c9-a9d7-c72a1a6d44a7">development snapshot</a> of IKVM supporting it. <br />
<br />
<blockquote><i><b>After a massive amount of work, we finally have a new development snapshot based on OpenJDK 7 b147. There is still a lot of work to do to implement all the new functionality, but at least all the OpenJDK code has now been integrated.</b></i></blockquote>Nice work guys.<br />
<br />
nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com0tag:blogger.com,1999:blog-11251261.post-51055479258992756052011-07-12T15:29:00.000-04:002011-07-12T15:29:21.707-04:00JBoss AS7 Wicked Fast Startup, But Has Serious Deal Breaker ?The new <a href="http://www.jboss.org/as7.html">JBoss AS7 Release</a> definitely has a blazingly fast startup. When I ran it for the first time: <br />
<br />
<b><span style="font-family: "Courier New",Courier,monospace;">14:26:24,770 INFO [org.jboss.as] (Controller Boot Thread) JBoss AS 7.0.0.Final "Lightning" started in <span style="color: red;">1489ms</span></span></b><br />
<br />
And not for nothing, it has a wicked fast shutdown too !<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;"><b>14:39:03,703 INFO [org.jboss.as] JBoss AS 7.0.0.Final "Lightning" stopped in 7ms</b></div><br />
A colleague commented:<br />
<br />
<!--[if gte mso 9]><xml> <w:WordDocument> <w:View>Normal</w:View> <w:Zoom>0</w:Zoom> <w:TrackMoves/> <w:TrackFormatting/> <w:PunctuationKerning/> <w:ValidateAgainstSchemas/> <w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid> <w:IgnoreMixedContent>false</w:IgnoreMixedContent> <w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText> <w:DoNotPromoteQF/> <w:LidThemeOther>EN-US</w:LidThemeOther> <w:LidThemeAsian>X-NONE</w:LidThemeAsian> <w:LidThemeComplexScript>X-NONE</w:LidThemeComplexScript> <w:Compatibility> <w:BreakWrappedTables/> <w:SnapToGridInCell/> <w:WrapTextWithPunct/> <w:UseAsianBreakRules/> <w:DontGrowAutofit/> <w:SplitPgBreakAndParaMark/> <w:DontVertAlignCellWithSp/> <w:DontBreakConstrainedForcedTables/> <w:DontVertAlignInTxbx/> <w:Word11KerningPairs/> <w:CachedColBalance/> </w:Compatibility> <w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel> <m:mathPr> <m:mathFont m:val="Cambria Math"/> <m:brkBin m:val="before"/> <m:brkBinSub m:val="--"/> <m:smallFrac m:val="off"/> <m:dispDef/> <m:lMargin m:val="0"/> <m:rMargin m:val="0"/> <m:defJc m:val="centerGroup"/> <m:wrapIndent m:val="1440"/> <m:intLim m:val="subSup"/> <m:naryLim m:val="undOvr"/> </m:mathPr></w:WordDocument> </xml><![endif]--><!--[if gte mso 9]><xml> <w:LatentStyles DefLockedState="false" DefUnhideWhenUsed="true"
DefSemiHidden="true" DefQFormat="false" DefPriority="99"
LatentStyleCount="267"> <w:LsdException Locked="false" Priority="0" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Normal"/> <w:LsdException Locked="false" Priority="9" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="heading 1"/> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 2"/> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 3"/> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 4"/> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 5"/> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 6"/> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 7"/> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 8"/> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 9"/> <w:LsdException Locked="false" Priority="39" Name="toc 1"/> <w:LsdException Locked="false" Priority="39" Name="toc 2"/> <w:LsdException Locked="false" Priority="39" Name="toc 3"/> <w:LsdException Locked="false" Priority="39" Name="toc 4"/> <w:LsdException Locked="false" Priority="39" Name="toc 5"/> <w:LsdException Locked="false" Priority="39" Name="toc 6"/> <w:LsdException Locked="false" Priority="39" Name="toc 7"/> <w:LsdException Locked="false" Priority="39" Name="toc 8"/> <w:LsdException Locked="false" Priority="39" Name="toc 9"/> <w:LsdException Locked="false" Priority="35" QFormat="true" Name="caption"/> <w:LsdException Locked="false" Priority="10" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Title"/> <w:LsdException Locked="false" Priority="1" Name="Default Paragraph Font"/> <w:LsdException Locked="false" Priority="11" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Subtitle"/> <w:LsdException Locked="false" Priority="22" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Strong"/> <w:LsdException Locked="false" Priority="20" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Emphasis"/> <w:LsdException Locked="false" Priority="59" SemiHidden="false"
UnhideWhenUsed="false" Name="Table Grid"/> <w:LsdException Locked="false" UnhideWhenUsed="false" Name="Placeholder Text"/> <w:LsdException Locked="false" Priority="1" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="No Spacing"/> <w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading"/> <w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List"/> <w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid"/> <w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1"/> <w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2"/> <w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1"/> <w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2"/> <w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1"/> <w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2"/> <w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3"/> <w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List"/> <w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading"/> <w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List"/> <w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid"/> <w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 1"/> <w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 1"/> <w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 1"/> <w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 1"/> <w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 1"/> <w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 1"/> <w:LsdException Locked="false" UnhideWhenUsed="false" Name="Revision"/> <w:LsdException Locked="false" Priority="34" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="List Paragraph"/> <w:LsdException Locked="false" Priority="29" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Quote"/> <w:LsdException Locked="false" Priority="30" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Intense Quote"/> <w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 1"/> <w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 1"/> <w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 1"/> <w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 1"/> <w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 1"/> <w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 1"/> <w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 1"/> <w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 1"/> <w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 2"/> <w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 2"/> <w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 2"/> <w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 2"/> <w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 2"/> <w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 2"/> <w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 2"/> <w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 2"/> <w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 2"/> <w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 2"/> <w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 2"/> <w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 2"/> <w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 2"/> <w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 2"/> <w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 3"/> <w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 3"/> <w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 3"/> <w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 3"/> <w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 3"/> <w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 3"/> <w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 3"/> <w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 3"/> <w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 3"/> <w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 3"/> <w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 3"/> <w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 3"/> <w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 3"/> <w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 3"/> <w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 4"/> <w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 4"/> <w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 4"/> <w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 4"/> <w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 4"/> <w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 4"/> <w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 4"/> <w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 4"/> <w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 4"/> <w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 4"/> <w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 4"/> <w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 4"/> <w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 4"/> <w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 4"/> <w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 5"/> <w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 5"/> <w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 5"/> <w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 5"/> <w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 5"/> <w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 5"/> <w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 5"/> <w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 5"/> <w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 5"/> <w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 5"/> <w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 5"/> <w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 5"/> <w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 5"/> <w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 5"/> <w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 6"/> <w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 6"/> <w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 6"/> <w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 6"/> <w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 6"/> <w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 6"/> <w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 6"/> <w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 6"/> <w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 6"/> <w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 6"/> <w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 6"/> <w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 6"/> <w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 6"/> <w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 6"/> <w:LsdException Locked="false" Priority="19" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Subtle Emphasis"/> <w:LsdException Locked="false" Priority="21" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Intense Emphasis"/> <w:LsdException Locked="false" Priority="31" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Subtle Reference"/> <w:LsdException Locked="false" Priority="32" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Intense Reference"/> <w:LsdException Locked="false" Priority="33" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Book Title"/> <w:LsdException Locked="false" Priority="37" Name="Bibliography"/> <w:LsdException Locked="false" Priority="39" QFormat="true" Name="TOC Heading"/> </w:LatentStyles> </xml><![endif]--><!--[if gte mso 10]> <style>
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:"Table Normal";
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-qformat:yes;
mso-style-parent:"";
mso-padding-alt:0in 5.4pt 0in 5.4pt;
mso-para-margin:0in;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-fareast-font-family:"Times New Roman";
mso-fareast-theme-font:minor-fareast;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;}
</style> <![endif]--> <br />
<div class="MsoNormal"><i><span style="mso-fareast-font-family: "Times New Roman";">"Yes, I've already been heckling the Tomcat guys, since they staked all their arguments on startup times. :)"</span></i></div><div class="MsoNormal"><br />
</div><div class="MsoNormal"><span>So that's all very exciting (I mean the whole AS7) but I have to say that the one deal breaker is the absence of <b>JMX-Console</b>. (Note: I might be wrong here, but it sure doesn't look like its in there) For those not in the know, this is a very spartan, but genius and </span><i><span>super functional</span></i><span> admin and development console for JBoss AS. I always loved the darn thing. It cuts straight to the chase giving you the info and operation invocation links you need without any of the fluff other application servers seem to insist on putting in.</span></div><div class="MsoNormal"><br />
</div><div class="MsoNormal"><span>The <b>JNDIViewer</b> alone was incredibly useful and if more widely used would preempt a bunch a problems because developers can actually <i>see what's bound in JNDI</i> ! What a concept ! </span></div><div class="MsoNormal"><span><br />
</span></div><div class="MsoNormal"><span>So has JMX been eschewed completely ? I seriously doubt it. Uh... wait... what is that.... </span></div><div class="MsoNormal"><span>I mean, I seriously hope not, that's what it is.</span></div><div class="MsoNormal"><span><br />
</span></div><div class="MsoNormal"><span>At any rate, it seems the platform MBeanServer has a few bits and pieces registered in it, and I suspect (or is it hope ?) there is more that is optionally configurable.</span></div><div class="MsoNormal"><span></span></div><div class="MsoNormal"><span><br />
</span></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmtMBE-g5SQTZliJkjqlysjjiMfVDwzRTdpKnh7L7Sf4bIZJMc09es-Zix61vDrYmFccymyEQPSpPAs_USacBG5a8i8MeRGqJMfszEoVx5epQPuiUkXZfMfwnNBytqG4kSU1vl/s1600/jbossjmx7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmtMBE-g5SQTZliJkjqlysjjiMfVDwzRTdpKnh7L7Sf4bIZJMc09es-Zix61vDrYmFccymyEQPSpPAs_USacBG5a8i8MeRGqJMfszEoVx5epQPuiUkXZfMfwnNBytqG4kSU1vl/s1600/jbossjmx7.png" /></a></div><div class="MsoNormal"><span><br />
</span></div><div class="MsoNormal"><br />
</div><div class="MsoNormal"><br />
</div>nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com1tag:blogger.com,1999:blog-11251261.post-63107923684971106732011-05-19T16:29:00.000-04:002011-05-19T16:29:58.771-04:00Graphite Server Virtual Box ApplianceI have made some additional improvements to my automated <a href="http://graphite.wikidot.com/start">Graphite</a> installer. Last time I <a href="http://seeallhearall.blogspot.com/2011/05/graphite-great-app-laborious-install.html">posted</a> the install script, it was admittedly missing several things, but the new one goes from <b style="font-family: "Courier New",Courier,monospace;">./install.sh</b> to the Graphite web console in one go.<br />
<br />
At any rate, the new installer has significantly eased the process of creating VM appliances for Graphite which I have made available for download if you are interested in taking Graphite for a test drive, or you need a pre-built development environment. Since the VMs, as configured, are resource limited, I am not sure if they are suitable for any production use, but if you bump up the CPUs, the memory and modify the setup to use some fast native (or SAN) disks for the Whisper storage, I imagine they would be pretty decent.<br />
<br />
<ul><li><a href="http://heliosapm.com/helios/graphite/Graphite32.0.9.8.tar">Ubuntu Desktop 10.10 32 bit, Graphite 0.9.8</a> <span style="font-size: xx-small;"><i>(928,977,606 bytes)</i></span></li>
<li><a href="http://heliosapm.com/helios/graphite/Graphite64.0.9.8.tar">Ubuntu Desktop 10.10 64 bit, Graphite 0.9.8</a> <span style="font-size: xx-small;"><i>(869,840,705 bytes)</i></span></li>
</ul>Here's some details you should know: <br />
<br />
<ul><li>Created with Virtual Box 4.0.6</li>
<li>The OS user is <b>helios</b>. The password is <b>helios</b>.</li>
<li>The Graphite admin user is <b>helios</b>. The password is <b>helios</b>. Now that I think about it, the user's email address is <b>graphite@heliosdev.org</b> so you might want to review my summary below on how you can change this.</li>
<li>Once you start the VM, the Graphite Console is available at <i><b>http://localhost</b></i> and remotely at whatever IP you designate. (Out of the box, the NIC is configured for bridging).</li>
<li>The Graphite install script and supporting files are in <b>/home/helios/graphite</b>.</li>
<li>The <b>carbon</b> listener is started by <i><b>/etc/init.d/carbon</b></i> which runs the process as user <b>www-data</b> and I recommend you stick to using it. If you start carbon directly with <b>sudo</b>, the process will run as root and may create files that are subsequently not readable by <b>www-data</b>.</li>
<li>The carbon [linetext] listener listens on port 2003.</li>
</ul><br />
<u><b>Changing the Graphite Admin User</b></u><br />
<br />
The user accounts are stored in a <b>sqlite</b> database/file <b>/opt/graphite/storage/graphite.db</b>, specifically in a table called <b>auth_user</b>. You can run the <b>sqlitebrowser</b> GUI tool and edit the file directly (using sudo). Or, you can delete the file (using sudo) and re-initialize as follows: (using sudo)<code> </code><br />
<br />
<ol><li><code>cd /opt/graphite/webapp/graphite</code><code> </code></li>
<li><code>sudo python manage.py syncdb</code></li>
</ol>This will recreate the database and prompt you to create a new admin user.<br />
<br />
And I thank <a href="http://www.hostmonster.com/">HostMonster</a> for their nicely priced hosting that affords me the ability to serve these files.<br />
<br />
Next post perhaps I will explain:<br />
<ul><li>Why did I build this appliance using Desktop instead of Server ?</li>
<li>Where is the VMware version of this appliance ?</li>
<li>Where and when is the OpenTrace client for passing collected JMX metrics to Graphite ?</li>
</ul>nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com7tag:blogger.com,1999:blog-11251261.post-40792022364107446562011-05-14T16:22:00.002-04:002011-05-20T09:21:12.508-04:00Graphite - Great App, Laborious Install (Until now ?)<a href="http://graphite.wikidot.com/start">Graphite</a> is a great APM supporting tool. It is basically (and broadly speaking) a python based server (Linux only AFAICT) with 3 components:<br />
<ol><li><a href="http://graphite.wikidot.com/whisper">Whisper</a>: An RRD like database which is quite fast.</li>
<li><a href="http://graphite.wikidot.com/carbon">Carbon</a>: A high performance cache and network listener that listens for submitted data and stashes it in Whisper.</li>
<li>Graphite: A web application that allows you to visualize and report data historically or in real time.</li>
</ol><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4wKgZ3E492oJT_tKQLnBykdEBIf3O0tERIVToudFk-9ESFUfdtyRgUznDd6a2VEgMq4nrn1gpw_pY2e7skVr5akVuPskD5R6bOAVpECqfU_XaxVOT_5uNxsHHnV03_V_n6SZr/s1600/Graphite.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="228" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4wKgZ3E492oJT_tKQLnBykdEBIf3O0tERIVToudFk-9ESFUfdtyRgUznDd6a2VEgMq4nrn1gpw_pY2e7skVr5akVuPskD5R6bOAVpECqfU_XaxVOT_5uNxsHHnV03_V_n6SZr/s400/Graphite.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8O9Vc_0wQ03OUZfE2AG1kNdPbOwSbGn3UeESfqQkExniop4gFSTkn4w2wIRgGtibZ4kI4GP_eea5xj6hS8mIajIxuDbvTOtyr4IMggzSWI7CpNodSj89B8hpQkZ97A1sY5P86/s1600/Graphite.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><br />
</a></div>The data submission protocol and data format is very simple so wherever you collect performance data from, it's easy to deliver it to Graphite. You can optionally submit data through RabbitMQ but I have observed that the basic Text over socket is better performing since you can batch up many metrics in one go,while the Rabbit submission is one metric per message.<br />
<br />
This python language is pretty interesting. Not that I am trying to learn it, but perusing the <b>.py</b> files in the Graphite distro is edifying and for a java person, the general working bits-and-pieces of python is not hard to understand. (Now writing something useful.... different story).<br />
<br />
After the umpteenth DZone posting from some code rock-star cum superhero admonishing us all to learn multiple languages, my feeling of inadequacy led me to consider delving into python. Sadly, I chose Erlang, having heard that women in bars are the most impressed with that. I'll save that story for another day, but suffice it to say that after I recovered from that blazing flame-out, I picked Groovy. Yes, it is still Java based, but the rock-stars let it slide because it has closures and befriends both sides of the <i><b>statically-typed-versus-dynamically-typed</b></i> interstellar war. Apparently my prior list up until Groovy which was Java 1.3, Java 1.4, Java 1.5, Java 1.6 and bash (for a total of 5) was not passing muster.<br />
<br />
I have been adapting OpenTrace to optionally send collected metrics to Graphite. Naturally, since I am A.R. about low level metrics, I publish the tracer's metrics through JMX.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtpOLiszPfSRztmljCb8S105OwirUaG50Xvt4BgHVRMjlCKNylF3Vunr8V0y3MdalFdGnFSgVwC2SPdpDM2yzLQ1LYkl8Y6ZzTg1TRYzuiiV26eSIOYTid4S5Kw8AddoRTjkeQ/s1600/GraphiteJMX.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="283" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtpOLiszPfSRztmljCb8S105OwirUaG50Xvt4BgHVRMjlCKNylF3Vunr8V0y3MdalFdGnFSgVwC2SPdpDM2yzLQ1LYkl8Y6ZzTg1TRYzuiiV26eSIOYTid4S5Kw8AddoRTjkeQ/s400/GraphiteJMX.png" width="400" /></a></div><br />
The idea is the tracer accumulates metrics until <i><b>t </b></i>time elapses, or <i><b>n</b></i> metrics accumulate, and then the buffer is flushed up to Carbon. So can you see the metrics in real time ? Well yes. (Nice of you to ask). The Graphite Dev team explains:<br />
<br />
<br />
<blockquote><h1 id="toc2"><i><span style="font-size: large;">How are Graphite graphs always real-time, even when carbon hasn't had time to write it's cached data to disk yet?</span></i></h1><i>Upon receiving a rendering request, the Graphite webapp simultaneously retrieves data for the requested metrics from the disk and from carbon's cache via a simple cache query socket that carbon-cache provides. Graphite then combines these two sources of data points into a single series, which is then rendered. This ensures that graphs are always real-time, even when the data hasn't been written to disk yet.</i></blockquote><br />
<br />
<br />
I have been working on an Ubuntu 10.10 VirtualBox appliance that I can distribute because the installation of Graphite is a bit laborious, and each time I do it, I always forget one thing or another. The <a href="http://graphite.wikidot.com/installation">installation documentation</a> is decent, but there's a lot of moving parts here, and many of them differ by Linux distro, so I can't really fault the Graphite dev team in this regard. I saw something somewhere about an effort to put together Linux packages, but I can't find reference to it now.<br />
<br />
At any rate, after the 23rd consecutive time of installing Graphite (and all the requisite packages) onto my Ubuntu guest, I think I finally nailed down a script that does most of the hard work, and it is here for your productivity and reading pleasure:<br />
<br />
<u><b>Updated: Friday May 20, 2011</b></u> <br />
<br />
<script src="https://gist.github.com/982866.js?file=install.sh">
</script>nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com0tag:blogger.com,1999:blog-11251261.post-35088395204537393852011-05-09T15:14:00.000-04:002011-05-09T15:14:00.232-04:00Spin vs. Sleep vs. JoinAs I was reading about why <a href="http://esper.codehaus.org/">Esper </a>sometimes implements <a href="http://en.wikipedia.org/wiki/Spin_Lock">Spin Locks</a> instead of thread suspension, I wondered what the actual benefits were. I put together a quick and very simple test to measure elapsed time and CPU utilization of 3 different suspension operations: (Using java version "1.6.0_21" on Windows 7)<br />
<ol><li>Spin: Capture <b>until</b> (the current time plus the suspend time in ms.) and loop until the current time is equal to or greater than <b>until</b>.</li>
<li>Sleep: Call <b>Thread.sleep(<i><suspend in="" ms.="" time=""></suspend></i>)</b></li>
<li><b>Join:</b> Call <b>Thread.currentThread().join(</b><b><i><suspend in="" ms.="" time=""></suspend></i></b><b>)</b>.<b> </b> </li>
</ol>This was a single thread, called in main, sleeping for 5 ms. in a loop of 100,000. As it turns out, <b><i>Spin</i></b> is the absolute <i>most</i> precise of the three, running with zero loss of precision, but at a cost of 499,905 ms. of CPU time.<br />
<br />
<i><b>Sleep</b></i> lost the most precision with 1,358 ms. worth of latency and a cost of 31 ms. of CPU time.<br />
<br />
<i><b>Join</b></i> seems to be the best compromise with zero CPU utilization and only 7 ms. loss of precision.<br />
<br />
So the moral of the story, it seems to me, is to always use <b><i>Join</i></b><i><b> </b></i>unless you need a hyper-precise wake-up time. Furthermore, it would seem that <i><b>Sleep</b></i> is the worst way to achieve a suspension with much lower precision than <i><b>Join</b></i> at an infinitely higher CPU cost.<br />
<br />
Output:<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;"><b>SpinLock vs. Sleep vs. Join<br />
Warming Up....<br />
Warm Up Complete<br />
Starting Spin Test<br />
Spin: 500000/499905204500<br />
Starting Sleep Test<br />
Sleep: 501358/31200200<br />
Starting Join Test<br />
Join: 500007/0</b></div><br />
==== Update ==== <br />
<br />
Tested the exact same code using java version "1.6.0_24" on Ubuntu 10.10 X64 and the results were a little different. The <i><b>Spin</b></i> did have some precision loss (but not much). The biggest difference was that <i><b>Join</b></i> went from zero CPU cost to 3,970 ms cost, making it more expensive than <i><b>Sleep</b></i> and with slightly less precision. Not sure what to make of that, except that I need to rerun the tests to make sure there is no skew from background processes or something.<br />
<br />
<b><span style="font-family: "Courier New",Courier,monospace;">SpinLock vs. Sleep vs. Join</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">Warming Up....</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">Warm Up Complete</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">Starting Spin Test</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">Spin: 500034/499590000000</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">Starting Sleep Test</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">Sleep: 516430/3680000000</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">Starting Join Test</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">Join: 516650/3970000000</span></b>nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com0tag:blogger.com,1999:blog-11251261.post-73043828025789949532008-08-11T22:54:00.003-04:002008-08-12T07:54:36.456-04:00Java run-time monitoring, Part 3<a href="http://www.ibm.com/developerworks/library/j-rtm3/index.html"><span style="font-size:180%;"><span style="font-weight: bold;">P</span></span>art 3</a> is not really about monitoring Java itself, but rather how to monitor other resources using Java, plus how to manage and visualize collected data. There are several examples I would have liked to put in the article, but with the PDF version running at 73 pages, it got a bit too long.nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com1tag:blogger.com,1999:blog-11251261.post-42196100918332698882008-08-05T07:24:00.003-04:002008-08-05T08:13:49.100-04:00Postcompilation instrumentation and performance monitoring<a href="http://www.ibm.com/developerworks/library/j-rtm2/index.html">Part 2</a> of my article series on Java run-time monitoring was published today. Last week I reviewed methods of instrumenting java classes at the source level. While not ideal in light of the ubiquity of AOP techniques to inject instrumentation at runtime, if you have or want to do it, take a page from Log4J's book and make sure that the instrumentation is externally configurable (i.e. you don't need to re-compile to tweak it). Here's a quick list of features you might look for in your Log4J instrumentation analog:<br /><ul><li><span style="font-weight: bold;">Verbosity</span>: Yep, just like Log4J (and many other logging packages) the Level of loggers can be modified at runtime through JMX or simply by placing a watch on the XML configuration file.</li><li><span style="font-weight: bold;">Thresholds</span>: Automatically trace incidents when measurements exceed externally defined thresholds.<br /></li><li><span style="font-weight: bold;">Ranges</span>: Automatically trace counts of measurements that fall into a value window as part of an externally defined and named range. (useful for SLAs where an operation's elapsed time might be classified as GOOD, WARN or CIRITCAL.)<br /></li><li><span style="font-weight: bold;">Rollups</span>: Maintain aggregated performance statistics all the way up a hierarchical tree.<br /></li></ul>So in this week's article, I review methods of instrumentation that can be used in the post-compilation phase, or more simply, without futzing with the source code.<br /><br /><ul><li><span style="font-weight: bold;">Interceptors</span>: J2EE and a bunch of other [not just] Java frameworks support the ability to inject interceptors into the application without ever touching the source code of your code application classes.</li><li><span style="font-weight: bold;">Class Wrapping</span>: Implement instrumentation in a class wrapper and then wrap your target class. You get the same functionality and instrumentation to boot.</li><li><span style="font-weight: bold;">Byte Code Instrumentation</span>: Inject instrumentation byte code directly into your already compiled classes. This can be done statically (i.e. you end up with the original jar and and instrumented jar) or it can be done dynamically at runtime when the classes are classloaded.</li></ul>An additional approach which I did not really address in the article is the concept of class swapping. For example, if you wanted to know some low down performance and byte volume details of a network service, you could create a new class called <code>com.myco.instrumentation.InstrumentedSocket</code> which extends <code>java.net.Socket</code> and adds in some basic instrumentation like counting byte transfer rates. At class load time, you could swap out the implementation of all instances of <code>java.net.Socket</code> with your instrumented version.<br /><br />Hopefully the article is useful to you.<br /><br />Cheers.nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com0tag:blogger.com,1999:blog-11251261.post-3571353989474407532008-07-30T00:30:00.001-04:002008-07-30T00:30:31.979-04:00Java Runtime Monitoring, Part 1<div xmlns='http://www.w3.org/1999/xhtml'><a href='http://www.ibm.com/developerworks/library/j-rtm1/index.html' target='_blank'>Part 1</a> of my developerWorks series on application monitoring was published today. My goal was to champion the use of fine grained monitoring of the JVM and application code instrumentation.<br/></div>nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com0tag:blogger.com,1999:blog-11251261.post-1128624928442668302005-10-06T11:51:00.001-04:002007-04-13T00:28:36.453-04:00AutoDiscovery - [Trying to] Say No To Static ConfigurationI am always on the lookout for ways to avoid configuration files and the like in favour of dynamic discovery. The JBoss HA-JNDI implementation is a great example where you can specify a set of properties and the ContextFactory will automatically discover a server matching your criteria.<br /><br />I recently tired of creating yet another user database with roles, passwords, expiration and all the rest of it, so on a new project we started recently for an internal web application, I decided I was going to use the company's Active Directory server. Seemed like a genius idea. Single sign on for my users and I get to delegate all the afformentioned to a bunch of LDAP servers that someone else maintains.<br /><br />Down the road a ways, I started thinking about which of 10 LDAP servers I should use. What if one of them goes down ? What if they change ? What if they add new servers ? It turns out (and perhaps you already knew this) that DNS will help you locate servers by service, not just by name. Sun provides a <a href="http://java.sun.com/j2se/1.4.2/docs/guide/jndi/jndi-dns.html">DNS Service Provider for JNDI </a>which makes the process nice-n-easy. I could not get it to work exactly as advertised, but with a bit of jiggery pokery, it did the job admirably. In the example below, you simply pass in a peculiar looking string that specifies the service, protocol and domain you are looking for. In my case that string was <span style="font-style: italic;"><span style="font-weight: bold;">_ldap._tcp.mydomain.com</span></span>.<br /><br /><pre><span style="color: rgb(0, 0, 0);"> </span><span style="color: rgb(0, 128, 0);">/**<br />* Returns an array of located hosts for the given service descriptor.<br />* @param service String<br />* @return LocatedHost[]<br />* @throws NamingException<br />*/</span><span style="color: rgb(0, 0, 0);"><br /></span><span style="color: rgb(0, 0, 128);"><b>public</b></span><span style="color: rgb(0, 0, 0);"> LocatedHost[] locateHosts(String service) </span><span style="color: rgb(0, 0, 128);"><b>throws</b></span><span style="color: rgb(0, 0, 0);"> NamingException {<br /> Properties dnsProperties = </span><span style="color: rgb(0, 0, 128);"><b>new</b></span><span style="color: rgb(0, 0, 0);"> Properties();<br /> DirContext ctx = </span><span style="color: rgb(0, 0, 128);"><b>null</b></span><span style="color: rgb(0, 0, 0);">;<br /> ArrayList locatedHosts = </span><span style="color: rgb(0, 0, 128);"><b>new</b></span><span style="color: rgb(0, 0, 0);"> ArrayList();<br /> dnsProperties.put(<i>Context</i>.<i>INITIAL_CONTEXT_FACTORY</i>, </span><span style="color: rgb(0, 0, 255);">"com.sun.jndi.dns.DnsContextFactory"</span><span style="color: rgb(0, 0, 0);">);<br /> dnsProperties.put(<i>Context</i>.<i>PROVIDER_URL</i>, </span><span style="color: rgb(128, 0, 128);">dnsList</span><span style="color: rgb(0, 0, 0);">);<br /> String[] hostAttributes = </span><span style="color: rgb(0, 0, 128);"><b>new</b></span><span style="color: rgb(0, 0, 0);"> String[] {</span><span style="color: rgb(0, 0, 255);">"A"</span><span style="color: rgb(0, 0, 0);">};<br /> LocatedHost locatedHost = </span><span style="color: rgb(0, 0, 128);"><b>null</b></span><span style="color: rgb(0, 0, 0);">;<br /> </span><span style="color: rgb(0, 0, 128);"><b>try</b></span><span style="color: rgb(0, 0, 0);"> {<br /> ctx = </span><span style="color: rgb(0, 0, 128);"><b>new</b></span><span style="color: rgb(0, 0, 0);"> InitialDirContext(dnsProperties);<br /> DnsContext dnsContext = (DnsContext)ctx.lookup(service);<br /> Attributes attrs = dnsContext.getAttributes(</span><span style="color: rgb(0, 0, 255);">""</span><span style="color: rgb(0, 0, 0);">);<br /> </span><span style="color: rgb(0, 0, 128);"><b>int</b></span><span style="color: rgb(0, 0, 0);"> i = attrs.get(</span><span style="color: rgb(0, 0, 255);">"srv"</span><span style="color: rgb(0, 0, 0);">).size();<br /> </span><span style="color: rgb(0, 0, 128);"><b>for</b></span><span style="color: rgb(0, 0, 0);">(</span><span style="color: rgb(0, 0, 128);"><b>int</b></span><span style="color: rgb(0, 0, 0);"> c = </span><span style="color: rgb(0, 0, 255);">0</span><span style="color: rgb(0, 0, 0);">; c < locatedhost =" <span style="> LocatedHost();<br /> String entry = attrs.get(</span><span style="color: rgb(0, 0, 255);">"srv"</span><span style="color: rgb(0, 0, 0);">).get(c).toString();<br /> String parsedEntries[] = entry.split(</span><span style="color: rgb(0, 0, 255);">" "</span><span style="color: rgb(0, 0, 0);">);<br /> String hostName = parsedEntries[</span><span style="color: rgb(0, 0, 255);">3</span><span style="color: rgb(0, 0, 0);">];<br /> </span><span style="color: rgb(0, 0, 128);"><b>int</b></span><span style="color: rgb(0, 0, 0);"> port = <i>Integer</i>.<i>parseInt</i>(parsedEntries[</span><span style="color: rgb(0, 0, 255);">2</span><span style="color: rgb(0, 0, 0);">]);<br /> locatedHost.setHostName(hostName);<br /> locatedHost.setPort(port);<br /> Attributes hAttr = ctx.getAttributes(hostName, hostAttributes);<br /> locatedHost.setHostAddress(hAttr.get(</span><span style="color: rgb(0, 0, 255);">"A"</span><span style="color: rgb(0, 0, 0);">).get().toString());<br /> locatedHosts.add(locatedHost);<br /> }<br /> </span><span style="color: rgb(0, 0, 128);"><b>return</b></span><span style="color: rgb(0, 0, 0);"> (LocatedHost[])locatedHosts.toArray(</span><span style="color: rgb(0, 0, 128);"><b>new</b></span><span style="color: rgb(0, 0, 0);"> LocatedHost[locatedHosts.size()]);<br /><br /> } </span><span style="color: rgb(0, 0, 128);"><b>catch</b></span><span style="color: rgb(0, 0, 0);"> (NamingException ex) {<br /> </span><span style="color: rgb(0, 0, 128);"><b>throw</b></span><span style="color: rgb(0, 0, 0);"> ex;<br /> } </span><span style="color: rgb(0, 0, 128);"><b>finally</b></span><span style="color: rgb(0, 0, 0);"> {<br /> </span><span style="color: rgb(0, 0, 128);"><b>try</b></span><span style="color: rgb(0, 0, 0);"> { ctx.close(); } </span><span style="color: rgb(0, 0, 128);"><b>catch</b></span><span style="color: rgb(0, 0, 0);"> (Exception </span><span style="color: rgb(128, 128, 128);">e</span><span style="color: rgb(0, 0, 0);">) {}<br /> }<br /></span><br /></span></pre><br /><br />A few things to note. This method is part of a class which takes a string of space separated DNS server URLs in the constructor. (Loaded into dnsProperties.) The format of the DNS URL is <span style="font-weight: bold;">dns://<ip></ip></span>. So to test on my [non DNS serving] laptop, I passed in <span style="font-weight: bold; color: rgb(0, 153, 0);">"dns://127.0.0.1 dns://216.254.95.2"</span>.<br /><br />What is retuned from the lookup entries is an SRV record which turned out to be returned as a string which looks like this:<br /><br /><span style="font-weight: bold;">0 100 389 serverAdc1.mydomain.com</span><br /><br />If I iterate through all returned servers, it turns out we have way more than 10 Active Directory servers. More like 53 of them in my domain, so I get back 53 records which look more or less like the one above. Not sure what the two numbers are, but I think the 100 bit is intended to indicate some sort of preference, but every server has the same values, except the actual server name. The third value is the port, and fourth, obviously, is the server name. There is more detail on this in the <a href="http://www.faqs.org/ftp/pub/internet-drafts/draft-ietf-ldapext-locate-08.txt">internet draft</a>.<br /><br />Last problem: The list returned is in no particular order, so the first server returned is as likely to be in California than in New Jersey where I am. This is sort of mesmerizing in a <span style="font-style: italic;">distributed network transparency and location independence</span> sort of way, but I quickly reaslized that actual authentication calls take significantly longer if the server is far away. I am still researching this issue, but it seems that there is no standard as to the sequence in which these records are returned. Accordingly, I also implemented a slightly less performant version of the code above that actually attempts an anonymous connection against each server as it is discovered. The elapsed time for the connection is measured, and the returned array is then sorted in ascending order of elpased time. Works great, but I would only run it once in a while......<br /><br />My most recent exploration on this general subject has been <span style="font-style: italic;"><span style="font-weight: bold;">Service Location Protocol</span>. </span>It really seems to fit the bill and should allow me to define custom services with all sorts of additional properties and then be able to automatically discover them. For example, if I want to discover the location of <span style="font-style: italic;"><span style="font-weight: bold;">an HTTP Custom Service Invocation Client for Milkshake Delivery</span></span> , the SLP server should be able to tell me where it is, provided that said service registered itself with the SLP server when it started.<br /><br />All my SuSE Linux servers had SLP installed by default. However, the only Java API I have found is from <a href="http://www.openslp.org/">OpenSLP</a> and I cannot get it to work at all. I can use the <span style="font-weight: bold;">slptool</span> on my servers and find things like the SSH services, but my Java client just listens for 5 seconds and then gives up. If anyone has this working, I would sure appreciate a pointer or two.nickmanhttp://www.blogger.com/profile/09238427833396441114noreply@blogger.com0