<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.igorminar.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;CUYDRHk6eSp7ImA9WxJUEEo.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950</id><updated>2009-07-08T10:12:55.711-07:00</updated><title>Igor Minar's Blog</title><subtitle type="html">A Sudden Burst of Ideas...</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://blog.igorminar.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://blog.igorminar.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>99</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><link rel="self" href="http://feeds.igorminar.com/IgorMinarsBlog" type="application/atom+xml" /><entry gd:etag="W/&quot;DkMDQ3o9cCp7ImA9WxJQFUs.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-2705753778911666250</id><published>2009-05-28T19:30:00.000-07:00</published><updated>2009-05-28T19:34:32.468-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-28T19:34:32.468-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="grizzly-sendfile" /><category scheme="http://www.blogger.com/atom/ns#" term="Java" /><category scheme="http://www.blogger.com/atom/ns#" term="Projects" /><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><category scheme="http://www.blogger.com/atom/ns#" term="Glassfish" /><title>grizzly-sendfile to Become an Official Grizzly Module</title><content type="html">After a chat with &lt;a href="http://weblogs.java.net/blog/jfarcand/"&gt;JFA&lt;/a&gt; about &lt;a href="http://grizzly-sendfile.kenai.com/"&gt;grizzly-sendfile&lt;/a&gt;'s future, I'm pleased to announce today that grizzly-sendfile 0.4 will be the first version of grizzly-sendfile released as an official module of grizzly. This is a huge news for grizzly-sendfile and I believe an equally important news for grizzly and its community.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;What this "merger" means for grizzly-sendfile:&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;great opportunity to extend the reach&lt;/li&gt;&lt;li&gt;opportunity to become the default static file handler in Grizzly&lt;/li&gt;&lt;li&gt;aspiration to become the default static file handler in GlassFish v3&lt;/li&gt;&lt;li&gt;more testing and QA&lt;/li&gt;&lt;li&gt;easier and faster access to grizzly developers and contributors&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h4&gt;What this "merger" means for grizzly:&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;contribution of 1 year of research, development and testing time in the area of static http downloads&lt;/li&gt;&lt;li&gt;several times better performance and scalability of http static file downloads&lt;/li&gt;&lt;li&gt;built-in X-Sendfile functionality&lt;/li&gt;&lt;li&gt;better JMX instrumentation for http downloads&lt;/li&gt;&lt;li&gt;and more&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;If you can't wait for 0.4, go and get &lt;a href="http://blog.igorminar.com/2009/05/grizzly-sendfile-03-is-out.html"&gt;recently released version 0.3&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This is a great day for both projects! :-)&lt;br /&gt;&lt;br /&gt;Project site: &lt;a href="http://grizzly-sendfile.kenai.com/"&gt;http://grizzly-sendfile.kenai.com/&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-2705753778911666250?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/1PT2mreMcAk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/2705753778911666250/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=2705753778911666250" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/2705753778911666250?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/2705753778911666250?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/1PT2mreMcAk/grizzly-sendfile-to-become-official.html" title="grizzly-sendfile to Become an Official Grizzly Module" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://blog.igorminar.com/2009/05/grizzly-sendfile-to-become-official.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0YMQng7eip7ImA9WxJREkU.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-7332903862043741427</id><published>2009-05-14T00:50:00.000-07:00</published><updated>2009-05-14T01:19:43.602-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-14T01:19:43.602-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="grizzly-sendfile" /><category scheme="http://www.blogger.com/atom/ns#" term="Java" /><category scheme="http://www.blogger.com/atom/ns#" term="Projects" /><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><category scheme="http://www.blogger.com/atom/ns#" term="Glassfish" /><title>grizzly-sendfile 0.3 is out!</title><content type="html">After a few months of late night hacking, &lt;a href="http://grizzly-sendfile.kenai.com/"&gt;grizzly-sendfile&lt;/a&gt; 0.3 is finally ready for prime time!&lt;br /&gt;&lt;br /&gt;New features include:&lt;ul&gt;&lt;li&gt;&lt;a href="http://kenai.com/projects/grizzly-sendfile/pages/SettingUp"&gt;grizzly 1.9.15 compatibility&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://kenai.com/projects/grizzly-sendfile/pages/SettingUp#Using_grizzly-sendfile_with_GlassFish_v3"&gt;glassfish v3b48 compatibility (via an OSGi bundle)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://blog.igorminar.com/2009/05/grizzly-sendfile-and-comparison-of.html"&gt;kick-ass performance and scalability&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://kenai.com/projects/grizzly-sendfile/pages/Grizzly-sendfile-server"&gt;grizzly-sendfile-server&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://kenai.com/projects/grizzly-sendfile/pages/AutoSendfile"&gt;autosendfile mode&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://kenai.com/projects/grizzly-sendfile/pages/Algorithms"&gt;Sendfile Algorithms&lt;/a&gt;: new EqualBlockingAlgorithm + improved EqualNonBlockingAlgorithm&lt;/li&gt;&lt;li&gt;lots of improvements to &lt;a href="http://kenai.com/projects/grizzly-sendfile/sources/mercurial/show/test/faban/grizzly-sendfile-benchmark"&gt;grizzly-sendfile-benchmark&lt;/a&gt;&lt;/li&gt;&lt;li&gt;maven build system&lt;/li&gt;&lt;li&gt;lots of smaller improvements and bug fixes&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;I also started using &lt;a href="http://kenai.com/jira/secure/IssueNavigator.jspa?reset=true&amp;mode=hide&amp;pid=10034&amp;resolution=-1&amp;sorter/field=updated&amp;sorter/order=DESC"&gt;kenai's JIRA&lt;/a&gt; for issue tracking. So feel free to file bugs or RFE's there.&lt;br /&gt;&lt;br /&gt;Benchmark &amp; Enjoy!&lt;br /&gt;&lt;br /&gt;&lt;a href="http://grizzly-sendfile.kenai.com/"&gt;Project Website&lt;/a&gt;&lt;br /&gt;&lt;a href="https://kenai.com/hg/grizzly-sendfile~mercurial/file/e91c72bf1346"&gt;The source code&lt;/a&gt;&lt;br /&gt;&lt;a href="http://kenai.com/projects/grizzly-sendfile/downloads"&gt;The binaries&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-7332903862043741427?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/EwJh1LRBxJk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/7332903862043741427/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=7332903862043741427" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/7332903862043741427?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/7332903862043741427?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/EwJh1LRBxJk/grizzly-sendfile-03-is-out.html" title="grizzly-sendfile 0.3 is out!" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.igorminar.com/2009/05/grizzly-sendfile-03-is-out.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEUCRX47cCp7ImA9WxJREUw.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-1840308271841899595</id><published>2009-05-10T19:00:00.000-07:00</published><updated>2009-05-12T00:11:04.008-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-05-12T00:11:04.008-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="grizzly-sendfile" /><category scheme="http://www.blogger.com/atom/ns#" term="Java" /><category scheme="http://www.blogger.com/atom/ns#" term="Projects" /><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><title>grizzly-sendfile and Comparison of Blocking and NonBlocking IO</title><content type="html">From the very early beginnings of my work on &lt;a href="http://kenai.com/projects/grizzly-sendfile"&gt;grizzly-sendfile&lt;/a&gt; (&lt;a href="http://blog.igorminar.com/2009/02/announcing-grizzly-sendfile.html"&gt;intro&lt;/a&gt;) I was curious to compare blocking and non-blocking IO side to side. Since I didn't have any practical experience to understand which one would be more suitable when, I designed grizzly-sendfile to be flexible so that I could try different strategies and come to conclusions based on some real testing rather than theorizing or based on the words of others. In this post I'd like to compare blocking and nonblocking IO, benchmark them, and draw some conclusions as to which one is more suitable for specific situations.&lt;br /&gt;&lt;br /&gt;grizzly-sendfile has a notion of algorithms that control the IO operations responsible for writing data to a &lt;a href="http://java.sun.com/javase/6/docs/api/java/nio/channels/SocketChannel.html"&gt;SocketChannel&lt;/a&gt; (grizzly-sendfile is based on &lt;a href="http://java.sun.com/javase/6/docs/api/java/nio/package-summary.html"&gt;NIO&lt;/a&gt; and leverages lots of great work put into &lt;a href="http://grizzly.dev.java.net/"&gt;grizzly&lt;/a&gt;). Different algorithms can do this in different ways, and this is &lt;a href="http://kenai.com/projects/grizzly-sendfile/pages/Algorithms"&gt;explained in depth&lt;/a&gt; on the &lt;a href="http://kenai.com/projects/grizzly-sendfile/pages/Home"&gt;project wiki&lt;/a&gt;. The point is that this allows me to create algorithms that use blocking or nonblocking IO in different ways, and easily swap them and compare their performance (in a very isolated manner).&lt;br /&gt;&lt;br /&gt;Two algorithms I implemented right away were &lt;a href="http://kenai.com/projects/grizzly-sendfile/sources/mercurial/content/grizzly-sendfile/src/main/java/com/igorminar/grizzlysendfile/algorithm/SimpleBlockingAlgorithm.java"&gt;SimpleBlockingAlgorithm&lt;/a&gt; (SBA) and &lt;a href="http://kenai.com/projects/grizzly-sendfile/sources/mercurial/content/grizzly-sendfile/src/main/java/com/igorminar/grizzlysendfile/algorithm/EqualNonBlockingAlgorithm.java"&gt;EqualNonBlockingAlgorithm&lt;/a&gt; (ENBA), and only recently followed by &lt;a href="http://kenai.com/projects/grizzly-sendfile/sources/mercurial/content/grizzly-sendfile/src/main/java/com/igorminar/grizzlysendfile/algorithm/EqualBlockingAlgorithm.java"&gt;EqualBlockingAlgorithm&lt;/a&gt; (EBA). The first one employs the traditional approach of sending a file via the network (while not EOF write data to a SocketChannel using blocking writes), while ENBA uses non-blocking writes and &lt;a href="http://java.sun.com/javase/6/docs/api/java/nio/channels/Selector.html"&gt;Selector&lt;/a&gt; re-registration (in place of blocking) to achieve the same task. This means that a download is split into smaller parts, each sequentially streamed by an assigned worker thread to the client. EBA works very similarly to ENBA, but uses blocking writes.&lt;br /&gt;&lt;br /&gt;I ran two variations of my &lt;a href="http://www.blogger.com/faban.sunsource.net/"&gt;faban&lt;/a&gt; &lt;a href="http://kenai.com/projects/grizzly-sendfile/sources/mercurial/show/test/faban/grizzly-sendfile-benchmark"&gt;benchmark&lt;/a&gt;&lt;a href="http://www.blogger.com/post-edit.g?blogID=6406593750327945950&amp;amp;postID=1840308271841899595#footnote-0"&gt;&lt;sup&gt;[0]&lt;/sup&gt;&lt;/a&gt; against these three algorithms. At first I made my simulated clients hit the server as often as possible and download files of different sizes as quickly as possible. Afterward I throttled the file download speed to 1MB/s per client (throttling was done by clients). While the first benchmark simulates traffic close to the one on the private network in a datacenter, the second benchmarks better represents client/server traffic on the Internet.&lt;br /&gt;&lt;br /&gt;grizzly-sendfile delegates the execution of the selected algorithm to a pool of worker threads, so the maximum number of the treads in the pool, along with the selected algorithm, are one of the major factors that affects the performance&lt;a name="#footnote-1"&gt;&lt;sup&gt;[1]&lt;/sup&gt;&lt;/a&gt; and scalability&lt;a name="#footnote-2"&gt;&lt;sup&gt;[2]&lt;/sup&gt;&lt;/a&gt; of the server. In my tests I kept the pool size relatively small (50 threads), in order to easily simulate situations when there are more concurrent requests than the number of workers, which is common during traffic spikes.&lt;br /&gt;&lt;br /&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Conc. Clients&lt;/th&gt;&lt;th&gt;Download limit&lt;/th&gt;&lt;th&gt;Algorithm&lt;/th&gt;&lt;th&gt;Avg init time&lt;a name="#footnote-3"&gt;&lt;sup&gt;[3]&lt;/sup&gt;&lt;/a&gt; (sec)&lt;/th&gt;&lt;th&gt;Avg speed&lt;a name="#footnote-4"&gt;&lt;sup&gt;[4]&lt;/sup&gt;&lt;/a&gt; (MB/s)&lt;/th&gt;&lt;th&gt;Avg total throughput (MB/s)&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;50&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;td&gt;SBA&lt;/td&gt;&lt;td&gt;0.019&lt;/td&gt;&lt;td&gt;&lt;b&gt;4.36&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;208.76&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;50&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;td&gt;ENBA&lt;/td&gt;&lt;td&gt;0.021&lt;/td&gt;&lt;td&gt;4.15&lt;/td&gt;&lt;td&gt;198.79&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;50&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;td&gt;EBA&lt;/td&gt;&lt;td&gt;&lt;b&gt;0.018&lt;/b&gt;&lt;/td&gt;&lt;td&gt;4.23&lt;/td&gt;&lt;td&gt;202.29&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="6" style="border-top: 1px solid black"&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;100&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;td&gt;SBA&lt;/td&gt;&lt;td&gt;4.666&lt;/td&gt;&lt;td&gt;&lt;b&gt;4.32&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;212.79&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;100&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;td&gt;ENBA&lt;/td&gt;&lt;td&gt;&lt;b&gt;0.048&lt;/b&gt;&lt;/td&gt;&lt;td&gt;1.84&lt;/td&gt;&lt;td&gt;168.15&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;100&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;td&gt;EBA&lt;/td&gt;&lt;td&gt;0.140&lt;/td&gt;&lt;td&gt;1.96&lt;/td&gt;&lt;td&gt;175.71&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="6" style="border-top: 1px solid black"&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;200&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;td&gt;SBA&lt;/td&gt;&lt;td&gt;14.288&lt;/td&gt;&lt;td&gt;&lt;b&gt;4.31&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;208.59&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;200&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;td&gt;ENBA&lt;/td&gt;&lt;td&gt;&lt;b&gt;0.108&lt;/b&gt;&lt;/td&gt;&lt;td&gt;0.87&lt;/td&gt;&lt;td&gt;144.69&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;200&lt;/td&gt;&lt;td&gt;none&lt;/td&gt;&lt;td&gt;EBA&lt;/td&gt;&lt;td&gt;0.264&lt;/td&gt;&lt;td&gt;0.97&lt;/td&gt;&lt;td&gt;158.83&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Conc. Clients&lt;/th&gt;&lt;th&gt;Download limit&lt;/th&gt;&lt;th&gt;Algorithm&lt;/th&gt;&lt;th&gt;Avg init time&lt;a name="#footnote-3"&gt;&lt;sup&gt;[3]&lt;/sup&gt;&lt;/a&gt; (sec)&lt;/th&gt;&lt;th&gt;Avg speed&lt;a name="#footnote-4"&gt;&lt;sup&gt;[4]&lt;/sup&gt;&lt;/a&gt; (MB/s)&lt;/th&gt;&lt;th&gt;Avg total throughput (MB/s)&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;50&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;SBA&lt;/td&gt;&lt;td&gt;0.003&lt;/td&gt;&lt;td&gt;1.0&lt;/td&gt;&lt;td&gt;&lt;b&gt;42.9&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;50&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;ENBA&lt;/td&gt;&lt;td&gt;0.002&lt;/td&gt;&lt;td&gt;0.98&lt;/td&gt;&lt;td&gt;41.82&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;50&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;EBA (81k)&lt;a name="#footnote-5"&gt;&lt;sup&gt;[5]&lt;/sup&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;0.003&lt;/td&gt;&lt;td&gt;0.98&lt;/td&gt;&lt;td&gt;41.91&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;50&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;EBA (40k)&lt;a name="#footnote-6"&gt;&lt;sup&gt;[6]&lt;/sup&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;0.002&lt;/b&gt;&lt;/td&gt;&lt;td&gt;0.98&lt;/td&gt;&lt;td&gt;42.85&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="6" style="border-top: 1px solid black"&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;100&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;SBA&lt;/td&gt;&lt;td&gt;20.5&lt;/td&gt;&lt;td&gt;1.0&lt;/td&gt;&lt;td&gt;40.4&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;100&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;ENBA&lt;/td&gt;&lt;td&gt;&lt;b&gt;0.003&lt;/b&gt;&lt;/td&gt;&lt;td&gt;0.99&lt;/td&gt;&lt;td&gt;&lt;b&gt;85.14&lt;b&gt;&lt;/b&gt;&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;100&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;EBA (81k)&lt;/td&gt;&lt;td&gt;0.018&lt;/td&gt;&lt;td&gt;1.0&lt;/td&gt;&lt;td&gt;84.19&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;100&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;EBA (40k)&lt;/td&gt;&lt;td&gt;0.013&lt;/td&gt;&lt;td&gt;0.99&lt;/td&gt;&lt;td&gt;84.34&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="6" style="border-top: 1px solid black"&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;200&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;SBA&lt;/td&gt;&lt;td&gt;64.8&lt;/td&gt;&lt;td&gt;1.0&lt;/td&gt;&lt;td&gt;37.12&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;200&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;ENBA&lt;/td&gt;&lt;td&gt;&lt;b&gt;0.112&lt;/b&gt;&lt;/td&gt;&lt;td&gt;0.86&lt;/td&gt;&lt;td&gt;141.8&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;200&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;EBA (81k)&lt;/td&gt;&lt;td&gt;0.2&lt;/td&gt;&lt;td&gt;0.95&lt;/td&gt;&lt;td&gt;&lt;b&gt;156.59&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;200&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;EBA (40k)&lt;/td&gt;&lt;td&gt;0.159&lt;/td&gt;&lt;td&gt;0.96&lt;/td&gt;&lt;td&gt;154.2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="6" style="border-top: 1px solid black"&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;300&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;SBA&lt;/td&gt;&lt;td&gt;113.9&lt;/td&gt;&lt;td&gt;1.0&lt;/td&gt;&lt;td&gt;34.2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;300&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;ENBA&lt;/td&gt;&lt;td&gt;&lt;b&gt;0.185&lt;/b&gt;&lt;/td&gt;&lt;td&gt;0.58&lt;/td&gt;&lt;td&gt;127.53&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;300&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;EBA (81k)&lt;/td&gt;&lt;td&gt;0.31&lt;/td&gt;&lt;td&gt;0.61&lt;/td&gt;&lt;td&gt;&lt;b&gt;133.66&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;300&lt;/td&gt;&lt;td&gt;1MB/s&lt;/td&gt;&lt;td&gt;EBA (40k)&lt;/td&gt;&lt;td&gt;0.239&lt;/td&gt;&lt;td&gt;0.63&lt;/td&gt;&lt;td&gt;132.75&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;br /&gt;The interpretation of the results is that with SBA the individual download speeds are slightly higher than when ENBA or EBA is used (this is mainly due to some extra scheduling related to the Selector reregistration). However, EBA and ENBA can utilize the available server bandwidth significantly better than SBA. This is especially true when there is a large number of slow clients downloading larger files. One big downside of SBA is that if the number of concurrent downloads is higher than the number of worker threads, the time to initiate download easily increases into extreme heights.&lt;br /&gt;&lt;br /&gt;The conclusion is that SBA is well suited for controlled environments where there is a small number of fast clients (server-to-server communication on an internal network), while EBA shines in environments where there is very little control over the number and speed of clients (file hosting on the Internet). While EBA performs and scales better than ENBA, two advantages that ENBA has over EBA are smaller latency and higher resiliency to DoS or DDoS attacks when malicious clients open connections and block.&lt;br /&gt;&lt;br /&gt;The results above do not represent well the performance and throughput of grizzly-sendfile (the benchmarks were run on my laptop!), but they certainly provide some evidence that can be used to determine characteristics of different algorithms. I'll do some more thorough testing later when I feel that grizzly-sendfile has enough features and it is time for some fine tuning (there is still a lot that can be done to make things more efficient).&lt;br /&gt;&lt;br /&gt;I love explaining things visually, so I drew the following diagrams that describe the differences between the two algorithms much better than many words.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;SimpleBlockingAlgorithm&lt;/h3&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_p64VvtgZDp4/Sgc_NPFQKKI/AAAAAAAAAOY/Iy1jDGE_MyU/s1600-h/Blocking_SingleBurst_Algorithm.png"&gt;&lt;img style="border: 0pt none ; margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 171px;" src="http://2.bp.blogspot.com/_p64VvtgZDp4/Sgc_NPFQKKI/AAAAAAAAAOY/Iy1jDGE_MyU/s400/Blocking_SingleBurst_Algorithm.png" alt="" id="BLOGGER_PHOTO_ID_5334301780263053474" border="0" /&gt;&lt;/a&gt;The server can concurrently handle only the same number of requests as the number of worker threads in the pool. All the extra downloads have to be queued until one of some workers completes a download. The downloads take shorter to process, but the worker is blocked while a write is blocked. The slower the client the more blocking occurs and the worker utilization (and server throughput) goes down.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;EqualNonBlockingAlgorithm&lt;/h3&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_p64VvtgZDp4/Sgc_VRCD4LI/AAAAAAAAAOg/XOuCWYo7-y8/s1600-h/NonBlocking_MultiBurst_Algorithm.png"&gt;&lt;img style="border: 0pt none ; margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 215px;" src="http://2.bp.blogspot.com/_p64VvtgZDp4/Sgc_VRCD4LI/AAAAAAAAAOg/XOuCWYo7-y8/s400/NonBlocking_MultiBurst_Algorithm.png" alt="" id="BLOGGER_PHOTO_ID_5334301918225490098" border="0" /&gt;&lt;/a&gt;The number of downloads a server can concurrently handle is not limited by the number of workers. The downloads take slightly longer to process, but the workers are much better utilized. The increase in utilization is due to the fact that no blocking occurs and workers can multiplex - "pause" downloads for which clients are processing the data (stored in OS/network buffers). In the meantime workers can serve whichever client is ready to receive more data. This causes the download to be split into several parts, each possibly served by a different worker thread.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;EqualBlockingAlgorithm&lt;/h3&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_p64VvtgZDp4/Sgc_HbmHalI/AAAAAAAAAOQ/bbb_61oZbBE/s1600-h/Blocking_MultiBurst_Algorithm.png"&gt;&lt;img style="border: 0pt none ; margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px;" src="http://2.bp.blogspot.com/_p64VvtgZDp4/Sgc_HbmHalI/AAAAAAAAAOQ/bbb_61oZbBE/s400/Blocking_MultiBurst_Algorithm.png" alt="" id="BLOGGER_PHOTO_ID_5334301680542902866" border="0" /&gt;&lt;/a&gt;This algorithm is a combination of the previous two. It uses blocking IO and multiplexing. I think that thanks to multiplexing the client has more time to process data in OS/network buffers and thus deplete the buffer more than without multiplexing, which results in less blocking. The blocked writes are also more efficient because they decrease the number of parts a download is split into and thus decrease the amount of overhead associated with re-registration.&lt;br /&gt;&lt;br /&gt;Based on these benchmarks, I'm going to call EqualBlockingAlgorithm the winner and make it the default grizzly-sendfile algorithm for now. It is quite easy to override this via the grizzly-sendfile configuration, so one will still be able to pick the algorithm that fits their deployment environment the best. To be honest the results of EBA benchmarks surprised me a bit because I expected ENBA to be the winner in throttled tests, so I'm really glad that I went into the trouble of creating and benchmarking it.&lt;br /&gt;&lt;br /&gt;All this work will be part of the 0.3 release, which is due any day now. &lt;a href="http://kenai.com/projects/grizzly-sendfile"&gt;Follow the project on kenai&lt;/a&gt; and subscribe to the &lt;a href="http://kenai.com/projects/grizzly-sendfile/lists"&gt;announce mailing list&lt;/a&gt; if you are interested to hear more news.&lt;br /&gt;&lt;br /&gt;&lt;hr style="display:block"/&gt;&lt;br /&gt;&lt;a name="footnote-1"&gt;&lt;/a&gt;[0] a few notes on how I tested - both clients and the &lt;a href=""&gt;grizzly-sendfile-server&lt;/a&gt; were running on my mac laptop using Java 6 and server vm. I set the ramp up period to 1min so that the server can warm up and then I started counting downloads for 10min and calculated the result. Files used for the tests were 1KB, 200KB, 500KB, 1MB 20MB and 100MB large and equaly represented. All the tests passed with 0 errors. A download is successful when the length, md5 checksum and http headers match expected values.&lt;br /&gt;&lt;a name="footnote-1"&gt;&lt;/a&gt;[1] performance - ability to process a single download quickly&lt;br /&gt;&lt;a name="footnote-2"&gt;&lt;/a&gt;[2] scalability - ability to process large number of concurrent downloads at acceptable performance&lt;br /&gt;&lt;a name="footnote-3"&gt;&lt;/a&gt;[3] init time - duration from the time a request is made, until the first bytes of the http response body (not headers) are received&lt;br /&gt;&lt;a name="footnote-4"&gt;&lt;/a&gt;[4] avg speed - for 100MB files. The smaller the file the lower the effective download speed because of the overhead associated with a download execution.&lt;br /&gt;&lt;a name="footnote-5"&gt;&lt;/a&gt;[5] EBA (81k) - EqualBlockingAlgorithm with buffer size 81KB (the default socket buffer size on mac)&lt;br /&gt;&lt;a name="footnote-6"&gt;&lt;/a&gt;[6] EBA (40k) - EqualBlockingAlgorithm with buffer size 40KB&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-1840308271841899595?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/jjGa2ljnVes" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/1840308271841899595/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=1840308271841899595" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/1840308271841899595?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/1840308271841899595?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/jjGa2ljnVes/grizzly-sendfile-and-comparison-of.html" title="grizzly-sendfile and Comparison of Blocking and NonBlocking IO" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_p64VvtgZDp4/Sgc_NPFQKKI/AAAAAAAAAOY/Iy1jDGE_MyU/s72-c/Blocking_SingleBurst_Algorithm.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://blog.igorminar.com/2009/05/grizzly-sendfile-and-comparison-of.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkACSHo_cCp7ImA9WxVbFUk.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-7154859016006159489</id><published>2009-03-31T17:05:00.000-07:00</published><updated>2009-03-31T17:12:49.448-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-03-31T17:12:49.448-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Java" /><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><category scheme="http://www.blogger.com/atom/ns#" term="SunWikis" /><title>Identifying ThreadLocal Memory Leaks in JavaEE Web Apps</title><content type="html">A few weeks ago &lt;a href="http://wikis.sun.com/"&gt;wikis.sun.com&lt;/a&gt; powered by &lt;a href="http://www.atlassian.com/software/confluence"&gt;Confluence "Enterprise" Wiki&lt;/a&gt; grew beyond yet another invisible line that triggered intermittent instabilities. Oh boy, how I love these moments. This time the issue was that Confluence just kept on running out of memory. Increasing the heap didn't help, even breaking the 32bit barrier and using a 64bit JVM was not good enough to keep the app running for more than 24 hours.&lt;br /&gt;&lt;br /&gt;The Xmx size of the heap suggested that something was out of order. It was time to take a heap dump using &lt;code&gt;jmap&lt;/code&gt; and check what was consuming so much memory. I tried &lt;code&gt;jhat&lt;/code&gt; to analyze the heap dump, but 3.5GB dump was just too much for it. The next tool I used was &lt;a href="http://www.alphaworks.ibm.com/tech/heapanalyzer"&gt;IBM's Heap Analyzer&lt;/a&gt; - a decent tool, which was able to read the dump, but consumed a lot of memory in order to do so (~8GB), and was pretty hard to use once the dump was processed.&lt;br /&gt;&lt;br /&gt;While looking for more heap analyzing tools, I found SAP Memory Analyzer, now known as &lt;a href="http://www.eclipse.org/mat/"&gt;Eclipse Memory Analyzer, a.k.a MAT&lt;/a&gt;. I thought "What the heck does SAP know about JVM?" and reluctantly gave it a try, only to find out how prejudiced I was. MAT is a really wonderful tool, which was able to process the heap really quickly, visualize the heap in a easy-to-navigate way, use special algorithms to find suspicious memory regions, and all of that while using only ~2GB of memory. An excellent preso that walks through MAT features and how heap and memory leaks work, can be found &lt;a href="http://jazoon.com/jazoon07/en/conference/presentationdetails.html?type=sid&amp;amp;detail=2160"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Thanks to MAT I was able to create two bug reports for folks at Atlassian (&lt;a href="http://jira.atlassian.com/browse/CONF-14988"&gt;CONF-14988&lt;/a&gt;, &lt;a href="http://jira.atlassian.com/browse/CONF-14989"&gt;CONF-14989&lt;/a&gt;). The only feature I missed was some kind of PDF or HTML export, but I did quite well with using &lt;a href="http://skitch.com/"&gt;Skitch&lt;/a&gt; to take screenshots and annotate them.&lt;br /&gt;&lt;br /&gt;One of the leaks was confirmed right away, while it wasn't clear what was causing &lt;a href="http://jira.atlassian.com/browse/CONF-14988"&gt;the other one&lt;/a&gt;. All we knew was that significant amounts of memory were retained via &lt;a href="http://java.sun.com/javase/6/docs/api/java/lang/ThreadLocal.html"&gt;ThreadLocal&lt;/a&gt; variables. More debugging was in order.&lt;br /&gt;&lt;br /&gt;I got this idea to create a servlet filter, that would inspect the thread-local store for the thread currently processing the request and log any thread-local references that exist before the request is dispatched down the chain and also when it comes back. Such a servlet could be packaged as a &lt;a href="http://confluence.atlassian.com/display/DOC/Servlet+Filter+Plugins"&gt;Confluence Servlet Filter Plugin&lt;/a&gt;, so that it is convenient to develop and deploy it.&lt;br /&gt;&lt;br /&gt;There was only one problem with this idea, the thread-local store is a private field of the &lt;code&gt;Thread&lt;/code&gt; class and is in fact implemented as an inner class with a package default access - kinda hard to get your hands on to. Thankfully &lt;a href="http://www.chuckcaplan.com/blog/archives/2005/08/java_private_do_1.html"&gt;private stuff is not necessarily private in Java&lt;/a&gt;, if you get your hands dirty with reflection code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Thread thread = Thread.currentThread();&lt;br /&gt;&lt;br /&gt;Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");&lt;br /&gt;threadLocalsField.setAccessible(true);&lt;br /&gt;&lt;br /&gt;Class threadLocalMapKlazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");&lt;br /&gt;Field tableField = threadLocalMapKlazz.getDeclaredField("table");&lt;br /&gt;tableField.setAccessible(true);&lt;br /&gt;&lt;br /&gt;Object table = tableField.get(threadLocalsField.get(thread));&lt;br /&gt;&lt;br /&gt;int threadLocalCount = Array.getLength(table);&lt;br /&gt;StringBuilder sb = new StringBuilder();&lt;br /&gt;StringBuilder classSb = new StringBuilder();&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;int leakCount = 0;&lt;br /&gt;&lt;br /&gt;for (int i=0; i &lt; threadLocalCount; i++) {&lt;br /&gt;    Object entry = Array.get(table, i);&lt;br /&gt;    if (entry != null) {&lt;br /&gt;        Field valueField = entry.getClass().getDeclaredField("value");&lt;br /&gt;        valueField.setAccessible(true);&lt;br /&gt;        Object value = valueField.get(entry);&lt;br /&gt;        if (value != null) {&lt;br /&gt;            classSb.append(value.getClass().getName()).append(", ");&lt;br /&gt;        } else {&lt;br /&gt;            classSb.append("null, ");&lt;br /&gt;        }&lt;br /&gt;        leakCount++;&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;sb.append("possible ThreadLocal leaks: ")&lt;br /&gt;        .append(leakCount)&lt;br /&gt;        .append(" of ")&lt;br /&gt;        .append(threadLocalCount)&lt;br /&gt;        .append(" = [")&lt;br /&gt;        .append(classSb.substring(0, classSb.length() - 2))&lt;br /&gt;        .append("] ");&lt;br /&gt;&lt;br /&gt;logger.warn(sb);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A simple plugin like this, &lt;a href="http://jira.atlassian.com/browse/CONF-14988?focusedCommentId=151770&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#action_151770"&gt;was able to confirm&lt;/a&gt; that the leaked &lt;code&gt;SAXParser&lt;/code&gt; instances are created and stored as thread-local variables somewhere within the code that exports content as PDF. That is good enough info to pinpoint the exact line of code that creates the thread-local instance by &lt;a href="http://blog.igorminar.com/2008/06/btrace-dtrace-for-java.html"&gt;BTrace&lt;/a&gt; (or code review), but that's a story for a separate blog post.&lt;br /&gt;&lt;br /&gt;The morale of the story: &lt;code&gt;ThreadLocal&lt;/code&gt; variables are a very powerful feature, which as is common for powerful stuff can result in a lot of nasty things when not used properly. Hopefully all the info I provided to Atlassian will be enough to get a speedy fix for the issue and bring stability to wikis.sun.com - at least until we step over the next "invisible line".&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-7154859016006159489?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/vASYSuH4eBs" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/7154859016006159489/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=7154859016006159489" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/7154859016006159489?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/7154859016006159489?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/vASYSuH4eBs/identifying-threadlocal-memory-leaks-in.html" title="Identifying ThreadLocal Memory Leaks in JavaEE Web Apps" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total><feedburner:origLink>http://blog.igorminar.com/2009/03/identifying-threadlocal-memory-leaks-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0YNQXs7eyp7ImA9WxVQGU0.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-2010648548288377382</id><published>2009-02-05T20:52:00.000-08:00</published><updated>2009-02-05T22:19:50.503-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-02-05T22:19:50.503-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="grizzly-sendfile" /><category scheme="http://www.blogger.com/atom/ns#" term="JRuby" /><category scheme="http://www.blogger.com/atom/ns#" term="Java" /><category scheme="http://www.blogger.com/atom/ns#" term="Projects" /><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><category scheme="http://www.blogger.com/atom/ns#" term="Mediacast" /><title>Announcing grizzly-sendfile!</title><content type="html">It's my pleasure to finally announce &lt;a href="http://kenai.com/projects/grizzly-sendfile/pages/Home"&gt;grizzly-sendfile&lt;/a&gt; v0.2 - the first stable version of a project that I started after I got one of those "Sudden Burst of Ideas" last summer.&lt;br /&gt;&lt;br /&gt;For people who follow the &lt;a href="https://grizzly.dev.java.net/"&gt;grizzly&lt;/a&gt; development or &lt;a href="http://mediacast.sun.com/"&gt;mediacast.sun.com&lt;/a&gt;, this is not exactly hot news. grizzly-sendfile has been used by mediacast since &lt;a href="http://wikis.sun.com/display/mediacast/2008/09/17/Mediacast+2.3+Deployment"&gt;last September&lt;/a&gt; and mentioned on the grizzly mailing list several times since then, but I haven't had time to promote it and explain what it does and how it works, so here I go.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;If you don't care about my diary notes, skip down to "What is grizzly-sendfile".&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;A bit of background: the whole story goes back to the end of 2007 when a bunch of us where finishing up the &lt;a href="http://blog.igorminar.com/2008/01/jruby-on-rails-rewrite-of.html"&gt;rewrite&lt;/a&gt; of mediacast.sun.com in &lt;a href="http://jruby.codehaus.org/"&gt;JRuby&lt;/a&gt; on Rails. At that time we realized that one of the most painful parts of the rewrite would be implementing the file streaming functionality. Back then Rails was single-threaded (&lt;a href="http://blog.headius.com/2008/08/qa-what-thread-safe-rails-means.html"&gt;not&lt;/a&gt; &lt;a href="http://guides.rubyonrails.org/2_2_release_notes.html#_thread_safety"&gt;any&lt;/a&gt; &lt;a href="http://blog.igorminar.com/2009/01/benchmarking-jruby-on-rails.html"&gt;more&lt;/a&gt;, yay!), so sending the data from rails was not an option. Fortunately, my then-colleague Peter, came up with an idea to use a servlet filter to intercept empty download responses from rails and stream the files from this filter. That did the trick for us, but it was a pretty ugly solution that was unreliable from time to time and was PITA to extend and maintain.&lt;br /&gt;&lt;br /&gt;At around this time, I learned about X-Sendfile - a not well known http header - that some webservers (e.g. apache and ligttpd) support. This header  could be used to offload file transfers from an application to the web server. Rails supports it natively via the &lt;code&gt;:x_sendfile&lt;/code&gt; option of &lt;a href="http://api.rubyonrails.org/classes/ActionController/Streaming.html#M000401"&gt;&lt;code&gt;send_file&lt;/code&gt;&lt;/a&gt; method.&lt;br /&gt;&lt;br /&gt;I started looking for the X-Sendfile support in &lt;a href="https://glassfish.dev.java.net/"&gt;GlassFish&lt;/a&gt;, which we have been using at mediacast, but it was missing. After some emails with glassfish and grizzly folks, mainly &lt;a href="http://weblogs.java.net/blog/jfarcand/"&gt;Jean-Francois&lt;/a&gt;, I learned that the core component of glassfish called &lt;a href="https://grizzly.dev.java.net/"&gt;grizzly&lt;/a&gt; could be extended via custom filters, which could implement this functionality.&lt;br /&gt;&lt;br /&gt;The idea stuck in my head for a few weeks. I looked up some info on grizzly and &lt;a href="http://en.wikipedia.org/wiki/New_I/O"&gt;NIO&lt;/a&gt; and then during one overnight drive to San Diego, I designed grizzly-sendfile in my head. It took many nights and a few weekends to get it into reasonable shape and test it under load with some custom &lt;a href="http://faban.sunsource.net/"&gt;faban&lt;/a&gt; benchmarks that I had to write, but in late August I had version 0.1 and was able to "sell" it to &lt;a href="http://blogs.sun.com/rama"&gt;Rama&lt;/a&gt; as a replacement of the servlet filter madness that we were using at mediacast.&lt;br /&gt;&lt;br /&gt;Except for a few initial bugs that showed up under some unusual circumstances, the 0.1 version was very stable. A few minor 0.1.x releases were followed by 0.2 version, which was installed on mediacast servers some time in November. Since then I've worked on docs and setting up &lt;a href="http://kenai.com/projects/grizzly-sendfile"&gt;the project&lt;/a&gt; at &lt;a href="http://kenai.com/"&gt;kenai.com&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;What is grizzly-sendfile?&lt;/h3&gt;From the &lt;a href="http://kenai.com/projects/grizzly-sendfile/pages/Home"&gt;wiki&lt;/a&gt;:&lt;br /&gt;grizzly-sendfile is an extension for grizzly - a NIO framework that among other things powers GlassFish application server.&lt;br /&gt;&lt;br /&gt;The goal of this extension is to facilitate an efficient file transfer functionality, which would allow applications to delegate file transfers to the application server, while retaining control over which file to send, access control or any other application specific logic.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;How does it work?&lt;/h3&gt;By mixing some NIO "magic" and leveraging code of the hard working grizzly team, I was able to come up with an ARP (asynchronous request processing) filter for grizzly. This filter can be easily plugged in to grizzly (and glassfish v2) and will intercept all the responses that contain &lt;code&gt;X-Sendfile&lt;/code&gt; header. The value of this header is the path of the file that the application that processed the request wants to send to the client.&lt;br /&gt;&lt;br /&gt;All that an application needs to do is to set the header. In Java a simple example of such a code looks like this:&lt;pre&gt; response.setHeader("X-Sendfile", "/path/to/file.avi");&lt;/pre&gt;&lt;br /&gt;In Rails, it looks even nicer:&lt;pre&gt;send_file '/path/to.png', :x_sendfile =&gt; true&lt;/pre&gt;&lt;br /&gt;That's it, grizzly-sendfile will take care of the rest.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_p64VvtgZDp4/SYut9yazg1I/AAAAAAAAAMw/8Z6La5M7k1k/s1600-h/grizzly-sendfile_architecture.png"&gt;&lt;img style="border: medium none ; margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 229px;" src="http://4.bp.blogspot.com/_p64VvtgZDp4/SYut9yazg1I/AAAAAAAAAMw/8Z6La5M7k1k/s400/grizzly-sendfile_architecture.png" alt="" id="BLOGGER_PHOTO_ID_5299520663549346642" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Why should you care?&lt;/h3&gt;&lt;br /&gt;For me it was all about keeping my code clean and solving problems at layers where they made the most sense. Then it was also about &lt;a href="http://kenai.com/projects/grizzly-sendfile/pages/Benchmarks"&gt;performance and scalability&lt;/a&gt; - the kind of stuff that one can do with NIO, can't be now done in JavaEE because of its synchronous nature. And then of course it was about having full control over downloads (like &lt;a href="http://kenai.com/projects/grizzly-sendfile/pages/Plugins#DownloadResultSender_Plugin"&gt;successful download notification&lt;/a&gt; and other customizations that are possible via &lt;a href="http://kenai.com/projects/grizzly-sendfile/pages/Plugins"&gt;grizzly-sendfile plugins&lt;/a&gt;). Oh, and I must mention &lt;a href="http://kenai.com/projects/grizzly-sendfile/pages/JMXInstrumentation"&gt;JMX monitoring&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://kenai.com/attachments/wiki_images/grizzly-sendfile/grizzly-sendfile_jmx.png"&gt;&lt;img style="border: medium none ; margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 600px;" src="http://kenai.com/attachments/wiki_images/grizzly-sendfile/grizzly-sendfile_jmx.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;What's next?&lt;/h3&gt;There is a lot of stuff on my &lt;a href="http://kenai.com/projects/grizzly-sendfile/pages/Roadmap"&gt;roadmap&lt;/a&gt;. Two of the main missing features are partial downloads and glassfish v3 (grizzly 1.9.x) support. Then there is better monitoring and tons of performance and scalability tuning, which I haven't really focus on yet. A lot of the API still needs to be polished and cleaned-up. Also needed is a solid test suite that is more fine grained than the system/integration tests that I created with faban.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Can you use grizzly-sendfile?&lt;/h3&gt;Yeah, &lt;a href="http://kenai.com/projects/grizzly-sendfile/downloads"&gt;go for it&lt;/a&gt;. This is my pet project that I developed in my free time. The project is licensed under GPL2, so you can even grab the &lt;a href="http://kenai.com/projects/grizzly-sendfile/sources/mercurial/show"&gt;code&lt;/a&gt; if you want.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Can you help?&lt;/h3&gt;Sure. Code reviews, patches, suggestions and help with testing and documentation are more than welcome!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-2010648548288377382?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/Xc1wNhcRKgg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/2010648548288377382/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=2010648548288377382" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/2010648548288377382?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/2010648548288377382?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/Xc1wNhcRKgg/announcing-grizzly-sendfile.html" title="Announcing grizzly-sendfile!" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_p64VvtgZDp4/SYut9yazg1I/AAAAAAAAAMw/8Z6La5M7k1k/s72-c/grizzly-sendfile_architecture.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.igorminar.com/2009/02/announcing-grizzly-sendfile.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0cERnYyfSp7ImA9WxVQFE4.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-4814098732920095589</id><published>2009-01-31T13:39:00.000-08:00</published><updated>2009-01-31T13:56:47.895-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-31T13:56:47.895-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="JRuby" /><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><category scheme="http://www.blogger.com/atom/ns#" term="Rails" /><title>Benchmarking JRuby on Rails</title><content type="html">Last night, while working on a project I found a really neat use of Rails &lt;a href="http://api.rubyonrails.org/classes/ActionController/Components.html"&gt;Components&lt;/a&gt;, but I also noticed that this part of Rails is deprecated, among other reasons because it's slow.&lt;br /&gt;&lt;br /&gt;Well, how slow? During my quest to find out, I collected some interesting data, and even more importantly put JRuby and MRI Ruby face to face.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Disclaimer&lt;/b&gt;: the benchmarks were not done on a well isolated and specially configured test harness, but I did my best to gather data with informational value. All the components were used with OOB settings.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Setup&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;ruby 1.8.6 (2008-03-03 patchlevel 114) [universal-darwin9.0] + Mongrel Web Server 1.1.4&lt;/li&gt;&lt;li&gt;jruby 1.1.6 (ruby 1.8.6 patchlevel 114) (2008-12-17 rev 8388) [x86_64-java] + GlassFish gem version: 0.9.2&lt;/li&gt;&lt;li&gt;common backend: mysql5  5.0.75 Source distribution (InnoDB table engine, Rails pool set to 30)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Benchmarks&lt;/h3&gt;I used an excellent high quality benchmarking framework &lt;a href="http://faban.sunsource.net/"&gt;Faban&lt;/a&gt; for my tests. I was lazy, so I only used &lt;a href="http://blogs.sun.com/shanti/entry/http_load_generator"&gt;fhb&lt;/a&gt; (very similar to &lt;a href="http://httpd.apache.org/docs/2.0/programs/ab.html"&gt;ab&lt;/a&gt;, but without &lt;a href="http://weblogs.java.net/blog/sdo/archive/2007/03/ab_considered_h.html"&gt;its flaws&lt;/a&gt;) to invoke simple benchmarks:&lt;ul&gt;&lt;li&gt;simple request benchmark: bin/fhb -r 60/120/5 -c 10 http://localhost:3000/buckets/1&lt;/li&gt;&lt;li&gt;component request benchmark:  bin/fhb -r 60/120/5 -c 10 http://localhost:3000/bucket1/object1&lt;/li&gt;&lt;/ul&gt;Both tests were run with JRuby as well as with RMI Ruby and in addition to that I ran the tests with Rails in single-threaded as well as multi-threaded modes. I didn't use mongler clusters or glassfish pooled instances - there was always only one Ruby instance serving all the requests.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Results&lt;/h3&gt;&lt;pre&gt;ruby 1.8.6 + mongrel&lt;br /&gt;---------------------------------&lt;br /&gt;simple action + single-threaded:&lt;br /&gt;ops/sec: 210.900&lt;br /&gt;% errors: 0.0&lt;br /&gt;avg. time: 0.047&lt;br /&gt;max time: 0.382&lt;br /&gt;90th %: 0.095&lt;br /&gt;&lt;br /&gt;simple action + multi-threaded:&lt;br /&gt;ops/sec: 226.483&lt;br /&gt;% errors: 0.0&lt;br /&gt;avg. time: 0.044&lt;br /&gt;max time: 0.180&lt;br /&gt;90th %: 0.095&lt;br /&gt;&lt;br /&gt;component action + single-threaded:&lt;br /&gt;ops/sec: 132.950&lt;br /&gt;% errors: 0.0&lt;br /&gt;avg. time: 0.075&lt;br /&gt;max time: 0.214&lt;br /&gt;90th %: 0.130&lt;br /&gt;&lt;br /&gt;component action + multi-threaded:&lt;br /&gt;ops/sec: 131.775&lt;br /&gt;% errors: 0.0&lt;br /&gt;avg. time: 0.076&lt;br /&gt;max time: 0.279&lt;br /&gt;90th %: 0.125&lt;br /&gt;&lt;br /&gt;jruby 1.2.6 + glassfish gem 0.9.2&lt;br /&gt;----------------------------------&lt;br /&gt;simple action + single-threaded:&lt;br /&gt;ops/sec: 141.417&lt;br /&gt;% errors: 0.0&lt;br /&gt;avg. time: 0.070&lt;br /&gt;max time: 0.259&lt;br /&gt;90th %: 0.115&lt;br /&gt;&lt;br /&gt;simple action + multi-threaded:&lt;br /&gt;ops/sec: 247.333&lt;br /&gt;% errors: 0.0&lt;br /&gt;avg. time: 0.040&lt;br /&gt;max time: 0.318&lt;br /&gt;90th %: 0.065&lt;br /&gt;&lt;br /&gt;component action + single-threaded:&lt;br /&gt;ops/sec: 107.858&lt;br /&gt;% errors: 0.0&lt;br /&gt;avg. time: 0.092&lt;br /&gt;max time: 0.595&lt;br /&gt;90th %: 0.145&lt;br /&gt;&lt;br /&gt;component action + multi-threaded:&lt;br /&gt;ops/sec: 179.042&lt;br /&gt;% errors: 0.0&lt;br /&gt;avg. time: 0.055&lt;br /&gt;max time: 0.357&lt;br /&gt;90th %: 0.085&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;table style="width: 458px; height: 117px;"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Platform/Action&lt;/th&gt;&lt;th&gt;Simple&lt;/th&gt;&lt;th&gt;+/-&lt;/th&gt;&lt;th&gt;Component&lt;/th&gt;&lt;th&gt;+/-&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;Ruby ST&lt;br /&gt;&lt;/th&gt;&lt;td&gt;210ops&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;td&gt;132ops&lt;/td&gt;&lt;td&gt;0%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;th&gt;Ruby MT&lt;/th&gt;&lt;td&gt;226ops&lt;/td&gt;&lt;td&gt;7.62%&lt;/td&gt;&lt;td&gt;131ops&lt;/td&gt;&lt;td&gt;-0.76%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;th&gt;JRuby ST&lt;/th&gt;&lt;td&gt;141ops&lt;/td&gt;&lt;td&gt;-32.86%&lt;/td&gt;&lt;td&gt;107ops&lt;/td&gt;&lt;td&gt;-18.94%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;th&gt;JRuby MT&lt;/th&gt;&lt;td&gt;247ops&lt;/td&gt;&lt;td&gt;17.62%&lt;/td&gt;&lt;td&gt;179ops&lt;/td&gt;&lt;td&gt;35.61%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;(ST - single-threaded; MT - multi-threaded)&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Conclusion&lt;/h3&gt;From my tests it appears that MRI is faster in single threaded mode, but JRuby makes up for the loss big time in the multi-threaded tests. It's also interesting to see that the multi-threaded mode gives MRI(green threads) a performance boost, but it's nowhere close to the boost that JRuby(native threads) can squeeze out from using multiple threads.&lt;br /&gt;&lt;br /&gt;During the tests I noticed that rails was reporting more times spent in the db when using JRuby (2-80ms) compared to MRI (1-3ms). I don't know how reliable this data is but I wonder if this is the bottleneck that is holding JRuby back in the single threaded mode.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-4814098732920095589?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/-ClhGFVbu-I" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/4814098732920095589/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=4814098732920095589" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/4814098732920095589?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/4814098732920095589?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/-ClhGFVbu-I/benchmarking-jruby-on-rails.html" title="Benchmarking JRuby on Rails" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://blog.igorminar.com/2009/01/benchmarking-jruby-on-rails.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck8ASH0_eip7ImA9WxVSGUg.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-4122874931418174796</id><published>2009-01-13T20:43:00.000-08:00</published><updated>2009-01-14T08:34:09.342-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-14T08:34:09.342-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="MacBook Pro" /><category scheme="http://www.blogger.com/atom/ns#" term="Solaris" /><category scheme="http://www.blogger.com/atom/ns#" term="MacOS" /><title>Using ZFS with Mac OS X 10.5</title><content type="html">A few days ago I got a new MacBook Pro. While waiting for it to be delivered, I started thinking about how I want to layout the installation of the OS. For a long long time I wanted to try to use &lt;a href="http://opensolaris.org/os/community/zfs/"&gt;ZFS file system&lt;/a&gt; on Mac and this looked like a wonderful opportunity. Getting rid of HFS+, which was causing me lots of problems (especially its case insensitive re-incarnation), sounds like a dream come true.&lt;br /&gt;&lt;br /&gt;If you've never heard of ZFS before, check out this good &lt;a href="http://opensolaris.org/os/community/zfs/demos/basics/"&gt;5min screencast&lt;/a&gt; of some of the important features.&lt;br /&gt;&lt;br /&gt;A brief google search revealed that there are several people using and developing ZFS for Mac. There is a Mac ZFS porting project at &lt;a href="http://zfs.macosforge.org/"&gt;http://zfs.macosforge.org&lt;/a&gt; and I found a lot of good info at &lt;a href="http://alblue.blogspot.com/search/label/zfs"&gt;AlBlue's blog&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Some noteworthy info:&lt;ul&gt;&lt;li&gt;The current ZFS port (build 119) is based on ZFS code that shipped with Solaris build 72&lt;/li&gt;&lt;li&gt;It's currently not possible to boot Mac OS X from a ZFS filesystem&lt;/li&gt;&lt;li&gt;Finder integration is not perfect yet - Finder lists a ZFS pool as an unmountable drive under devices&lt;/li&gt;&lt;li&gt;There are several reports of kernel panics, most of which appeared in connection to the use of cheap external USB disks (I haven't experienced any)&lt;/li&gt;&lt;li&gt;There are &lt;a href="http://zfs.macosforge.org/trac/wiki/issues"&gt;a bunch of minor issues&lt;/a&gt;, which I'm sure will eventually go away.&lt;/li&gt;&lt;/ul&gt;None of the above was a show stopper for me, so I went ahead with the installation. My plan was simple - repartition the internal hard drive to a small bootable partition and a large partition used by ZFS, which will hold my home directory and other filesystems.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Install ZFS&lt;/h3&gt;Even though MacOS X 10.5 comes with ZFS support, it's only a read-only support. In order to be able to really use ZFS, full ZFS implementation must be installed.&lt;br /&gt;&lt;br /&gt;The installation is very simple and can be done by following these instructions: &lt;a href="http://zfs.macosforge.org/trac/wiki/downloads"&gt;http://zfs.macosforge.org/trac/wiki/downloads&lt;/a&gt;. Alternatively, AlBlue created a &lt;a href="http://alblue.blogspot.com/2008/11/zfs-119-on-mac-os-x.html"&gt;fancy installer&lt;/a&gt; for the lazy ones out there.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Repartition Disk&lt;/h3&gt;Once ZFS is installed and the OS was rebooted, I could repartition the internal disk. If you are using an external hard drive, you'll most likely need to use &lt;code&gt;zpool&lt;/code&gt; command instead.&lt;br /&gt;&lt;br /&gt;First let's check what the disk looks like:&lt;pre&gt;$ diskutil list&lt;br /&gt;/dev/disk0&lt;br /&gt;#:                       TYPE NAME                    SIZE       IDENTIFIER&lt;br /&gt;0:      GUID_partition_scheme                        *298.1 Gi   disk0&lt;br /&gt;1:                        EFI                         200.0 Mi   disk0s1&lt;br /&gt;2:                  Apple_HFS boot                    297.8 Gi   disk0s2&lt;/pre&gt;Good, the internal disk was identified as &lt;code&gt;/dev/disk0&lt;/code&gt; and it currently contains an EFI (boot) slice and ~300G data slice/partition.&lt;br /&gt;&lt;br /&gt;Let's repartition the disk so that it contains two data partitions.&lt;pre&gt;$ sudo diskutil resizeVolume disk0s2 40G ZFS tank 257G&lt;br /&gt;Password:&lt;br /&gt;Started resizing on disk disk0s2 boot&lt;br /&gt;Verifying&lt;br /&gt;Resizing Volume&lt;br /&gt;Adjusting Partitions&lt;br /&gt;Formatting new partitions&lt;br /&gt;Formatting disk0s3 as ZFS File System with name tank&lt;br /&gt;[ + 0%..10%..20%..30%..40%..50%..60%..70%..80%..90%..100% ]&lt;br /&gt;Finished resizing on disk disk0&lt;br /&gt;/dev/disk0&lt;br /&gt;#:                       TYPE NAME                    SIZE       IDENTIFIER&lt;br /&gt;0:      GUID_partition_scheme                        *298.1 Gi   disk0&lt;br /&gt;1:                        EFI                         200.0 Mi   disk0s1&lt;br /&gt;2:                  Apple_HFS boot                    39.9 Gi    disk0s2&lt;br /&gt;3:                        ZFS tank                    252.0 Gi   disk0s3&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Great, the disk was repartitioned and the existing data partition, which I call &lt;code&gt;boot&lt;/code&gt;, was resized into a smaller 40GB partition and the extra space was used to create a ZFS pool called &lt;code&gt;tank&lt;/code&gt;. Btw all the data on the &lt;code&gt;boot&lt;/code&gt; partition was preserved.&lt;br /&gt;&lt;br /&gt;Let's check my new pool:&lt;pre&gt;$ zpool list&lt;br /&gt;NAME                    SIZE    USED   AVAIL    CAP  HEALTH     ALTROOT&lt;br /&gt;tank                    256G    360K    256G     0%  ONLINE     -&lt;/pre&gt;&lt;pre&gt;$ zpool status&lt;br /&gt;pool: tank&lt;br /&gt;state: ONLINE&lt;br /&gt;status: The pool is formatted using an older on-disk format.  The pool can&lt;br /&gt; still be used, but some features are unavailable.&lt;br /&gt;action: Upgrade the pool using 'zpool upgrade'.  Once this is done, the&lt;br /&gt; pool will no longer be accessible on older software versions.&lt;br /&gt;scrub: none requested&lt;br /&gt;config:&lt;br /&gt;&lt;br /&gt; NAME        STATE     READ WRITE CKSUM&lt;br /&gt; tank        ONLINE       0     0     0&lt;br /&gt;   disk0s3   ONLINE       0     0     0&lt;br /&gt;&lt;br /&gt;errors: No known data errors&lt;/pre&gt;The warning above just means that a new ZFS storage format is available but is not used by the current pool. As far as I could find there are no benefits for upgrading to the new format on Mac, but if I did, I would lose compatibility with Macs that have only the read-only ZFS support.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Create Filesystems&lt;/h3&gt;So now that the new pool exists, I can create a shiny new filesystem using a single command:&lt;pre&gt;$ sudo zfs create tank/me3x&lt;br /&gt;$ zfs list&lt;br /&gt;NAME        USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;tank        388K   252G   270K  /Volumes/tank&lt;br /&gt;tank/me3x    19K   252G    19K  /Volumes/tank/me3x&lt;/pre&gt;To configure this new filesystem as my home directory, I created a temporary admin account, logged in under this account and mounted the ZFS fs as /Users/me3x:&lt;pre&gt;$ sudo mv /Users/me3x /Users/me3x.hfs&lt;br /&gt;$ sudo zfs set mountpoint=/Users/me3x tank/me3x&lt;br /&gt;$ sudo cp -rp /Users/me3x.hfs /Users/me3x&lt;/pre&gt;That's it. My Mac account now resides on a ZFS file system. Now I can finally enjoy all the benefits of using ZFS on my &lt;a href="http://opensolaris.org/os/"&gt;OpenSolaris&lt;/a&gt; box in my office as well as on my Mac. Bye bye HFS, I won't miss you! &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-4122874931418174796?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/lOgusv8Iw_Q" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/4122874931418174796/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=4122874931418174796" title="27 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/4122874931418174796?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/4122874931418174796?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/lOgusv8Iw_Q/using-zfs-with-mac-os-x-105.html" title="Using ZFS with Mac OS X 10.5" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">27</thr:total><feedburner:origLink>http://blog.igorminar.com/2009/01/using-zfs-with-mac-os-x-105.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0EBR3o9eyp7ImA9WxVSF0w.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-7339631980403720090</id><published>2009-01-11T16:16:00.000-08:00</published><updated>2009-01-11T16:20:56.463-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2009-01-11T16:20:56.463-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="JRuby" /><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><category scheme="http://www.blogger.com/atom/ns#" term="Rails" /><title>Freezing activerecord-jdbc Gems into a Rails Project</title><content type="html">Over the Christmas break a Slovak friend of mine (Hi Martin!) asked me to build a simple book library management app for a school in the Philippines where he's been volunteering for the past year. I thought to my self that if someone can volunteer one year of his life in such an amazing way, I could spend a few hours to help him out too.&lt;br /&gt;&lt;br /&gt;Since from his description it was obvious that he was looking for a low maintenance solution, I though that a rails application with an embedded database would be a good choice. I worked with &lt;a href="http://db.apache.org/derby/"&gt;derby (JavaDB)&lt;/a&gt; in the past and I knew that derby drivers were already available as an &lt;a href="http://kenai.com/projects/activerecord-jdbc/pages/Home"&gt;active-record adapter gem&lt;/a&gt;, so I thought that it would be pretty simple to set up dev environment using Rails, JRuby, and embedded derby db. Surprisingly there were a few issues along the way.&lt;br /&gt;&lt;br /&gt;I started with defining the database config in &lt;code&gt;config/database.yml&lt;/code&gt;:&lt;pre&gt;development:&lt;br /&gt;adapter: jdbcderby&lt;br /&gt;database: db/library_development&lt;br /&gt;pool: 5&lt;br /&gt;timeout: 5000&lt;br /&gt;...&lt;br /&gt;...&lt;/pre&gt;The database files for the dev db will be stored under &lt;code&gt;RAILS_ROOT/db/library_development&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Secondly I specified the gem dependency in &lt;code&gt;config/environment.rb&lt;/code&gt; (you gotta love this Rails 2.1+ feature):&lt;pre&gt;Rails::Initializer.run do |config|&lt;br /&gt;...&lt;br /&gt;config.gem "activerecord-jdbcderby-adapter", :version =&gt; '0.9', :lib =&gt; 'active_record/connection_adapters/jdbcderby_adapter'&lt;br /&gt;...&lt;br /&gt;end&lt;/pre&gt;Note that you must specify the &lt;code&gt;:lib&lt;/code&gt; parameter, otherwise Rails won't be able to initialize the gem and you'll end up with:&lt;pre&gt;no such file to load -- activerecord-jdbcderby-adapter&lt;/pre&gt;So far so good. Now let's install the gems we depend on:&lt;pre&gt;$ jruby -S rake gems:install&lt;br /&gt;(in /Users/me3x/Development/library)&lt;br /&gt;rake aborted!&lt;br /&gt;Please install the jdbcderby adapter: `gem install activerecord-jdbcderby-adapter` (no such file to load -- active_record/connection_adapters/jdbcderby_adapter)&lt;br /&gt;&lt;br /&gt;(See full trace by running task with --trace)&lt;/pre&gt;Huh? I asked rake to install gems and I get an error that I need to install gems first? It turns out that this error comes from ActiveRecord, which tries to initialize db according to &lt;code&gt;database.yml&lt;/code&gt;, and only then &lt;code&gt;environment.rb&lt;/code&gt; gets to be read.&lt;br /&gt;&lt;br /&gt;Ok, so let's install the db dependencies manually:&lt;pre&gt;$ sudo jruby -S gem install activerecord-jdbcderby-adapter&lt;br /&gt;Password:&lt;br /&gt;JRuby limited openssl loaded. gem install jruby-openssl for full support.&lt;br /&gt;http://wiki.jruby.org/wiki/JRuby_Builtin_OpenSSL&lt;br /&gt;Successfully installed activerecord-jdbc-adapter-0.9&lt;br /&gt;Successfully installed jdbc-derby-10.3.2.1&lt;br /&gt;Successfully installed activerecord-jdbcderby-adapter-0.9&lt;br /&gt;3 gems installed&lt;br /&gt;Installing ri documentation for activerecord-jdbc-adapter-0.9...&lt;br /&gt;Installing ri documentation for jdbc-derby-10.3.2.1...&lt;br /&gt;Installing ri documentation for activerecord-jdbcderby-adapter-0.9...&lt;br /&gt;Installing RDoc documentation for activerecord-jdbc-adapter-0.9...&lt;br /&gt;Installing RDoc documentation for jdbc-derby-10.3.2.1...&lt;br /&gt;Installing RDoc documentation for activerecord-jdbcderby-adapter-0.9...&lt;/pre&gt;&lt;br /&gt;Cool, let's check if all the dependencies are available:&lt;pre&gt;$ jruby -S rake gems&lt;br /&gt;(in /Users/me3x/Development/library)&lt;br /&gt;- [I] activerecord-jdbcderby-adapter = 0.9&lt;br /&gt;- [I] activerecord-jdbc-adapter = 0.9&lt;br /&gt;- [I] jdbc-derby = 10.3.2.1&lt;br /&gt;&lt;br /&gt;I = Installed&lt;br /&gt;F = Frozen&lt;br /&gt;R = Framework (loaded before rails starts)&lt;/pre&gt;Yay, all dependencies are &lt;i&gt;installed&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;In the past when dependencies couldn't be declared in environment.rb, I found developing with frozen rails and gems much more manageable, especially when the app is being developed by more than one person. This also made for less deployment surprises. With the &lt;code&gt;config.gem&lt;/code&gt; defined dependencies, the situation changes quite a bit, but there are situations when it still makes sense to freeze gems into the project. So let's freeze the gems:&lt;pre&gt;$ jruby -S rake gems:unpack:dependencies&lt;br /&gt;(in /Users/me3x/Development/library)&lt;br /&gt;WARNING:  Installing to ~/.gem since /usr/local/jruby/jruby-1.1.6/lib/ruby/gems/1.8 and&lt;br /&gt;   /usr/local/jruby/current/bin aren't both writable.&lt;br /&gt;Unpacked gem: '/Users/me3x/Development/library/vendor/gems/activerecord-jdbcderby-adapter-0.9'&lt;br /&gt;Unpacked gem: '/Users/me3x/Development/library/vendor/gems/activerecord-jdbc-adapter-0.9'&lt;br /&gt;Unpacked gem: '/Users/me3x/Development/library/vendor/gems/jdbc-derby-10.3.2.1'&lt;/pre&gt;&lt;br /&gt;Looks good, let's check it:&lt;pre&gt;$ jruby -S rake gems&lt;br /&gt;(in /Users/me3x/Development/library)&lt;br /&gt;- [F] activerecord-jdbcderby-adapter = 0.9&lt;br /&gt;- [F] activerecord-jdbc-adapter = 0.9&lt;br /&gt;- [F] jdbc-derby = 10.3.2.1&lt;br /&gt;&lt;br /&gt;I = Installed&lt;br /&gt;F = Frozen&lt;br /&gt;R = Framework (loaded before rails starts)&lt;/pre&gt;Nice all the dependencies are now &lt;i&gt;frozen&lt;/i&gt;!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-7339631980403720090?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/_zqat4tOAlE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/7339631980403720090/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=7339631980403720090" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/7339631980403720090?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/7339631980403720090?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/_zqat4tOAlE/freezing-activerecord-jdbc-gems-into.html" title="Freezing activerecord-jdbc Gems into a Rails Project" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.igorminar.com/2009/01/freezing-activerecord-jdbc-gems-into.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CE4DQH84eSp7ImA9WxRWFE0.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-6179506886443323648</id><published>2008-10-30T14:42:00.000-07:00</published><updated>2008-10-30T14:42:51.131-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-10-30T14:42:51.131-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><category scheme="http://www.blogger.com/atom/ns#" term="Glassfish" /><title>How to Install a Glassfish Patch</title><content type="html">Recently I've been working quite extensively with &lt;a href="https://glassfish.dev.java.net"&gt;glassfish&lt;/a&gt; and had a need to apply a patch for some of the core code. &lt;br /&gt;&lt;br /&gt;To my big surprise I was not able to find the official way to apply patches to a glassfish installation. I found several patches posted on the issue tracker and paying customers have access to &lt;a href="http://blogs.sun.com/theaquarium/entry/new_patch_releases_for_sun"&gt;tested and supported patch bundles&lt;/a&gt;, which are released outside of the regular release cycle. But even some extensive googling didn't easily reveal how to apply them.&lt;br /&gt;&lt;br /&gt;Then luckily I found &lt;a href="http://www.nabble.com/Selectively-replacing-server-classes-td13456703.html"&gt;this discussion&lt;/a&gt; on the glassfish mailing list, which describes the process.&lt;br /&gt;&lt;br /&gt;To make life easier for others and google to index this information, here is a brief recap:&lt;ol&gt;&lt;li&gt;Create a directory &lt;code&gt;&amp;lt;glassfishroot&amp;gt;/lib/patches&lt;/code&gt;&lt;/li&gt;&lt;li&gt;Copy the jar with your patch into this directory&lt;/li&gt;&lt;li&gt;Edit your &lt;code&gt;&amp;lt;glassfishroot&amp;gt;/domain/&amp;lt;domainname&amp;gt;/config/domain.xml&lt;/code&gt; and add attribute &lt;code&gt;classpath-prefix="${com.sun.aas.installRoot}/lib/patches/&amp;lt;patchname.jar&amp;gt;"&lt;/code&gt; to the &lt;code&gt;&amp;lt;java-config ...&amp;gt;&lt;/li&gt; node&lt;/li&gt;&lt;li&gt;Restart your domain&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;I tested this with Glassfish v2, I'll have to check if it works with the upcoming v3 as well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-6179506886443323648?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/5JCY90JWs4o" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/6179506886443323648/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=6179506886443323648" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/6179506886443323648?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/6179506886443323648?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/5JCY90JWs4o/how-to-install-glassfish-patch.html" title="How to Install a Glassfish Patch" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/10/how-to-install-glassfish-patch.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0cBRno9cSp7ImA9WxRQGUk.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-1718141255510051649</id><published>2008-10-13T18:50:00.000-07:00</published><updated>2008-10-13T18:50:57.469-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-10-13T18:50:57.469-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Projects" /><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><category scheme="http://www.blogger.com/atom/ns#" term="SunWikis" /><title>My Confluence 3.0 Wishlist</title><content type="html">Confluence 2.9 was released last month and I've seen references to 2.10 in the Confluence issue tracker, so I expect to see it out in 1-2 months. That makes me think about what's next.&lt;br /&gt;&lt;br /&gt;As a part of my adventures of working on Sun's external wiki &lt;a href="http://wikis.sun.com/"&gt;wikis.sun.com&lt;/a&gt;, I've been working on Confluence plugins and even the Confluence core code for a year and a half now, adding new features, enhancing the existing features and very often fixing bugs. Sometimes it was trivial to enhance the code or fix a bug, other times it was not, but what I want to write about today are things that were not possible at all without irreversibly forking the code.&lt;br /&gt;&lt;br /&gt;Confluence 3.0 should be a version that really deserves to have the first digit incremented. Not because &lt;a href="http://confluence.atlassian.com/display/DOC/Confluence+Release+Cycle"&gt;marketing said it's time for that&lt;/a&gt;, but because the changes in the application are so significant.&lt;br /&gt;&lt;br /&gt;I'm sure that Atlassian has lots of ideas about what Confluence 3.0 should look like,  but Atlassian guys, in case you start to run out of ideas, here is my wish list:&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Fix the Database Schema&lt;/h3&gt;Confluence has been in development for years and the database schema definitely shows that. Since the database is the heart of the application, I think it deserves a lot of attention and major performance boost could be gained by doing a clean up.&lt;br /&gt;&lt;br /&gt;Specific improvements:&lt;ul&gt;&lt;li&gt;Establish and in the future enforce naming conventions&lt;/li&gt;&lt;li&gt;Replace all the natural foreign keys with surrogate keys, e.g. user name, spacekey, group name should be replaced with ids in all the referencing tables (this would finally allow &lt;a href="http://jira.atlassian.com/browse/CONF-4063"&gt;CONF-4063&lt;/a&gt; to be implemented)&lt;/li&gt;&lt;li&gt;Add caches for the lower function (&lt;a href="http://jira.atlassian.com/browse/CONF-10030"&gt;patch&lt;/a&gt;) and maybe &lt;a href="http://railscasts.com/episodes/23"&gt;counter caches&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Rework the Clustering&lt;/h3&gt;Clustering is usually supposed to fulfill two functions: scalability and robustness. In the case of Confluence mainly the second attribute is missing. In fact, I'd go as far as saying that a Confluence cluster is less robust than a single instance of Confluence. Why? Because the way it is implemented makes the entire cluster vulnerable when one node has problems.&lt;br /&gt;&lt;br /&gt;I personally experienced several cluster lock-ups or crashes, usually initiated by a separate Confluence bug, in which the effect was multiplied by the clustering code. One of such of these bugs: &lt;a href="http://jira.atlassian.com/browse/CONF-12319"&gt;CONF-12319&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.blogger.com/www.parleys.com/display/PARLEYS/Pragmatic+Clustering+Guide"&gt;Mike's presentation&lt;/a&gt; covers quite a few design goals behind the implementation in Confluence. Clustering can really get ugly and complicated and Mike covered it pretty well. Unfortunately the distributed share part of the clustering makes Confluence prone to problems.&lt;br /&gt;&lt;br /&gt;One of the clustering goals that Mike emphasizes in his presentation is that clustering should be "admin-friendly" (low admin overhead and easy setup). While I agree with the low overhead part, the easiness of setup should not compromise the goals which clustering is trying to fulfill in the first place. Clustering is for people who are serious about running Confluence, and as such should be expected to be qualified for the job.&lt;br /&gt;&lt;br /&gt;Specific improvements:&lt;ul&gt;&lt;li&gt;Either reevaluate the distributed share clustering so that it is super robust, or consider implementing clustering via a centralized share&lt;/li&gt;&lt;li&gt;Avoid shutting down the entire cluster when "cluster panic" is detected. A better solution, which avoids unnecessary downtime, would be to shut down all the nodes, except for the nodes properly clustered with the oldest node.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Clean Up the HTML and CSS Code&lt;/h3&gt;The html code that comes out of Confluence is horrendous. While the rendered output looks pretty pleasant, looking under the hood (browsing the source code in a browser) is not recommended for pregnant women, men with ED, high cholesterol, and generally not recommended for people over 50.&lt;br /&gt;&lt;br /&gt;Some improvements were done in this area in the recent releases, but all of them were just minor cosmetic surgeries. Confluence really needs major surgery that will bring the html code up to current standards. The benefit of this will be much faster page loads and code that is easier to maintain and enhance.&lt;br /&gt;&lt;br /&gt;Specific improvements:&lt;ul&gt;&lt;li&gt;Rewrite most of the templates and macros to make them XHTML 1.0 compliant&lt;/li&gt;&lt;li&gt;Minify and combine javascript and css files (&lt;a href="http://jira.atlassian.com/browse/CONF-8622"&gt;CONF-8622&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;Use &lt;a href="http://alistapart.com/articles/sprites"&gt;image sprites&lt;/a&gt; to even further speed up page loads (especially in the rich text editor)&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Redo the URI Namespace&lt;/h3&gt;Human friendly URIs and URLs are becoming more and more important on today's Internet. Confluence is not doing well in this area.&lt;br /&gt;&lt;br /&gt;Specific improvements:&lt;ul&gt;&lt;li&gt;&lt;code&gt;/display/MySpace/My+Page&lt;/code&gt; - is the &lt;code&gt;/display&lt;/code&gt; part really necessary? Can't we do &lt;code&gt;/MySpace/My+Page&lt;/code&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;/pages/diffpages.action?pageId=2490471&amp;amp;originalId=45714293&lt;/code&gt; - What is this? I don't know. How about: &lt;code&gt;/MyWiki/My+Page/diff/23:22&lt;/code&gt;. I think that actually means something. There might be a better format, this is just a thought.&lt;/li&gt;&lt;li&gt;I think in general redoing the URI name space using REST conventions would be interesting.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Improve Atlassian-Renderer&lt;/h3&gt;When I was creating come patches for the atlassian-renderer I was surprised to find that atlassian-render, the module responsible for rendering wiki markup into html is full of hardcoded html snippets. The main reason why this surprised me is that most of the Confluence code is pluggable, which allows for parts of the code to be replaced with a better version without a lot of problem. This is not the case with the render. And this presents two problems: it's not possible get Confluence to &lt;b&gt;directly&lt;/b&gt; render anything else than html (pdf and doc are only derived from the html), and it's not possible to use anything else than Confluence markup as the input for the renderer.&lt;br /&gt;&lt;br /&gt;The first problem makes me unable to render custom output like &lt;a href="http://en.wikipedia.org/wiki/DocBook"&gt;docbook&lt;/a&gt; or to improve the PDF output, which is pretty poor.&lt;br /&gt;&lt;br /&gt;The second issue means that all the customers that use Confluence are locked-in because all the content created via Confluence is Confluence-specific and can't be easily moved to a different wiki engine when needed.&lt;br /&gt;&lt;br /&gt;In my opinion the sooner all major wiki engine developers settle on one wiki markup standard the sooner we will all be better off. This might be especially difficult for Atlassian to swallow and implement, because they standardized on their own markup that they also use in their other products.&lt;br /&gt;&lt;br /&gt;An interesting initiative that is gaining a lot of traction is &lt;a href="http://www.wikicreole.org/"&gt;Creole&lt;/a&gt;, a standardized wiki markup. Confluence is one of the few major wiki players that doesn't support this initiative.&lt;br /&gt;&lt;br /&gt;Specific improvement:&lt;ul&gt;&lt;li&gt;Split the current renderer into two pluggable parts: parser and renderer&lt;/li&gt;&lt;li&gt;Implement Creole support (&lt;a href="http://jira.atlassian.com/browse/CONF-12077"&gt;CONF-12077&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Improve Developer Documentation&lt;br /&gt;&lt;/h3&gt; spent countless hours, especially in the beginnings trying to figure out how Confluence works and how Confluence plugins should be written. I learned some new tricks and that's the good part, the bad thing is that the experience could have been much better if the the code contained more javadocs comments and if the plugin interfaces and mainly the configuration file format was better documented.&lt;br /&gt;&lt;br /&gt;Specific improvements:&lt;ul&gt;&lt;li&gt;Add JavaDoc comments where missing&lt;/li&gt;&lt;li&gt;Finally provide &lt;b&gt;complete&lt;/b&gt; specification and documentation for the plugin config file (&lt;a href="http://jira.atlassian.com/browse/JRA-12183"&gt;JRA-12183&lt;/a&gt;)&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Thanks! :-)&lt;/h3&gt;That's about as much as I can think of for now. There are probably other things that I missed and then there enhancements around security, which I know are already on the roadmap.&lt;br /&gt;&lt;br /&gt;I understand that most of the changes above will create incompatibilities with many existing themes and plugins, but hey, Confluence 3 will happen only once EVAR and releases like this are expected to bring major incompatibilities. Data can always be migrated automatically and existing plugins and themes will be migrated when there are people interested in using them and proper migration instructions are provided.&lt;br /&gt;&lt;br /&gt;I hope that Confluence 3 will not be a "marketing" release, but instead something really cool that all users &lt;b&gt;and&lt;/b&gt; developers will enjoy working with.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-1718141255510051649?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/f3TGjEFcOoE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/1718141255510051649/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=1718141255510051649" title="10 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/1718141255510051649?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/1718141255510051649?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/f3TGjEFcOoE/my-confluence-30-wishlist.html" title="My Confluence 3.0 Wishlist" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">10</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/10/my-confluence-30-wishlist.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUcBQHY5eyp7ImA9WxRSEUQ.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-114328985511979489</id><published>2008-09-11T20:21:00.000-07:00</published><updated>2008-09-11T22:24:11.823-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-09-11T22:24:11.823-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Security" /><category scheme="http://www.blogger.com/atom/ns#" term="OpenSolaris" /><category scheme="http://www.blogger.com/atom/ns#" term="MacOS" /><title>Unintrusive but secure passwordless ssh authentication</title><content type="html">On a daily basis I need to log in to many remote servers inside or outside of Sun via SSH, often dozens of times per day. This can get pretty tiresome if you need to type in your password with every log in.&lt;br /&gt;&lt;br /&gt;Some suggest setting up so-called "passwordless" authentication by generating ssh keys and specifying empty passphrase for the private key. This will result in passwordless authentication, but will also decreased security. Should anyone get hold of your private key, (s)he'll get access to all of your remote systems.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Ssh-agent"&gt;&lt;code&gt;ssh-agent&lt;/code&gt;&lt;/a&gt; can help a lot in keeping the security level high and minimizing the number of times you need to type in the password. However, if you use a terminal with tabs or use both local and remote terminals on your workstation, you'll end up running many ssh-agent processes and having to authenticate every time you start such a process, which diminishes most of the conveniences of using &lt;code&gt;ssh-agent&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Frustrated with this situation and with a bit of help from &lt;a href="http://blogs.sun.com/martin"&gt;Martin&lt;/a&gt;, I created a shell script, which I added to my &lt;code&gt;.bash_profile&lt;/code&gt; startup script. All I have to do now is to authenticate when my first terminal session starts and I'm good until the next time I restart my OS. sweeeet...&lt;br /&gt;&lt;br /&gt;Here is how you could set it up on a &lt;b&gt;workstation&lt;/b&gt; and a &lt;b&gt;remote-server&lt;/b&gt;:&lt;br /&gt;&lt;br /&gt;First, if you haven't generated your private/public ssh key pair, do that now:&lt;pre&gt;workstation $ &lt;b&gt;ssh-keygen&lt;/b&gt;&lt;br /&gt;Generating public/private rsa key pair.&lt;br /&gt;Enter file in which to save the key (/Users/user/.ssh/id_rsa): &lt;b&gt;**********************&lt;/b&gt;&lt;br /&gt;Enter same passphrase again: &lt;b&gt;**********************&lt;/b&gt;&lt;br /&gt;Your identification has been saved in id_rsa.&lt;br /&gt;Your public key has been saved in id_rsa.pub.&lt;br /&gt;The key fingerprint is:&lt;br /&gt;01:a6:95:23:1c:74:53:c7:f4:87:07:a2:50:ef:99:16 user@Computer.local&lt;/pre&gt;&lt;br /&gt;Now make sure that the file system permissions are set up correctly:&lt;pre&gt;workstation $ &lt;b&gt;cd ~/.ssh/&lt;/b&gt;&lt;br /&gt;workstation $ &lt;b&gt;ls -l&lt;/b&gt;&lt;br /&gt;total 56&lt;br /&gt;-rw-------  1 user  staff  1743 Aug 31 00:13 id_rsa&lt;br /&gt;-rw-r--r--  1 user  staff   398 Aug 31 00:13 id_rsa.pub&lt;br /&gt;&lt;/pre&gt;The &lt;code&gt;id_rsa&lt;/code&gt; file must be readable only by owner, if this is not true, the key will be ignored.&lt;br /&gt;&lt;br /&gt;On the &lt;b&gt;remote server&lt;/b&gt; you need to authorize your newly generated key pair by appending it's public key to the &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; file under your remote home directory:&lt;pre&gt;workstation $ &lt;b&gt;cat ~/.ssh/id_rsa.pub |ssh user@remote-server 'sh -c "cat - &gt;&gt;~/.ssh/authorized_keys"'&lt;/b&gt;&lt;/pre&gt;&lt;br /&gt;Now you can try to log in:&lt;pre&gt;workstation $ &lt;b&gt;ssh user@remote-server&lt;/b&gt;&lt;br /&gt;Enter passphrase for /Users/user/.ssh/id_rsa: **********************&lt;br /&gt;Identity added: /Users/user/.ssh/id_rsa (/Users/user/.ssh/id_rsa)&lt;br /&gt;Last login: Thu Sep 11 20:19:19 2008&lt;br /&gt;remote-server $&lt;/pre&gt;&lt;br /&gt;If you open a new tab in your terminal and try to log in again, you'll be asked to enter the passphrase yet again. This is where my script becomes useful. First download the script from &lt;a href="http://mediacast.sun.com/"&gt;Mediacast&lt;/a&gt;: &lt;a href="http://mediacast.sun.com/users/IgorMinar/media/ssh-agent-init.sh/details"&gt;ssh-agent-init.sh&lt;/a&gt; and store it somewhere in your home directory&lt;pre&gt;workstation $ &lt;b&gt;mkdir ~/bin&lt;/b&gt;&lt;br /&gt;workstation $ &lt;b&gt;cd ~/bin&lt;/b&gt;&lt;br /&gt;workstation $ &lt;b&gt;wget http://mediacast.sun.com/users/IgorMinar/media/ssh-agent-init.sh&lt;/b&gt;&lt;br /&gt;workstation $ &lt;b&gt;chmod o+x ~/bin/ssh-agent-init.sh&lt;/b&gt;&lt;/pre&gt;&lt;br /&gt;The next (last) step is optional if you want to start the script manually you can skip it.&lt;br /&gt;&lt;br /&gt;I wanted to have this script automatically invoked when I start my terminal for the first time in the interactive mode. All I needed to modify were my &lt;code&gt;.bash_profile&lt;/code&gt; (used for interactive sessions) and &lt;code&gt;.bashrc&lt;/code&gt; (used for non-interactive sessions) startup scripts for my bash shell (modifications are in italics):&lt;pre&gt;workstation $ &lt;b&gt;cat ~/.bash_profile&lt;/b&gt;&lt;br /&gt;...&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;&lt;i&gt;. ~/bin/ssh-agent-init.sh&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;workstation $ &lt;b&gt;cat ~/.bashrc&lt;/b&gt;&lt;br /&gt;&lt;i&gt;export NONINTERACTIVE=1&lt;/i&gt;&lt;br /&gt;. ~/.bash_profile&lt;br /&gt;&lt;/pre&gt;(Note: I source the &lt;code&gt;~/.bash_profile&lt;/code&gt; script from the &lt;code&gt;~/.bashrc&lt;/code&gt; script to enable code reuse between the two scripts.)&lt;br /&gt;&lt;br /&gt;That's it! If you now try to open a terminal tab for the first time, you'll be asked for passphrase. Once that is done, any new tab or any other shell session created under the same account will reuse the same ssh-agent process.&lt;br /&gt;&lt;br /&gt;I've been using this script on my MacOS X laptop as well as &lt;a href="http://opensolaris.com/"&gt;OpenSolaris&lt;/a&gt; workstation for a few weeks now and it's been working like charm.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-114328985511979489?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/E1dV7F9YuJI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/114328985511979489/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=114328985511979489" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/114328985511979489?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/114328985511979489?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/E1dV7F9YuJI/unintrusive-but-secure-passwordless-ssh.html" title="Unintrusive but secure passwordless ssh authentication" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/09/unintrusive-but-secure-passwordless-ssh.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkMEQHs9cCp7ImA9WxdXGE4.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-8432066724191037893</id><published>2008-06-30T07:00:00.000-07:00</published><updated>2008-06-30T07:00:01.568-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-30T07:00:01.568-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Projects" /><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><title>Enough with the Flash Already</title><content type="html">It often bothers me when Flash is being misused for small visual effects on home pages of websites. Not just that it slows things down, but it also usually breaks these websites terribly when I want to copy&amp;amp;paste stuff or when one doesn't have the Flash plugin installed.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.fgslovakia.com/author"&gt;Margarete&lt;/a&gt;, has recently bugged me quite a bit about making the front page of her website a bit more "visual". She started with an idea of adding some flash to her website, which I dismissed quickly.&lt;br /&gt;&lt;br /&gt;Fortunately I was able to come up with a neat little CSS code, which is way better than Flash for what she was trying to achieve. Don't be mistaken, I didn't even need JavaScript to do all the subtle movements and eager image loading, it's all pure CSS.&lt;br /&gt;&lt;br /&gt;The front page:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_p64VvtgZDp4/SGhRpu8FyAI/AAAAAAAAAIY/MEVYelCBiPU/s1600-h/home_page_FGSlovakia_web.jpg"&gt;&lt;img style="border: medium none ; margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_p64VvtgZDp4/SGhRpu8FyAI/AAAAAAAAAIY/MEVYelCBiPU/s400/home_page_FGSlovakia_web.jpg" alt="" id="BLOGGER_PHOTO_ID_5217509945725274114" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;A small "visual" effect when one mouses over the "Blog" element:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_p64VvtgZDp4/SGhXl5TxzQI/AAAAAAAAAIg/2m4CguTknsA/s1600-h/home_page_FGSlovakia_focus.jpg"&gt;&lt;img style="border: medium none ; margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_p64VvtgZDp4/SGhXl5TxzQI/AAAAAAAAAIg/2m4CguTknsA/s400/home_page_FGSlovakia_focus.jpg" alt="" id="BLOGGER_PHOTO_ID_5217516476859272450" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I still plan to enhance the code a bit more to make it faster to load by decreasing the number of HTTP requests needed to load the asset files as well as size of the images, but I'm quite happy with what it looks and feels like already. Check out the live version at &lt;a href="http://www.fgslovakia.com/"&gt;www.fgslovakia.com&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;A good technical article describing a technique very similar to the one that I used can be found the &lt;a href="http://alistapart.com/articles/sprites"&gt;A List Apart&lt;/a&gt; website.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-8432066724191037893?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/-cg4b84LNbI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/8432066724191037893/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=8432066724191037893" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/8432066724191037893?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/8432066724191037893?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/-cg4b84LNbI/enough-with-flash-already.html" title="Enough with the Flash Already" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_p64VvtgZDp4/SGhRpu8FyAI/AAAAAAAAAIY/MEVYelCBiPU/s72-c/home_page_FGSlovakia_web.jpg" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/06/enough-with-flash-already.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUMGRHY-fip7ImA9WxdXFE8.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-2232198749815830846</id><published>2008-06-25T07:00:00.000-07:00</published><updated>2008-06-25T13:57:05.856-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-25T13:57:05.856-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Java" /><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><category scheme="http://www.blogger.com/atom/ns#" term="SunWikis" /><title>BTrace == DTrace for Java</title><content type="html">&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_p64VvtgZDp4/SGHbC6E0jJI/AAAAAAAAAIQ/DM5QTFYGvYA/s1600-h/BTrace.png"&gt;&lt;img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand; border: 0px solid black;" src="http://3.bp.blogspot.com/_p64VvtgZDp4/SGHbC6E0jJI/AAAAAAAAAIQ/DM5QTFYGvYA/s400/BTrace.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5215690686467443858" /&gt;&lt;/a&gt;Last week, I was trying to nail down a bug in &lt;a href="http://wikis.sun.com/"&gt;SunWikis&lt;/a&gt; that was triggered by some kind of race condition. These kinds of issues are pretty nasty, especially if the application in question is a pretty complex beast, something that &lt;a href="http://www.blogger.com/www.atlassian.com/software/confluence"&gt;Confluence&lt;/a&gt; definitely qualifies as.&lt;br /&gt;&lt;br /&gt;The debugger was no help because the timing had to be very precise for the issue to occur, and logging was totally useless because of zillions lines of logs that would be difficult to safely filter out. In many cases the information I needed was not present in logs anyway and since my guess was that the bug was coming from 3rd party code, logged data couldn't be easily expanded. &lt;a href="http://www.sun.com/bigadmin/content/dtrace/"&gt;DTrace&lt;/a&gt;, which I blogged about in &lt;a href="http://blog.igorminar.com/2008/06/dtrace-and-java-observations-and-docs.html"&gt;my previous post&lt;/a&gt;, could have revealed some information, but I think that it would be very difficult to write a good D script that could give me some clues.&lt;br /&gt;&lt;br /&gt;While waiting for my dev server to restart, I came across an &lt;a href="http://blogs.sun.com/theaquarium/entry/btrace_showing_promises"&gt;interesting blog post&lt;/a&gt; that caught my eye. It mentioned this thing called &lt;a href="https://btrace.dev.java.net/"&gt;BTrace&lt;/a&gt;, that I hadn't heard about before. It promised it to be a DTrace aimed at Java apps. With a fair amount of skepticism, I navigated to the BTrace website and started reading. Four things about BTrace earned my interest:&lt;ul&gt;&lt;li&gt;It's based on same or very similar principals as DTrace, but specialized for Java apps&lt;/li&gt;&lt;li&gt;No restarts necessary, you can observe a running app by connecting to a JVM identified by a PID&lt;/li&gt;&lt;li&gt;The BTrace programs are written in a language that is a subset of Java, and heavily based on annotations. This was a bit of a turnoff for me  at first, until I found out that the programs don't need to be compiled manually but &lt;code&gt;btrace&lt;/code&gt; takes care of that for me. Bravo!&lt;/li&gt;&lt;li&gt;I can call DTrace from my BTrace programs if I need to instrument native code or the OS&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Armed with all this info and my eyes sparking, I wrote my first BTrace program and it worked as advertised! No restarts, no compilation, no hassle.&lt;br /&gt;&lt;br /&gt;A few hours later, I had a program that was observing exactly those parts of Confluence that I needed. All of this while the application was running uninterrupted. I was able to snoop on how my code was interacting with Confluence and Confluence was in turn interacting with Hibernate in a way that I would have never dreamed of. All of this while concurrent events aimed at triggering the bug were happening.&lt;br /&gt;&lt;br /&gt;Running BTrace programs is as easy as this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ ./bin/btrace \&lt;br /&gt;  -classpath hibernate-2.1.8-atlassian.jar:spring-hibernate2-2.0.6.jar \&lt;br /&gt;  6266 HibernateSessions.java&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Where 6266 is the PID of my app/web server and &lt;code&gt;HibernateSessions.java&lt;/code&gt; is my BTrace program.&lt;br /&gt;&lt;br /&gt;Parts of the program looks like this (check out the javadoc comments for explanation of the code):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import com.sun.btrace.annotations.*;&lt;br /&gt;import static com.sun.btrace.BTraceUtils.*;&lt;br /&gt;import net.sf.hibernate.HibernateException;&lt;br /&gt;import net.sf.hibernate.collection.PersistentCollection;&lt;br /&gt;import net.sf.hibernate.engine.SessionImplementor;&lt;br /&gt;import org.springframework.orm.hibernate.support.OpenSessionInViewFilter;&lt;br /&gt;&lt;br /&gt;@BTrace&lt;br /&gt;public class HibernateSessions {&lt;br /&gt;  /**&lt;br /&gt;   * A thread local variable used to filter out all the events that we&lt;br /&gt;   * are not interested it.&lt;br /&gt;   */&lt;br /&gt;  @TLS private static boolean viaSso = false;&lt;br /&gt;  private static boolean printStack = false;&lt;br /&gt;&lt;br /&gt;  /** print a message when a thread enters doFilter method of SsoFilter */&lt;br /&gt;  @OnMethod(&lt;br /&gt;      clazz="com.sun.dse.wikis.auth.SsoFilter",&lt;br /&gt;      method="doFilter"&lt;br /&gt;  )&lt;br /&gt;  public static void enableProbing() {&lt;br /&gt;      println(str(currentThread(), " - Entered SSO Filter"));&lt;br /&gt;      viaSso = true;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /** print a message when exiting the doFilter method of SsoFilter */&lt;br /&gt;  @OnMethod(&lt;br /&gt;      clazz="com.sun.dse.wikis.auth.SsoFilter",&lt;br /&gt;      method="doFilter",&lt;br /&gt;      location=@Location(Kind.RETURN)&lt;br /&gt;  ) &lt;br /&gt;  public static void disableProbing() {&lt;br /&gt;      println(str(currentThread(),  " - Exited SSO Filter"));&lt;br /&gt;      viaSso = false;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * print an message with a HibernateException is thrown, with detailed &lt;br /&gt;   * info about the current context&lt;br /&gt;   */&lt;br /&gt;  @OnMethod(&lt;br /&gt;      clazz="net.sf.hibernate.HibernateException",&lt;br /&gt;      method="&lt;init&gt;"&lt;br /&gt;  )&lt;br /&gt;  public static void onthrowreturn(HibernateException self, String s)  {&lt;br /&gt;      println(str(currentThread(), &lt;br /&gt;              strcat(" - HibernateException was thrown: ",&lt;br /&gt;              strcat(s, strcat(" | collection: ", str(collection))))));&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Print a message when Hibernate is attaching a collection to a session.&lt;br /&gt;   * This was crucial info for me to get in order to resolve the issue&lt;br /&gt;   */&lt;br /&gt;  @OnMethod(&lt;br /&gt;      clazz="net.sf.hibernate.collection.PersistentCollection",&lt;br /&gt;      method="setCurrentSession",&lt;br /&gt;      location = @Location(Kind.RETURN)&lt;br /&gt;  )&lt;br /&gt;  public static void setSessionForCollection(boolean returnValue)  {&lt;br /&gt;      if (returnValue == true) {&lt;br /&gt;        println(str(currentThread(),&lt;br /&gt;          strcat(" - a collection was attached to a session. Collection:",&lt;br /&gt;          str(collection))));&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/init&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The result of this was a workaround for my code as well as a pretty &lt;a href="http://jira.atlassian.com/browse/CONF-12201"&gt;detailed bug report&lt;/a&gt; that Atlassian confirmed to be a very similar to a confirmed bug that was reported recently. I could hardly achieve this without BTrace or without an in-depth knowledge of how all the Confluence code works.&lt;br /&gt;&lt;br /&gt;As great as BTrace is, it is still a beta. If you decide to use it in production environment you should keep that in mind. During the several hours I spent working with BTrace, I experienced one JVM crash/lockup caused by BTrace. I bet that this kind of issues will be ironed out soon, and often this kind of risk is worth undertaking in order to resolve otherwise untraceable bugs.&lt;br /&gt;&lt;br /&gt;Since last week, BTrace is part of my toolkit and I already have other mysteries on my ToDo list that I want to shine some light on with BTrace. I applaud the BTrace team for giving us this amazing tool!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-2232198749815830846?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/q41OiYdoMpA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/2232198749815830846/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=2232198749815830846" title="19 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/2232198749815830846?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/2232198749815830846?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/q41OiYdoMpA/btrace-dtrace-for-java.html" title="BTrace == DTrace for Java" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_p64VvtgZDp4/SGHbC6E0jJI/AAAAAAAAAIQ/DM5QTFYGvYA/s72-c/BTrace.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">19</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/06/btrace-dtrace-for-java.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08ER3o5eyp7ImA9WxdXE0o.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-3266980207776881815</id><published>2008-06-24T22:30:00.000-07:00</published><updated>2008-06-24T22:30:06.423-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-06-24T22:30:06.423-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Java" /><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><title>DTrace and Java - Observations and Docs</title><content type="html">For a long time I've been hearing about &lt;a href="http://www.sun.com/bigadmin/content/dtrace/"&gt;DTrace&lt;/a&gt; and how cool it was. I read a lot about it and saw some presentation on observing Java apps with DTrace, but only recently have I found enough free time to extensively play with it. And oh boy, it really is cool. But even with all this coolness, dtracing Java apps feels a bit awkward.&lt;br /&gt;&lt;br /&gt;I think there are two reasons for that. The &lt;a href="http://wikis.sun.com/display/DTrace/D+Program+Structure"&gt;D language&lt;/a&gt;, which looks like C, but has a few fundamental differences, is pretty far from what Java developers are used to. And the second reason is the fact that DTrace is in reality instrumenting JVM and not the application running in the JVM. This means that dtracing Java is limited by abilities of the &lt;a href="http://java.sun.com/javase/6/docs/technotes/guides/vm/dtrace.html"&gt;Java DTrace provider&lt;/a&gt; and also that the transition between the different layers of the stack can add some awkwardness.&lt;br /&gt;&lt;br /&gt;Having said that, DTrace is really awesome and can be enormously helpful in many cases when developing or troubleshooting (Java) apps.&lt;br /&gt;&lt;br /&gt;One thing I &lt;b&gt;really&lt;/b&gt; miss in DTrace is regex support in any part of D scripts. There is a limited &lt;a href="http://wikis.sun.com/display/DTrace/D+Program+Structure"&gt;globbing support&lt;/a&gt; and &lt;a href="http://mail.opensolaris.org/pipermail/dtrace-discuss/2007-May/003953.html"&gt;some workarounds&lt;/a&gt;, but they ain't pretty.&lt;br /&gt;&lt;br /&gt;Here are some DTrace resources that I found useful:&lt;ul&gt;&lt;li&gt;&lt;a href="http://wikis.sun.com/display/DTrace/Documentation"&gt;DTrace Wiki Docs&lt;/a&gt; - the complete documentation (I love to see cool docs like this at our &lt;a href="http://wikis.sun.com/"&gt;SunWikis&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.sun.com/software/solaris/howtoguides/dtracehowto.jsp"&gt;a DTrace HowTo&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.solarisinternals.com/wiki/index.php/DTrace_Topics_Java"&gt;DTrace for Java at Solaris Internals&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://opensolaris.org/os/community/dtrace/dtracetoolkit/"&gt;DTrace Toolkit&lt;/a&gt; - a compilation of DTrace scripts written by DTrace pros&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-3266980207776881815?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/j_zsZifuq7s" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/3266980207776881815/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=3266980207776881815" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/3266980207776881815?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/3266980207776881815?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/j_zsZifuq7s/dtrace-and-java-observations-and-docs.html" title="DTrace and Java - Observations and Docs" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/06/dtrace-and-java-observations-and-docs.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEEEQXk-cSp7ImA9WxdSGE8.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-2100058781290522732</id><published>2008-05-26T11:30:00.000-07:00</published><updated>2008-05-26T11:30:00.759-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-05-26T11:30:00.759-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Java" /><category scheme="http://www.blogger.com/atom/ns#" term="Projects" /><category scheme="http://www.blogger.com/atom/ns#" term="Sun" /><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><category scheme="http://www.blogger.com/atom/ns#" term="SunWikis" /><title>Catching StackOverflowError and a Bug in Regex Implementation in Java</title><content type="html">Shortly after we launched &lt;a href="http://wikis.sun.com"&gt;SunWikis&lt;/a&gt; almost a year ago, we started having an issue with &lt;a href="http://java.sun.com/javase/6/docs/api/"&gt;StackOverflowError&lt;/a&gt;, which was occurring when the content of certain wiki pages was being parsed and URLs where being extracted. I documented the issue in &lt;a href="http://jira.atlassian.com/browse/CONF-9392"&gt;CONF-9392&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The problem is not really a bug in Confluence (even though using an overly-complex regular expressions doesn't help the case), but it's a problem in JDK. After a brief search at &lt;a href="http://bugs.sun.com"&gt;bugs.sun.com&lt;/a&gt; I found the root cause of our StackOverflowError documented as bug &lt;a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6337993"&gt;6337993&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Originally I tried to mitigate the situation by increasing the memory reserved for the stack data via the &lt;code&gt;-Xss&lt;/code&gt; JVM parameter. This helped a little bit, but wasn't good enough in most cases.&lt;br /&gt;&lt;br /&gt;Last week I decided to go against everything I've been taught, and wrote a patch for Confluence that wraps the part of code that results in the StackOverflowError into a try/catch block. I know that any throwables that extend from Error &lt;a href="http://java.sun.com/javase/6/docs/api/java/lang/Error.html"&gt;should not be caught&lt;/a&gt; by a client code because they usually indicate a failure that only JVM should try to recover from, but IMO in the case of StackOverflowError, the situation is a bit different. That is mainly because before throwing the StackOverflowError, JVM pops the stack, so by the time the code execution gets to the catch block, JVM has already recovered from the error.&lt;br /&gt;&lt;br /&gt;I don't claim this to be a solution to the problem, it's just a workaround that works better than increasing the stack size in this particular case. The fact that Confluence doesn't find all the URLs in wiki pages (used mainly to list outgoing links in the page info view) is just a small sacrifice, compared to inability to save or copy the page.&lt;br /&gt;&lt;br /&gt;As for the solution, it seems that reimplementing Java's regular expressions library would be the most suitable one. I tried to run a code that fails in Java in JRuby, which uses a port of &lt;a href="http://www.geocities.jp/kosako3/oniguruma/"&gt;Oniguruma&lt;/a&gt; regex engine for Java and things worked flawlessly and as I read it also gives JRuby a &lt;a href="http://www.infoq.com/news/2007/11/oniguruma-joni-jruby"&gt;performance boost over java.util.regex&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-2100058781290522732?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/fhLX85ZbB5o" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/2100058781290522732/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=2100058781290522732" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/2100058781290522732?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/2100058781290522732?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/fhLX85ZbB5o/catching-stackoverflowerror-and-bug-in.html" title="Catching StackOverflowError and a Bug in Regex Implementation in Java" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/05/catching-stackoverflowerror-and-bug-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE8DQHY8fip7ImA9WxZRFEQ.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-7625900366416350110</id><published>2008-02-08T11:00:00.000-08:00</published><updated>2008-02-08T10:54:31.876-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-02-08T10:54:31.876-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="JRuby" /><category scheme="http://www.blogger.com/atom/ns#" term="Projects" /><category scheme="http://www.blogger.com/atom/ns#" term="Sun" /><category scheme="http://www.blogger.com/atom/ns#" term="Mediacast" /><category scheme="http://www.blogger.com/atom/ns#" term="Rails" /><title>Mediacast Deployment Diagram</title><content type="html">During our last meeting, &lt;a href="http://weblogs.java.net/blog/arungupta/"&gt;Arun&lt;/a&gt; suggested that I publish the high-level deployment diagram of &lt;a href="http://mediacast.sun.com"&gt;mediacast.sun.com&lt;/a&gt;. So here it is:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_p64VvtgZDp4/R6yhC1RzzpI/AAAAAAAAAII/5mRZxbxi8f0/s1600-h/Mediacast_Deployment_Diagram.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_p64VvtgZDp4/R6yhC1RzzpI/AAAAAAAAAII/5mRZxbxi8f0/s400/Mediacast_Deployment_Diagram.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5164679942720704146" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I described everything in textual form in my previous &lt;a href="http://blog.igorminar.com/2008/01/jruby-on-rails-rewrite-of.html"&gt;blog entry about mediacast&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-7625900366416350110?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/Wj34R9B9VOo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/7625900366416350110/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=7625900366416350110" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/7625900366416350110?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/7625900366416350110?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/Wj34R9B9VOo/mediacast-deployment-diagram.html" title="Mediacast Deployment Diagram" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_p64VvtgZDp4/R6yhC1RzzpI/AAAAAAAAAII/5mRZxbxi8f0/s72-c/Mediacast_Deployment_Diagram.jpg" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/02/mediacast-deployment-diagram.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkMHRnc7eip7ImA9WxZSFUo.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-249386073751627270</id><published>2008-01-27T20:16:00.000-08:00</published><updated>2008-01-28T17:33:57.902-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-28T17:33:57.902-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="JRuby" /><category scheme="http://www.blogger.com/atom/ns#" term="Projects" /><category scheme="http://www.blogger.com/atom/ns#" term="Sun" /><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><category scheme="http://www.blogger.com/atom/ns#" term="Mediacast" /><category scheme="http://www.blogger.com/atom/ns#" term="Rails" /><title>JRuby on Rails Rewrite of mediacast.sun.com Launched</title><content type="html">A few days ago, we finally released &lt;a href="http://mediacast.sun.com/"&gt;Mediacast 2.0&lt;/a&gt; - a complete rewrite of the old Mediacast application.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_p64VvtgZDp4/R50181RzzoI/AAAAAAAAAIA/0zBAagWqj10/s1600-h/Picture+4.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://2.bp.blogspot.com/_p64VvtgZDp4/R50181RzzoI/AAAAAAAAAIA/0zBAagWqj10/s400/Picture+4.png" alt="" id="BLOGGER_PHOTO_ID_5160340067246657154" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The original application was based on a few servlets, filters, and a lot of JSPs. It was all put together in hurry a few of years ago, when one of those fire-drill requests came to provide Sun employees with a site where they could publish files. The old code was hard to maintain and extend, and that's why we decided to EOL the old code base and write something better from scratch.&lt;br /&gt;&lt;br /&gt;I'm a fan of &lt;a href="http://www.rubyonrails.org/"&gt;Rails&lt;/a&gt; and for the past year I've been amazed by the great progress of the &lt;a href="http://jruby.codehaus.org/"&gt;JRuby&lt;/a&gt; project. This was one of the reasons why I suggested that we could try to rewrite the app in Rails and deploy it in a regular Java web container, thanks to JRuby and &lt;a href="http://wiki.jruby.org/wiki/Goldspike"&gt;Goldspike&lt;/a&gt;. It took some time for &lt;a href="http://blogs.sun.com/rama/"&gt;Rama&lt;/a&gt; to give us a go-ahead, but finally in late September, two other colleagues (with no Rails or JRuby experience) and I started to work on the rewrite alongside our other projects (&lt;a href="http://forums.sun.com/"&gt;forums.sun.com&lt;/a&gt; and &lt;a href="http://wikis.sun.com/"&gt;wikis.sun.com&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;Many people asked us why JRuby on Rails was picked for this project. Here is an incomplete list of reasons:&lt;ul&gt;&lt;li&gt;we were starting from scratch, so we were not tied to any legacy code, and could pick any web framework that runs on Java&lt;/li&gt;&lt;li&gt;proof of concept project to evaluate the technology for other uses across our organization&lt;/li&gt;&lt;li&gt;verify that Rails really delivers the rapid development promise&lt;/li&gt;&lt;li&gt;something new/fun for the team to help balance out the not-so-fun stuff :)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Here is my experience:&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Rails and JRuby Learning Curve&lt;/h2&gt;&lt;br /&gt;I worked on my first Rails project in the summer of 2006 and since then I worked on a few other (internal) Rails projects. But having to teach others and deploy an application externally  using JRuby was a new experience for me.&lt;br /&gt;&lt;br /&gt;As far as teaching goes, I can't critique myself, but what I can say is that I had a lot of good help thanks to some &lt;a href="http://www.pragprog.com/titles/rails2"&gt;Rails&lt;/a&gt;, &lt;a href="http://www.pragprog.com/titles/ruby"&gt;Ruby&lt;/a&gt; and &lt;a href="http://www.apress.com/book/view/9781590598818"&gt;JRuby&lt;/a&gt; books, and many websites and blogs that have sprung up on the Internet in the last couple of years.&lt;br /&gt;&lt;br /&gt;Learning JRuby (with previous Ruby and Java experience) is easy, because most of the time "it just works" and very rarely is the developer aware that the C-based MRI Ruby interpreter is not being used. The ability to access and seamlessly integrate with existing or new Java code is a huge plus without which we would not have been able to launch this project successfully (more on this later).&lt;br /&gt;&lt;br /&gt;The deployment part was a different story. The Ruby on Rails application can be deployed in many different ways and JRuby offers a new alternative approach to all of them. Instead of e.g. having Apache webserver reverse-proxy requests to an army of Mongrels, JRuby and &lt;a href="http://wiki.jruby.org/wiki/Goldspike"&gt;Goldspike&lt;/a&gt; make it possible to deploy Rails applications in a JavaEE web container. IMO this is much cleaner than anything else that is available in the non-JRuby Rails world. The downside is that this deployment method is quite new and there isn't a lot of documentation and community knowledge about it.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Application Architecture&lt;/h2&gt;&lt;br /&gt;Our app is a simple database-driven Rails application consisting of a handful of models, 4 controllers, and a bunch of view templates. The only piece of data that is not stored in the database are the actual files, which are stored on the file system.&lt;br /&gt;&lt;br /&gt;To be on the safe side when it comes to performance, we employed quite a lot of fragment caching, which sped up our application quite a bit.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Development Environment&lt;/h2&gt;&lt;br /&gt;Our development environment is based on a self-contained&lt;a href="#1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; JRuby (1.0.3) on Rails (1.2.6) application stored in a &lt;a href="http://www.selenic.com/mercurial"&gt;Mercurial&lt;/a&gt; repository. The IDE we use is &lt;a href="http://www.netbeans.org/"&gt;NetBeans 6&lt;/a&gt; with Ruby support and &lt;a href="http://wiki.netbeans.org/MercurialVersionControl"&gt;Mercurial plugin&lt;/a&gt;. The DB of our choice is MySQL and servers we use during development are &lt;a href="http://wiki.rubyonrails.org/rails/pages/WEBrick"&gt;WEBrick&lt;/a&gt; and &lt;a href="https://glassfish.dev.java.net/"&gt;Glassfish v2ur1&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;NetBeans makes it super easy to write the Ruby code and offers a lot of neat features for Rails application development. I have to say that &lt;a href="https://blogs.sun.com/tor"&gt;Tor&lt;/a&gt; and the gang did a great job. In fact I switched from Eclipse to NetBeans thanks to its great Ruby support.&lt;br /&gt;&lt;br /&gt;Because the application runs in the JVM we can use JConsole to monitor the app while load testing which comes in really handy!&lt;br /&gt;&lt;br /&gt;So all is good here, except for one thing. I got used to using the amazing &lt;a href="https://rubyforge.org/projects/ruby-debug/"&gt;ruby-debug&lt;/a&gt; debugger for debugging my Ruby on Rails applications. This debugger is not yet available for JRuby on Rails application (unless you &lt;a href="http://mysterycoder.blogspot.com/2007/10/debugging-jruby-on-rails-app-with-jruby.html"&gt;hack your way through&lt;/a&gt;). I read somewhere that jruby-debug should be available with NetBeans 6.1. Once that is done, the JRoR dev environment will be on par with RoR (in fact thanks to tools like JConsole it will be superior).&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Production Environment&lt;/h2&gt;&lt;br /&gt;We use a pair of load-balanced &lt;a href="http://www.sun.com/servers/coolthreads/t2000/"&gt;T2000&lt;/a&gt; with &lt;a href="http://www.blogger.com/www.sun.com/software/solaris"&gt;Solaris 10&lt;/a&gt;, &lt;a href="https://jdk6.dev.java.net/"&gt;JDK6&lt;/a&gt; and &lt;a href="http://www.sun.com/software/products/appsrvr/index.xml"&gt;SJSAS 9.1u1&lt;/a&gt;. These two servers share a nfs NAS drive used for file storage and fragment cache storage. The DB backend is a MySQL database server, which we access through a connection pool set up in the app server.&lt;br /&gt;&lt;br /&gt;All of this was fairly easy to set up. As you can see there were no special requirements when comparing this environment to a usual JavaEE production environment.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;The Good&lt;/h2&gt;&lt;br /&gt;Once my colleagues grasped some RoR basics, we got the core of the application up and running fairly fast. Sometimes it still amazes even me how much one can do with Rails in a short amount of time. Thanks to a small amount of code one needs to write, the code review process is fast as well, and fixing a bug often means changing only a few lines of code.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;The Bad&lt;/h2&gt;&lt;br /&gt;Since most of the C-based gems are not compatible out-of-box with JRuby (at the moment), the &lt;a href="https://rubyforge.org/projects/jruby-extras/"&gt;jruby-extras&lt;/a&gt; project aims to deliver JRuby compatible versions of these libraries. One of these libraries - JRuby-OpenSSL - was needed for me to integrate our app with our authentication webservice. I soon found out that JRuby-OpenSSL was not completely implemented yet and parts of functionality that I needed were missing. I'm sure that it is just a matter of time when problems like this will go away (if it hasn't happened already) as these libraries will mature.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;The Ugly&lt;/h2&gt;&lt;br /&gt;The Goldspike project provides a bridge between the JavaEE and RoR world. It does this by dispatching the incoming HTTP requests into Rails running in a JRuby runtime.&lt;br /&gt;&lt;br /&gt;This works great for all requests that take little time to process, but if you have long-running requests like large file uploads or downloads, these requests will occupy your JRuby runtimes and you soon realize that you are running out of runtimes in your small pool, at which point your application becomes unresponsive for any new requests.&lt;br /&gt;&lt;br /&gt;This was the biggest problem we hit, but thanks to the possibility to seamlessly integrate Java code into our JRoR app and a great idea that my colleague Peter had, we solved this issue quite elegantly.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;JRuby and Java Come to the Rescue&lt;/h2&gt;&lt;br /&gt;The two main issues we faced and that consumed most of my time on this project were related to the immaturity of the libraries around JRuby and application characteristics specific to JRoR deployment. Both of these issues can be resolved thanks to years of long Java and JavaEE history, and their libraries.&lt;br /&gt;&lt;br /&gt;The problem with reliable connection to our SOAP-over-HTTPS based authentication webservice was resolved by generating a client Java webservice stub and using that instead of &lt;a href="http://dev.ctor.org/soap4r"&gt;SOAP4R&lt;/a&gt; which didn't work properly because of the problems with JRuby-OpenSSL.&lt;br /&gt;&lt;br /&gt;The second problem with the long-running processes occupying our precious JRuby runtimes, we solved by using a servlet filter and a fake HttpServletResponse (that we sent to Goldspike instead of the original one) and streaming the data from the filter instead from the runtime. I'll write a separate blog entry on this.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Overall Impression&lt;/h2&gt;&lt;br /&gt;This project was/is fun to work on. We experimented with quite a few new (for us) technologies and learned a lot along the way. To be honest, I expected to deploy the app much sooner but at that time I was not aware of the two above mentioned issues which consumed a lot of my time.&lt;br /&gt;&lt;br /&gt;I was a bit worried about the performance of the application, but that turned out to be a non issue once we had the download servlet filter in place, and with the performance improvements in JRuby 1.1 things will be even better in the future.&lt;br /&gt;&lt;br /&gt;Overall I'm happy with the outcome of our project and I look forward to adding more functionality to the application.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Our Future Plans&lt;/h2&gt;&lt;br /&gt;From the infrastructure perspective:&lt;ul&gt;&lt;li&gt;upgrade to JRuby 1.1 and Rails 2.0&lt;/li&gt;&lt;li&gt;start using &lt;a href="http://wiki.jruby.org/wiki/Warbler"&gt;Warbler&lt;/a&gt;, which looks to be superior to Goldspike's Rails plugin, for building the war file&lt;/li&gt;&lt;li&gt;experiment with in-memory session state replication in Glassfish&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;From the feature perspective:&lt;ul&gt;&lt;li&gt;better categorization of media items&lt;/li&gt;&lt;li&gt;search functionality via integration with &lt;a href="http://search.sun.com"&gt;search.sun.com&lt;/a&gt;&lt;/li&gt;&lt;li&gt;previews of media items&lt;/li&gt;&lt;li&gt;new UI design&lt;/li&gt;&lt;li&gt;and the toughest one - audio and video streaming&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;JRuby/Goldspike/Glassfish Things I Would Like to See Improved&lt;/h2&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;I'm not sure if it is us and our (mis)configuration, Goldspike, JRuby or Glassfish, but right now we need quite a &lt;b&gt;big JVM heap&lt;/b&gt; to keep things running. With 8 JRuby instances in the pool and Http thread count set to 512, we need -Xmx set to 2-2.5GB. This is a bit too much I think. We'll have to look into this and find the culprit.&lt;/li&gt;&lt;li&gt;The &lt;b&gt;application startup is quite slow&lt;/b&gt;, especially on a machine like T2000, which doesn't perform well for heavy-weighted single-threaded operations. I don't see a reason, why Goldspike couldn't initialize the JRuby runtime pool concurrently cutting down the startup time significantly.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Offload JRuby runtimes as much as possible&lt;/b&gt;. Currently operations like file upload or file download (via Rails' send_file) are handled by JRuby runtimes. I think that it should be possible to take care of these operations outside of the runtime, allowing the runtime to process other rails requests in the meantime.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a name="1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; - Rails and all the other gems and jars are frozen into the project&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-249386073751627270?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/RAbdDc60kd0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/249386073751627270/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=249386073751627270" title="20 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/249386073751627270?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/249386073751627270?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/RAbdDc60kd0/jruby-on-rails-rewrite-of.html" title="JRuby on Rails Rewrite of mediacast.sun.com Launched" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/_p64VvtgZDp4/R50181RzzoI/AAAAAAAAAIA/0zBAagWqj10/s72-c/Picture+4.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">20</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/01/jruby-on-rails-rewrite-of.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUcHSXgzeyp7ImA9WxZSE0U.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-3845249118265350439</id><published>2008-01-21T23:59:00.000-08:00</published><updated>2008-01-26T14:37:18.683-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-26T14:37:18.683-08:00</app:edited><title>On the Move (Away from Blogger)</title><content type="html">Today was a big day for my blog. I started a "big" move that was hanging over my mind for a few months.&lt;br /&gt;&lt;br /&gt;I got my own domain &lt;a href="http://igorminar.com/"&gt;igorminar.com&lt;/a&gt; and changed the blogger.com settings so that the domain name &lt;a href="http://blog.igorminar.com/"&gt;blog.igorminar.com&lt;/a&gt; is being used instead of &lt;a href="http://net3x.blogspot.com/"&gt;net3x.blogspot.com&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Except for a small hick-up with DNS cache and a weird 404 Server not Found error (described &lt;a href="http://bloggerstatusforreal.blogspot.com/2007/11/custom-domain-publishing-and-404-error.html"&gt;in this blog post&lt;/a&gt;) everything went smoothly. My plan is to keep on using Blogger's custom domain feature for a few weeks and then migrate all the content over to my own blogging engine. I'll most likely end up using &lt;a href="http://mephistoblog.com/"&gt;Mephisto&lt;/a&gt;, &lt;a href="http://jruby.codehaus.org/"&gt;JRuby&lt;/a&gt;, &lt;a href="https://glassfish.dev.java.net/"&gt;Glassfish&lt;/a&gt; and &lt;a href="http://www.mysql.com/"&gt;MySQL&lt;/a&gt;, but things can still change.&lt;br /&gt;&lt;br /&gt;The reason why I'm moving in two phases is to keep all the referring links that point to the old urls as well as most of the search engine ranking. The Blogger's custom domain feature now redirects everything to the new urls via 301 Permanent Redirect, so hopefully the transition will be close to painless. I'm not SEO expert, but this for sure is an interesting experiment.&lt;br /&gt;&lt;br /&gt;And for those SEO "experts" that were claiming that getting Blogger to do proper redirects after a blog move was impossible here is a little proof:&lt;pre&gt;&lt;br /&gt;$ wget --spider net3x.blogspot.com&lt;br /&gt;--16:34:30--  http://net3x.blogspot.com/&lt;br /&gt;      =&gt; `index.html.2'&lt;br /&gt;Resolving net3x.blogspot.com... 72.14.207.191&lt;br /&gt;Connecting to net3x.blogspot.com|72.14.207.191|:80... connected.&lt;br /&gt;HTTP request sent, awaiting response... 301 Moved Permanently&lt;br /&gt;Location: http://blog.igorminar.com/ [following]&lt;br /&gt;--16:34:30--  http://blog.igorminar.com/&lt;br /&gt;      =&gt; `index.html.2'&lt;br /&gt;Resolving blog.igorminar.com... 66.249.81.121&lt;br /&gt;Connecting to blog.igorminar.com|66.249.81.121|:80... connected.&lt;br /&gt;HTTP request sent, awaiting response... 200 OK&lt;br /&gt;Length: 0 [text/html]&lt;br /&gt;200 OK&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-3845249118265350439?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/8KAfOgudtiA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/3845249118265350439/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=3845249118265350439" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/3845249118265350439?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/3845249118265350439?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/8KAfOgudtiA/on-move-away-from-blogger.html" title="On the Move (Away from Blogger)" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">5</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/01/on-move-away-from-blogger.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0IGRngyeCp7ImA9WxZTGEQ.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-1548008903269126379</id><published>2008-01-20T23:20:00.000-08:00</published><updated>2008-01-20T23:12:07.690-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-20T23:12:07.690-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="MacOS" /><title>My Custom Prompt for Leopard's Terminal</title><content type="html">As I mentioned in &lt;a href="http://net3x.blogspot.com/2008/01/leopards-terminal-tabs.html"&gt;my previous post&lt;/a&gt;, even though it is not perfect, I settled on using Terminal as my terminal application.&lt;br /&gt;&lt;br /&gt;The default prompt that comes with MacOS is not very useful, so I configured my own by adding this line to my &lt;code&gt;.bashrc&lt;/code&gt; file:&lt;br /&gt;&lt;pre&gt;export PS1="\[\033]0;\u@\h:\w\007\]\u@\h:\W\\$ "&lt;/pre&gt;&lt;br /&gt;This results in a prompt that looks like this:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_p64VvtgZDp4/R5RBR5lhkxI/AAAAAAAAAHk/AbnH-YzsnEQ/s1600-h/Terminal3.png"&gt;&lt;img style="border: medium none ; margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_p64VvtgZDp4/R5RBR5lhkxI/AAAAAAAAAHk/AbnH-YzsnEQ/s400/Terminal3.png" alt="" id="BLOGGER_PHOTO_ID_5157819249017852690" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;This prompt displays the prompt as &lt;code&gt;username@hostname:currentdir$&lt;/code&gt; and in addition to that  it sets the window title to &lt;code&gt;username@hostname:pwd&lt;/code&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-1548008903269126379?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/xa3K-1KPd6U" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/1548008903269126379/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=1548008903269126379" title="2 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/1548008903269126379?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/1548008903269126379?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/xa3K-1KPd6U/my-custom-prompt-for-leopards-terminal.html" title="My Custom Prompt for Leopard's Terminal" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/_p64VvtgZDp4/R5RBR5lhkxI/AAAAAAAAAHk/AbnH-YzsnEQ/s72-c/Terminal3.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/01/my-custom-prompt-for-leopards-terminal.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkAASXozfip7ImA9WxZTGEQ.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-530441694643457631</id><published>2008-01-20T22:55:00.000-08:00</published><updated>2008-01-20T22:59:08.486-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-20T22:59:08.486-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps" /><category scheme="http://www.blogger.com/atom/ns#" term="Apple Problems" /><category scheme="http://www.blogger.com/atom/ns#" term="MacOS" /><title>Leopard's Terminal Tabs</title><content type="html">As I shared yesterday, &lt;a href="http://net3x.blogspot.com/2008/01/im-obsesed-with-tabs.html"&gt;I love tabs&lt;/a&gt;. One of the few good things that MacOS 10.5 Leopard brought, was enhanced Terminal application. A basic feature - Tab support - that was missing in Terminal for way too long, was finally added, and Terminal app became finally usable for anyone who spends a great time of the day working on the command line.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_p64VvtgZDp4/R5Qs-ZlhkvI/AAAAAAAAAHU/PNEd6H8SBXg/s1600-h/Terminal.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; border:none;" src="http://1.bp.blogspot.com/_p64VvtgZDp4/R5Qs-ZlhkvI/AAAAAAAAAHU/PNEd6H8SBXg/s400/Terminal.png" alt="" id="BLOGGER_PHOTO_ID_5157796923777848050" /&gt;&lt;/a&gt;&lt;br /&gt;Before Leopard, I used &lt;a href="http://iterm.sourceforge.net/"&gt;iTerm&lt;/a&gt;, but there was just something about it that I didn't like, but the availability of tabs made me get over it. Now with Leopard there is no need for iTerm any more.&lt;br /&gt;&lt;br /&gt;Just to point out that Apple seems to be unable to get anything 100% right these days, there are two flaws in the Terminal's tab implementation.&lt;br /&gt;&lt;br /&gt;The first flaw is that you can't easily switch between tabs using keyboard shortcut. All you can do is to use &lt;code&gt;cmd+{&lt;/code&gt; and &lt;code&gt;cmd+}&lt;/code&gt; to switch to previous or next tab respectively. What a "wonderful" choice of keys that require use of right hand or both hands. Most importantly, one can't use the handy &lt;code&gt;cmd+1&lt;/code&gt;, &lt;code&gt;cmd+2&lt;/code&gt;, ..., &lt;code&gt;cmd+9&lt;/code&gt; shortcuts (or anything similar) to switch to one of the first nine tabs. Maybe Apple doesn't want to overload us with new features and is planning to add this to Terminal in MacOS 10.6. :-) Fortunately, the &lt;code&gt;cmd+{&lt;/code&gt; and &lt;code&gt;cmd+}&lt;/code&gt; shortcuts can be remapped via System Preferences.&lt;br /&gt;&lt;br /&gt;The second flaw is that Terminal doesn't let you name the tabs or configure how the tabs should be named. This is a useful feature that many other terminal applications have, but Apple decided that using the name of the program of the currently running process is good enough. Well, this is what it looks like:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_p64VvtgZDp4/R5Q5JJlhkwI/AAAAAAAAAHc/52vurVDrhcc/s1600-h/Terminal2.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; border: none;" src="http://4.bp.blogspot.com/_p64VvtgZDp4/R5Q5JJlhkwI/AAAAAAAAAHc/52vurVDrhcc/s400/Terminal2.png" alt="" id="BLOGGER_PHOTO_ID_5157810302600975106" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;People are doing all kinds of crazy things to work around this issue. Including using &lt;a href="http://www.allthepages.org/archives/2007/10/specify_tab_names_in_terminal.html"&gt;hardlinks to change names of programs&lt;/a&gt; or creating &lt;a href="http://pseudogreen.org/blog/set_tab_names_in_leopard_terminal.html"&gt;dummy processes that carry the tab name&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;I would be very happy if Apple did something similar to what xterm does - &lt;a href="http://www.faqs.org/docs/Linux-mini/Xterm-Title.html"&gt;using an environmental variable to set name of the tab dynamically&lt;/a&gt;. But I have a feeling that we'll see MacOSX Trash go 3D sooner than properly working Terminal tabs. :(&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-530441694643457631?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/CgW01-EZuc8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/530441694643457631/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=530441694643457631" title="8 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/530441694643457631?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/530441694643457631?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/CgW01-EZuc8/leopards-terminal-tabs.html" title="Leopard's Terminal Tabs" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_p64VvtgZDp4/R5Qs-ZlhkvI/AAAAAAAAAHU/PNEd6H8SBXg/s72-c/Terminal.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">8</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/01/leopards-terminal-tabs.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkcERHw7eyp7ImA9WxZTGEo.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-7106355479527955657</id><published>2008-01-20T16:15:00.000-08:00</published><updated>2008-01-20T16:06:45.203-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-20T16:06:45.203-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps" /><category scheme="http://www.blogger.com/atom/ns#" term="MacOS" /><title>Mac Applications That I Use</title><content type="html">I've been meaning to write this entry on applications installed on my computer for a few months now, but never found time to do it. Hopefully none of those new Mac users that I told to check out my blog "in a few days", several months ago, got angry at me. If nothing else, my promise helped me to get more regular readers :).&lt;br /&gt;&lt;br /&gt;Anyway, here is the list of apps installed on my Mac (all these apps work with Leopard, unless I noted otherwise):&lt;br /&gt;&lt;h2&gt;Essentials&lt;/h2&gt;A Mac without these apps would never be the same:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://download.blacktree.com/download.php?id=com.blacktree.Quicksilver&amp;amp;type=dmg&amp;amp;new=yes"&gt;Quicksilver&lt;/a&gt; - an app that helps you find things on your computer and execute actions on these things. I use it to launch apps, pause iTunes, find contacts in the AddressBook, and many many many other things. Most of the cool functionality is added via plugins, so don't forget to checkout the plugin section of preferences. This app is a must have. Here are &lt;a href="http://docs.blacktree.com/quicksilver/what_is_quicksilver"&gt;Some docs&lt;/a&gt; and a &lt;a href="http://video.google.com/videoplay?docid=8493378861634507068"&gt;good video&lt;/a&gt;.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.adiumx.com/"&gt;Adium&lt;/a&gt; - a really handy IM client that can handle all the popular IM protocols. Definitely a must have. (it's much better than iChat when it comes to IM-ing).&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://growl.info/downloads.php"&gt;Growl&lt;/a&gt; - Growl is a notification system for Mac OS X: it allows applications that support Growl to send you notifications. Notifications include Adium or Skype messages, network up/down events, volume mount/unmount events, currently played iTunes song and many others. &lt;a href="http://growl.info/about.php"&gt;More info&lt;/a&gt; - One extension that I found to be incompatible with Leopard is GrowlMail for Mail.app notifications, when I received many new messages at once this extension would cause the Mail.app to crash.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.macports.org/"&gt;MacPorts&lt;/a&gt; - This is a geeky one: The MacPorts Project is an open-source community initiative to design an easy-to-use system for compiling, installing, and upgrading either command-line, X11 or Aqua based open-source software on the Mac OS X operating system.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.mozilla.com/en-US/firefox/"&gt;Firefox&lt;/a&gt; - no description needed :)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h2&gt;Nice to Have&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.islayer.com/index.php?op=item&amp;amp;id=28"&gt;iStat menus&lt;/a&gt; - a nifty  app that displays stats about cpu load, cpu temperature, memory and network utilization, and a lot of other interesting data about your Mac. I use it as a replacement of &lt;a href="http://www.ragingmenace.com/software/menumeters/"&gt;MenuMeters&lt;/a&gt; which I couldn't get to run reliably on Leopard.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://perian.org/"&gt;Perian&lt;/a&gt; - Perian is a free, open source QuickTime component that adds native support for many popular video formats.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://caminobrowser.org/download/releases/"&gt;Camino&lt;/a&gt; - yeah, as if the fact that &lt;a href="http://net3x.blogspot.com/2008/01/im-obsesed-with-tabs.html"&gt;I'm obsessed with tabs&lt;/a&gt; was not enough, I usually run 2 or 3 browsers at the same time to be able to take advantage of their unique features, or more often, while developing webapps, I need to be able to have more than one session open.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://sourceforge.net/projects/cotvnc/"&gt;Chicken of the VNC&lt;/a&gt; - A handy VNC client&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/macfuse/"&gt;MacFUSE + sshfs&lt;/a&gt; - MacOS X implementation of &lt;a href="http://fuse.sourceforge.net/"&gt;FUSE&lt;/a&gt;, which makes it possible to painlessly mount all kinds of different filesystems e.g. sshfs.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/macfusion/downloads/list"&gt;MacFusion&lt;/a&gt; - GUI frontend for MacFUSE&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.videolan.org/vlc/"&gt;VLC&lt;/a&gt; - alternative to QuickTime video player with build-in support for all kinds of video and audio codes, subtitle support and many other features&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://sourceforge.net/projects/osxvnc/"&gt;Vine Server (OSXvnc)&lt;/a&gt; - a good VNC server in case you need a bit more control than the Leopard's built-in server offers&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://skype.com/"&gt;Skype&lt;/a&gt; - I have a love-hate relationship with Skype. As soon as I find an alternative crossplatform communication tool with audio and video support and cheap calls to Slovakia, I'll switch. Maybe &lt;a href="http://www.blogger.com/www.gizmoproject.com"&gt;Gizmo&lt;/a&gt; will become that tool one day&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.neooffice.org/neojava/en/maindownload.php"&gt;NeoOffice&lt;/a&gt; - don't forget to install the latest &lt;a href="http://www.neooffice.org/neojava/en/patch.php"&gt;Patch&lt;/a&gt;. I have big hopes for &lt;a href="http://porting.openoffice.org/mac/faq/aqua-prerelease/index.html"&gt;OpenOffice Aqua&lt;/a&gt; to be released soon. &lt;a href="http://net3x.blogspot.com/2007/06/openofficeorg-aqua-development-snapshot.html"&gt;My initial experiments&lt;/a&gt; revealed some very good performance results.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.shinywhitebox.com/home/home.html"&gt;iShowU&lt;/a&gt; - a cool app for creating screencasts. This is the only paid-for app on my list. But it's well worth it.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://lightheadsw.com/caffeine/"&gt;Caffeine&lt;/a&gt; - Caffeine is a tiny program that prevents your Mac from automatically going to sleep, dimming the screen or starting screen savers. Very useful during video calls, while reading PDFs or watching movies.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://iconfactory.com/software/twitterrific"&gt;Twitterrific&lt;/a&gt; - a Twitter client&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://azureus.sourceforge.net/download.php"&gt;Azureus&lt;/a&gt; - Java BitTorrent client&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.orange-carb.org/SkeyCalc/"&gt;SkeyCalc - An OTP (S/Key) calculator&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Cisco VPN Client&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Development&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://download.netbeans.org/netbeans/6.0/final/"&gt;NetBeans&lt;/a&gt; - there is a lot of controversy in the community when it comes to the Eclipse vs NetBeans question. I used Eclipse for a really long time, but ever since I tried NB6 Milestone 8 or so, I started liking NB more than Eclipse. I'll give Eclipse another shot in a few weeks, but for now NetBeans is my default IDE. And as far as Ruby/JRuby development is concerned, I don't think that this is going to change for a long time.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://dist.codehaus.org/jruby/"&gt;JRuby&lt;/a&gt; - a pure Java-based Ruby interpreter&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="https://glassfish.dev.java.net/"&gt;GlassFish&lt;/a&gt; - JavaEE 5.0 compliant application server&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;h2&gt;Other Stuff&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.siliconimage.com/docs/SiI3132_1.1.9u_Sil_Pkg.zip"&gt;Drivers for my Vydeo eSATA 34: Dual Port 34mm Express Card&lt;/a&gt; - More info in &lt;a href="http://net3x.blogspot.com/2007/03/500gb-esata-external-storage-with.html"&gt;my older blog entry&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Other Lists&lt;/h2&gt;&lt;a href="http://blogs.sun.com/rama/entry/what_to_install_on_your"&gt;Rama&lt;/a&gt; and &lt;a href="http://blogs.sun.com/martin/entry/things_you_can_t_live"&gt;Martin&lt;/a&gt; composed similar lists some time ago, so you might want to check those out as well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-7106355479527955657?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/0AQQ9GoJN4U" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/7106355479527955657/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=7106355479527955657" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/7106355479527955657?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/7106355479527955657?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/0AQQ9GoJN4U/mac-applications-that-i-use.html" title="Mac Applications That I Use" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/01/mac-applications-that-i-use.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C08MQ387eSp7ImA9WxZTGE8.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-2422764467755599269</id><published>2008-01-20T01:00:00.000-08:00</published><updated>2008-01-20T01:38:02.101-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-20T01:38:02.101-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Life" /><category scheme="http://www.blogger.com/atom/ns#" term="Fun" /><title>I'm Obsessed with Tabs</title><content type="html">This is a screenshot I took during a regular development/research day:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_p64VvtgZDp4/R5MK1ZlhktI/AAAAAAAAAHE/GE4hpvzW5do/s1600-h/FirefoxTabs.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand; border: none;" src="http://4.bp.blogspot.com/_p64VvtgZDp4/R5MK1ZlhktI/AAAAAAAAAHE/GE4hpvzW5do/s400/FirefoxTabs.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5157477910786970322" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;What is your average number of browser tabs opened at once?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-2422764467755599269?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/aDFoYg_hRsM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/2422764467755599269/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=2422764467755599269" title="8 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/2422764467755599269?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/2422764467755599269?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/aDFoYg_hRsM/im-obsesed-with-tabs.html" title="I'm Obsessed with Tabs" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_p64VvtgZDp4/R5MK1ZlhktI/AAAAAAAAAHE/GE4hpvzW5do/s72-c/FirefoxTabs.png" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">8</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/01/im-obsesed-with-tabs.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0cFRnc8fyp7ImA9WxZTGEo.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-4501570224674767458</id><published>2008-01-18T11:45:00.000-08:00</published><updated>2008-01-20T15:16:57.977-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2008-01-20T15:16:57.977-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Projects" /><category scheme="http://www.blogger.com/atom/ns#" term="Ruby" /><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><title>MultiSudokuSolver - A fun project I worked on during the Xmas break</title><content type="html">While I was visiting my family in Slovakia during the winter 2005/2006 I found a &lt;a href="http://en.wikipedia.org/wiki/Sudoku"&gt;sudoku&lt;/a&gt; magazine in my dad's apartment. I like this kind of stuff, so I mediately started solving one puzzle after another, until I got to a "Speciality MultiSudoku" puzzle:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_p64VvtgZDp4/R5EAAJlhkrI/AAAAAAAAAG0/IaSZEI871gM/s1600-h/sudoku.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_p64VvtgZDp4/R5EAAJlhkrI/AAAAAAAAAG0/IaSZEI871gM/s400/sudoku.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5156903050889237170" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;This puzzle is composed of five regular 9x9 puzzles that are interconnected and you can't solve either of them alone, meaning that you need to be solving all five of them at the same time and use intermediate results from one puzzle to get an intermediate result for another puzzle that this puzzle is connected with. It sounded like a good challenge so I started working on it. After two or three nights I realized that I made a mistake!! Ughhh. I erased everything I had solved and started from scratch. After another few nights I found another problem, got turned off by this puzzle and put it away.&lt;br /&gt;&lt;br /&gt;I came across the puzzle once again when my wife and I returned to Slovakia this winter. I found the puzzle in the apartment and, being a person who likes challenges and doesn't get turned off my failures for too long, I decided to erase everything and this time be very careful and solve the puzzle once and for all.&lt;br /&gt;&lt;br /&gt;After about two evenings I found something I didn't want to see. An error!!! It was hard to believe it, but there it was. I tried to fix it, but if you don't spot an error in a sudoku puzzle early enough, you'll spend more time fixing it than if you started from scratch.&lt;br /&gt;&lt;br /&gt;Do you think I felt like starting from scratch? No! But I couldn't let this be. So since I had 10 or so days off during the Christmas break (thanks Sun!!!), I decided to    use my brain in a more productive way and to write a small program that would solve the puzzle for me.&lt;br /&gt;&lt;br /&gt;Given my interest in Ruby and JRuby and the type of problem I was about to solve, the language choice was an easy one to make.&lt;br /&gt;&lt;br /&gt;A few days later I had a script that solved the puzzle for me within 1.07 seconds. Ya! You heard right! Solved not in a few evenings but in just a little more than one second.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Top Left:&lt;br /&gt;718|956|324&lt;br /&gt;452|387|619&lt;br /&gt;936|142|587&lt;br /&gt;-----------&lt;br /&gt;341|528|796&lt;br /&gt;695|713|842&lt;br /&gt;287|694|153&lt;br /&gt;-----------&lt;br /&gt;523|461|978&lt;br /&gt;869|275|431&lt;br /&gt;174|839|265&lt;br /&gt;&lt;br /&gt;Top Right:&lt;br /&gt;635|428|971&lt;br /&gt;214|679|853&lt;br /&gt;897|531|426&lt;br /&gt;-----------&lt;br /&gt;976|382|514&lt;br /&gt;152|764|389&lt;br /&gt;348|915|267&lt;br /&gt;-----------&lt;br /&gt;463|857|192&lt;br /&gt;529|146|738&lt;br /&gt;781|293|645&lt;br /&gt;&lt;br /&gt;Bottom Left:&lt;br /&gt;728|154|396&lt;br /&gt;194|836|752&lt;br /&gt;653|279|184&lt;br /&gt;-----------&lt;br /&gt;519|368|427&lt;br /&gt;346|725|918&lt;br /&gt;872|491|563&lt;br /&gt;-----------&lt;br /&gt;231|987|645&lt;br /&gt;467|512|839&lt;br /&gt;985|643|271&lt;br /&gt;&lt;br /&gt;Bottom Right:&lt;br /&gt;215|849|637&lt;br /&gt;348|627|591&lt;br /&gt;976|153|824&lt;br /&gt;-----------&lt;br /&gt;869|431|752&lt;br /&gt;154|972|368&lt;br /&gt;732|568|149&lt;br /&gt;-----------&lt;br /&gt;521|796|483&lt;br /&gt;483|215|976&lt;br /&gt;697|384|215&lt;br /&gt;&lt;br /&gt;Center:&lt;br /&gt;978|215|463&lt;br /&gt;431|678|529&lt;br /&gt;265|439|781&lt;br /&gt;-----------&lt;br /&gt;813|754|692&lt;br /&gt;649|382|157&lt;br /&gt;527|961|834&lt;br /&gt;-----------&lt;br /&gt;396|847|215&lt;br /&gt;752|196|348&lt;br /&gt;184|523|976&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Lessons learned: "automate automate automate!" and "Don't work hard, but work smart!" :)&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The algorithm is very simple. The model is based on the simplest unit - a cell which is part of a row, column and a square. Each puzzle consists of 9 columns, 9 rows and 9 squares. Cells are flexible enough to be part of more than one square, row or a column at a time, so I can have cells that are present in more than one puzzle at a time. With a flexible model like this, all that the program needs to do is to use some basic rules to eliminate candidates and determine cell values. This type of solver is often referred to as human-style solver, because it uses the same techniques used by humans.&lt;br /&gt;&lt;br /&gt;If anyone is interested in having a look at the source code it can be downloaded from here: &lt;a href="http://mediacast.sun.com/users/IgorMinar/media/multi_sudoku_solver.rb/details"&gt;multi_sudoku_solver.rb&lt;/a&gt;. A word of warning - the code is not cleaned up nor documented. This was just a fun project that I worked on, and the fact that I achieved what I set out to achieve was a good enough milestone to call this project done.&lt;br /&gt;&lt;br /&gt;After this experience I don't feel like solving sudoku puzzles any more. They are just a repetitious problems that are well suited for automated solution. Most importantly - I actually had more fun writing the program than I had with solving the puzzle :).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-4501570224674767458?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/WnDhti1_1eY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/4501570224674767458/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=4501570224674767458" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/4501570224674767458?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/4501570224674767458?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/WnDhti1_1eY/multisudokusolver-fun-project-i-worked.html" title="MultiSudokuSolver - A fun project I worked on during the Xmas break" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/_p64VvtgZDp4/R5EAAJlhkrI/AAAAAAAAAG0/IaSZEI871gM/s72-c/sudoku.jpg" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total><feedburner:origLink>http://blog.igorminar.com/2008/01/multisudokusolver-fun-project-i-worked.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DU8NR3s7fCp7ImA9WB9WGEo.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-6066152747185930454</id><published>2007-11-23T19:20:00.000-08:00</published><updated>2007-11-23T20:18:16.504-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2007-11-23T20:18:16.504-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Gadgets" /><title>Shure SE210 Earphones (upgrade from E2c)</title><content type="html">When I bought my &lt;a href="http://www.shure.com/PersonalAudio/Products/Earphones/ESeries/us_pa_E2c_content"&gt;Shure E2c&lt;/a&gt; earphones two years ago, I was blown away by the sound quality. I never spend more than $50 for earphones before and after buying the E2c model I would never consider downgrading to anything less. Shure really knows how to create addictive high quality products. The quality of the sound produced by my old E2c earphones was so good that I had to get rid of some albums that I ripped into mp3s long time ago when hard drive space was measured in megabytes rather than in gigabytes.&lt;br /&gt;&lt;br /&gt;After two years of slave-like everyday service my Shure E2c earphones started to show signs of wear and it became apparent that they were going to retire soon.&lt;br /&gt;&lt;br /&gt;Afraid of being without a good pair of earphones during my upcoming transatlantic flight, I got a new pair of &lt;a href="http://www.shure.com/PersonalAudio/Products/Earphones/SEModels/us_pa_SE210_content"&gt;Shure SE210&lt;/a&gt;. The design has change slightly compared to the older model, but it still preserves the main features that I loved so much.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_p64VvtgZDp4/R0eTfr1wcUI/AAAAAAAAAGI/gdKXHGafCUU/s1600-h/shure_se210.jpg"&gt;&lt;img style="border: medium none ; margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_p64VvtgZDp4/R0eTfr1wcUI/AAAAAAAAAGI/gdKXHGafCUU/s400/shure_se210.jpg" alt="" id="BLOGGER_PHOTO_ID_5136236072592765250" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I've been listening to some music via these earphones for only an hour or so, but I can already tell that SE210 brings "great sound" to a brand new level. I don't consider myself to be an audiophile, but even my untrained ear can hear the big difference between the two. The new pair fits better as well as feels more comfortable in my ears.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://j0ni.livejournal.com/"&gt;J&lt;/a&gt;, lost his mind a couple of weeks ago and bought a pair of the high-end &lt;a href="http://www.shure.com/PersonalAudio/Products/Earphones/SEModels/us_pa_se530_content"&gt;SE530&lt;/a&gt;. I don't even want to know what those sound like. I know that if I tried them, I would be tempted to pay an insane amount of money for them, so I better say away :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-6066152747185930454?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/cExeYNSk2iQ" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/6066152747185930454/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=6066152747185930454" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/6066152747185930454?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/6066152747185930454?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/cExeYNSk2iQ/shure-se210-earphones-upgrade-from-e2c.html" title="Shure SE210 Earphones (upgrade from E2c)" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/_p64VvtgZDp4/R0eTfr1wcUI/AAAAAAAAAGI/gdKXHGafCUU/s72-c/shure_se210.jpg" height="72" width="72" /><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">6</thr:total><feedburner:origLink>http://blog.igorminar.com/2007/11/shure-se210-earphones-upgrade-from-e2c.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkADSXY9fip7ImA9WB9QFko.&quot;"><id>tag:blogger.com,1999:blog-6406593750327945950.post-3977636666644878266</id><published>2007-10-29T09:30:00.000-07:00</published><updated>2007-10-29T09:19:38.866-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2007-10-29T09:19:38.866-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Software Engineering" /><category scheme="http://www.blogger.com/atom/ns#" term="Rails" /><title>Inflector/environment.rb Bug in Rails</title><content type="html">Today I needed to get Rails to grok an uncountable noun "media". Easy you think, right? Just add an inflection rule.&lt;br /&gt;&lt;br /&gt;So I went to &lt;code&gt;config/environment.rb&lt;/code&gt;, uncommented the Inflection example and modified it with my "media" noun, just like this:&lt;pre&gt;Rails::Initializer.run do |config|&lt;br /&gt;  ...&lt;br /&gt;  ...&lt;br /&gt;  ...&lt;br /&gt;  &lt;br /&gt;  Inflector.inflections do |inflect|&lt;br /&gt;    inflect.uncountable %w( media )&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  # See Rails::Configuration for more options&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;# Add new mime types for use in respond_to blocks:&lt;br /&gt;# Mime::Type.register "text/richtext", :rtf&lt;br /&gt;# Mime::Type.register "application/x-mobile", :mobile&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;When I tried to run rails console or tests I saw an error like this:&lt;pre style="overflow: auto;"&gt;&lt;br /&gt;me@Xbook:testapp$ ./script/console &lt;br /&gt;Loading development environment.&lt;br /&gt;./script/../config/../config/environment.rb:55:NameError: uninitialized constant Inflector&lt;br /&gt;/opt/local/lib/ruby/gems/1.8/gems/actionpack-1.13.5/lib/action_controller/assertions/selector_assertions.rb:525:NoMethodError: undefined method `camelize' for "top":String&lt;br /&gt;/Users/me3x/Development/DSE/mediacast2/apps/testapp/app/controllers/application.rb:4:NameError: uninitialized constant ActionController::Base&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The problem turned out to be in the commented-out rails example in &lt;code&gt;environment.rb&lt;/code&gt;. The &lt;code&gt;Inflector.inflections&lt;/code&gt; block must not be nested within the &lt;code&gt;Initializer.run&lt;/code&gt; block.&lt;br /&gt;&lt;br /&gt;This fixes the problem:&lt;pre&gt;&lt;br /&gt;Rails::Initializer.run do |config|&lt;br /&gt;  ...&lt;br /&gt;  ...&lt;br /&gt;  ...&lt;br /&gt;&lt;br /&gt;  # See Rails::Configuration for more options&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;Inflector.inflections do |inflect|&lt;br /&gt;  inflect.uncountable %w( media )&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;# Add new mime types for use in respond_to blocks:&lt;br /&gt;# Mime::Type.register "text/richtext", :rtf&lt;br /&gt;# Mime::Type.register "application/x-mobile", :mobile&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6406593750327945950-3977636666644878266?l=blog.igorminar.com'/&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/IgorMinarsBlog/~4/0o1IcEVLlZw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://blog.igorminar.com/feeds/3977636666644878266/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=6406593750327945950&amp;postID=3977636666644878266" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/3977636666644878266?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/6406593750327945950/posts/default/3977636666644878266?v=2" /><link rel="alternate" type="text/html" href="http://feeds.igorminar.com/~r/IgorMinarsBlog/~3/0o1IcEVLlZw/inflectorenvironmentrb-bug-in-rails.html" title="Inflector/environment.rb Bug in Rails" /><author><name>Igor Minar</name><uri>http://www.blogger.com/profile/03520548417275543432</uri><email>noreply@blogger.com</email><gd:extendedProperty name="OpenSocialUserId" value="16502904255273991736" /></author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total><feedburner:origLink>http://blog.igorminar.com/2007/10/inflectorenvironmentrb-bug-in-rails.html</feedburner:origLink></entry></feed>
