<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-8457759433255973831</id><updated>2012-02-16T06:07:37.134-08:00</updated><category term='stomp'/><category term='android'/><category term='frameworks'/><category term='python'/><category term='php'/><category term='coilmq'/><category term='tires'/><category term='dreamhost'/><category term='cycling'/><category term='cyanogenmod'/><category term='zine'/><category term='smartphone'/><category term='captivate'/><category term='review'/><category term='commuting'/><category term='pycon'/><category term='threading'/><category term='habanero cycles'/><title type='text'>Snakes that Bite</title><subtitle type='html'>Lessons learned and ah-ha moments in software development, cycling, and animal husbandry.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://snakesthatbite.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://snakesthatbite.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Hans</name><uri>http://www.blogger.com/profile/17473998616519034255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>11</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-8457759433255973831.post-1165213800677268467</id><published>2011-11-20T17:54:00.001-08:00</published><updated>2011-11-20T19:51:16.881-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='habanero cycles'/><category scheme='http://www.blogger.com/atom/ns#' term='cycling'/><category scheme='http://www.blogger.com/atom/ns#' term='review'/><title type='text'>Addicted to Ti: Habanero Cycles</title><content type='html'>My first road bike (of my adult life) was a 2002 Lemond Victoire. &amp;nbsp;It was a wonderful bike, but it had one fatal flaw: it didn't fit me. &amp;nbsp;Of course it took me awhile (about 4 years, I guess) to fully appreciate this; after all, I had nothing to compare it to. &amp;nbsp;I didn't realize that hitting ones knees on the front bars when pedaling out of the saddle wasn't the norm, that fairly significant (&amp;gt; 1") toe-front-wheel overlap wasn't a requirement, or that 12.5cm disparity between seat and handlebar height was as appropriate a position for a bike as a Snoop Dogg music video. Apparently I was a little naive. &amp;nbsp;And by "was", I mean up until a couple weeks ago when I sold that bike and bought a new Habanero Cycles frame.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://habcycles.com/"&gt;Habanero Cycles&lt;/a&gt;, judging from their site,&amp;nbsp;is a few guys down in Florida that act as a (super-lightweight) middle man for titanium frames made in China. &amp;nbsp;I think this graphic on the landing page pretty much sums up the shop:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-rbkF_WjtIEI/Tsm7kXuLYTI/AAAAAAAAAUk/t4ltPHC_OqM/s1600/nohype.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/-rbkF_WjtIEI/Tsm7kXuLYTI/AAAAAAAAAUk/t4ltPHC_OqM/s1600/nohype.gif" /&gt;&lt;/a&gt;&lt;/div&gt;This simple GIF, with its jagged pixel edges and MS-Paint-era aesthetic, eschews modern web design fashions and technologies. &amp;nbsp;Habanero Cycles with their straight tubes, traditional triangles, and encouragement of 1" head tubes seems to do much the &amp;nbsp;does the equivalent in the cycling world.&lt;br /&gt;&lt;br /&gt;But, perhaps unlike the GIF image above, I think a lot of people would agree that these are really timeless &lt;i&gt;and attractive&lt;/i&gt; &amp;nbsp;bicycles. &amp;nbsp;I certainly think so. &amp;nbsp;After all, this is my &lt;i&gt;second&amp;nbsp;&lt;/i&gt;Habanero Cycles frame.&lt;br /&gt;&lt;br /&gt;Last year, I decided to build a cyclocross bike for commuting, to replace my single speed. &amp;nbsp;I like riding a single speed, but I wanted a bike that could fit fenders and one that had gears since I was anticipating pulling a baby trailer with it on the weekends. &amp;nbsp;Here was the bike in "commuter mode" after building it and before actually making it practical for commuting (adding fenders, lights, switching to compact crankset, etc.):&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-ySUkwX3WfsA/Tsm_YmH4i5I/AAAAAAAAAUs/DQUtkPu677o/s1600/5369149404_4b03f38cd5_b.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="213" src="http://4.bp.blogspot.com/-ySUkwX3WfsA/Tsm_YmH4i5I/AAAAAAAAAUs/DQUtkPu677o/s320/5369149404_4b03f38cd5_b.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;As it turns out, it also works for riding as a cyclocross bike. &amp;nbsp;I raced in my first cx race this fall and plan to do another in a couple weeks.&lt;br /&gt;&lt;br /&gt;My new bike is a road frame, which I have to explain to my non-cycling friends and family is &lt;i&gt;very, very different&lt;/i&gt; from a cyclocross frame -- you just can't really tell by looking at it.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-3855RvcIkt0/TsnAnIDjsCI/AAAAAAAAAU0/0bk5ho94aiU/s1600/habanero-road.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="240" src="http://1.bp.blogspot.com/-3855RvcIkt0/TsnAnIDjsCI/AAAAAAAAAU0/0bk5ho94aiU/s320/habanero-road.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;(Yes, I removed the down-tube decals; I wasn't a big fan of the orange lettering on this model.)&lt;br /&gt;&lt;br /&gt;I think that I would be betraying the "NO HYPE" mantra of Habanero Cycles if I started weaving word tapestries (or at least potholders) from the cycling reviewer lexicon:&amp;nbsp;"lateral stiffness", "vertical compliance", "soak up road chatter", "effortless climbing", etc. &amp;nbsp;(but not too many &lt;i&gt;cetera&lt;/i&gt;, this is a shockingly shallow vocabulary pool). &amp;nbsp;I do, however, have a few general statements that I would like to make about both frames:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;They are very well built. &amp;nbsp;The craftsmanship is excellent; the finish fantastic; the welds are beautiful. &amp;nbsp;(I tried explaining this value of "beautiful welds" to my wife, but she seemed unconvinced that this is why it should cost as much as a personal computer.)&lt;/li&gt;&lt;li&gt;They are built like (light) tanks. &amp;nbsp;Strength and durability are values that Habanero Cycles&amp;nbsp;extols&amp;nbsp;and these frames implement those values. &amp;nbsp;And as a result ...&lt;/li&gt;&lt;li&gt;They're not super-lightweight frames. &amp;nbsp;I'd guess that you could probably find high-end designer steel frames around the same weight, but I can't imagine they would be anywhere near as strong or stiff. &amp;nbsp;My Lemond Victoire frame probably weighed almost 1 lb less than the Habanero that replaced it, but it was very flexy (wheels would rub brakes on out of seat climbing). &amp;nbsp;I would rather have a solid frame. &amp;nbsp;And you can save weight in other places. &amp;nbsp;My 60cm Habanero build weighted in at 17 lbs 3oz (compared to 18 lbs 11oz for my Lemond) -- and didn't break the bank.&lt;/li&gt;&lt;li&gt;They ride like bicycles. &amp;nbsp;In truth, I think both frames feel fantastic. &amp;nbsp;If I had to elaborate, I would say that the ride is similar to steel bikes I've ridden (e.g. my SE Lager single speed or the '80s Bianchi I rented last winter): the frame feels solid &amp;amp; mutes texture on paved surfaces. &amp;nbsp;To borrow another buzz phrase, these are also laterally stiff frames. &amp;nbsp;I don't notice them wagging side to side when standing on the pedals. &amp;nbsp;They both just kinda ride like bikes should.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Perhaps on par with the quality of the frames themselves, though, is the the customer service. I have worked mostly with Mark (I think he does the online stuff for them, and I tend to do more by email than phone) and the customer support has been superb. &amp;nbsp;We had many back and forth emails where he helped me choose frame size and suggestion other component dimensions to get the geometry I wanted; he's worked with me through some follow-up issues with seatpost slipping (diagnosis is bad seatpost binder bold; he's sending a replacement) -- answering emails on weekends, etc. &amp;nbsp;I have certainly never experienced such excellent customer service in the cycling industry.&lt;br /&gt;&lt;br /&gt;After writing this, I think it's less that I'm addicted to titanium, and more that I'm addicted to the ideals embodied by that "NO HYPE" GIF image. &amp;nbsp;I like the fact that these bikes don't claim to be anything more than well-built bicycles: they embrace the fundamental triangle design that all other frame designs circle around, use traditional dimensions and geometries, favor strength and durability over weight savings, and represent a really well-crafted product. &amp;nbsp;I do like the stress-free qualities (low-maintenance finish, no rusting) and ride quality, but I think this is just part of that same bigger picture here. &amp;nbsp;There's nothing wrong with innovation, but there's often nothing wrong with the original edition either.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8457759433255973831-1165213800677268467?l=snakesthatbite.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://snakesthatbite.blogspot.com/feeds/1165213800677268467/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://snakesthatbite.blogspot.com/2011/11/addicted-to-ti-habanero-cycles.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/1165213800677268467'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/1165213800677268467'/><link rel='alternate' type='text/html' href='http://snakesthatbite.blogspot.com/2011/11/addicted-to-ti-habanero-cycles.html' title='Addicted to Ti: Habanero Cycles'/><author><name>Hans</name><uri>http://www.blogger.com/profile/17473998616519034255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-rbkF_WjtIEI/Tsm7kXuLYTI/AAAAAAAAAUk/t4ltPHC_OqM/s72-c/nohype.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8457759433255973831.post-7934915663627103942</id><published>2011-10-19T12:50:00.000-07:00</published><updated>2011-11-20T17:53:33.532-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='commuting'/><category scheme='http://www.blogger.com/atom/ns#' term='tires'/><category scheme='http://www.blogger.com/atom/ns#' term='cycling'/><category scheme='http://www.blogger.com/atom/ns#' term='review'/><title type='text'>Commuter tires: the Continental 4 Seasons review</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: left;"&gt;Choosing tires always feels like such an important decision. &amp;nbsp;They're obviously an important aspect of a bike's setup, the interface to the environment, and a powerful fashion statement. &amp;nbsp;It's not a fleeting decision; tires last a long time -- or they're supposed to -- and while you can change them if they're wrong it's a pain and it makes your hands black. &amp;nbsp;I can only imagine that the weight of this decision is akin to drafting a fantasy football team -- or maybe even a &lt;i&gt;real&lt;/i&gt; football team.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;And probably much like a football team, you wouldn't want to use the &lt;i&gt;same &lt;/i&gt;tires from year to year. &amp;nbsp;No, you'd want to toss out the thing that worked great (in my case the&amp;nbsp;&lt;a href="http://www.panaracer.com/urban.php"&gt;Panaracer T-Serv&lt;/a&gt;&amp;nbsp;tires that I sold with my single speed) and change it up.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;b&gt;&lt;a href="http://www.conti-online.com/generator/www/de/en/continental/bicycle/themes/race/racetyres/grand_prix_4_season/gp4season_en.html"&gt;Continental Grand Prix 4 Seasons&lt;/a&gt;&lt;/b&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;MSRP $80 (!!!), street price $60+.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;I love my (700x23c) &lt;a href="http://www.conti-online.com/generator/www/de/en/continental/bicycle/themes/race/racetyres/gatorskin/gatorskin_en.html"&gt;Continental Gatorskin&lt;/a&gt; tires on my road bike so much I decided that the Continental 4 Seasons were the tires for me. &amp;nbsp;After all, I &lt;i&gt;do&lt;/i&gt; ride for 4 seasons. &amp;nbsp;The marketing blurb struck a chord in my heart:&amp;nbsp;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;i&gt;A tire meant for road riders who do it the real way - ride all the time, on all types of roads, in all types of conditions. [...]&amp;nbsp;Get the speed and dependability you need with the 4 Season, built for the long haul road rider.&lt;/i&gt;&lt;/div&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;Perfect! At the time I found them on sale for $47 so the price wasn't just stupid. &amp;nbsp;I was starting off riding in the winter, so I figured it was a perfect test. &amp;nbsp;These are lightweight tires at 240g for the 700x25c.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;Well, my first rear tire only lasted 300 miles or so before the bead blew out on an otherwise serene ride into work. &amp;nbsp;Luckily it blew out right near work, so I could walk the bike in the last couple blocks.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-n4dsqPU5ovU/Tpsr5q1qrVI/AAAAAAAAAQY/XYK0lBkNB28/s1600/IMG_20111016_091856.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://4.bp.blogspot.com/-n4dsqPU5ovU/Tpsr5q1qrVI/AAAAAAAAAQY/XYK0lBkNB28/s320/IMG_20111016_091856.jpg" width="240" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;I made a halfhearted attempt to get Continental to warranty the tire, but they never got back to me. &amp;nbsp;I couldn't find my receipt. &amp;nbsp;I ordered from the internet. &amp;nbsp;So I walked into my LBS with my head hung low and paid full price for a replacement. &amp;nbsp;I figured that by paying the price of a one-way airplane ticket across the country, I'd probably be able to at least get that many miles out of it.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;Well, after approximately 3,000 miles this is what my rear tire looks like. &amp;nbsp;(The danger of having full fenders on your bike is that you can forget to check your rear tire.)&amp;nbsp;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-JYy5Z3mykFU/Tpsr7j5DobI/AAAAAAAAAQg/KXu0uMhbTnY/s1600/IMG_20111015_191222.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="http://1.bp.blogspot.com/-JYy5Z3mykFU/Tpsr7j5DobI/AAAAAAAAAQg/KXu0uMhbTnY/s320/IMG_20111015_191222.jpg" width="240" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;I have to say that I find it a little ironic they're called "4 seasons", since they only seem to last two. &amp;nbsp;Maybe by "long haul road rider" they actually meant, "someone that takes a short spin on the weekends -- weather permitting".&lt;br /&gt;&lt;br /&gt;Maybe 3,000 miles is expected (though it looks like I should have replaced these hundreds of miles ago), but it seems a little premature to me.&lt;br /&gt;&lt;br /&gt;I will say that the tires have good grip and they did as well as could be expected on icy trails. &amp;nbsp;Probably the softness of the rubber is what's accounting for the wear. &amp;nbsp;I did get a flat once when I drove over glass on the nearby trail, but one can only expect so much of rubber.&lt;br /&gt;&lt;br /&gt;So, your mileage may vary. &amp;nbsp;(Hopefully your mileage is greater than mine.)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8457759433255973831-7934915663627103942?l=snakesthatbite.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://snakesthatbite.blogspot.com/feeds/7934915663627103942/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://snakesthatbite.blogspot.com/2011/10/commuter-tires-continental-4-seasons.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/7934915663627103942'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/7934915663627103942'/><link rel='alternate' type='text/html' href='http://snakesthatbite.blogspot.com/2011/10/commuter-tires-continental-4-seasons.html' title='Commuter tires: the Continental 4 Seasons review'/><author><name>Hans</name><uri>http://www.blogger.com/profile/17473998616519034255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-n4dsqPU5ovU/Tpsr5q1qrVI/AAAAAAAAAQY/XYK0lBkNB28/s72-c/IMG_20111016_091856.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8457759433255973831.post-19917485280023547</id><published>2011-10-15T18:27:00.000-07:00</published><updated>2011-10-15T18:27:18.937-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='cyanogenmod'/><category scheme='http://www.blogger.com/atom/ns#' term='smartphone'/><category scheme='http://www.blogger.com/atom/ns#' term='captivate'/><category scheme='http://www.blogger.com/atom/ns#' term='android'/><title type='text'>CyanogenMod, you saved my Captivate.</title><content type='html'>I've decided to start writing a few short blog posts again. &amp;nbsp;I think my problem with "contributing to the internet" is that it does require a certain sense of confidence in the value of one's thoughts or opinions. &amp;nbsp;I typically don't think mine are worth sharing, but every now and then I do think "this might help others avoid misery". &amp;nbsp;This is one of those times. &amp;nbsp;Oh, Samsung Captivate, it is of you I sing.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-3zINAqkS-pM/TpocrKRyYMI/AAAAAAAAAQI/QH6_hla16Xo/s1600/captivate.jpeg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="308" src="http://4.bp.blogspot.com/-3zINAqkS-pM/TpocrKRyYMI/AAAAAAAAAQI/QH6_hla16Xo/s320/captivate.jpeg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;If you were, as I was, lured away from the iPhone to the Samsung Captivate (the Galaxy S model for AT&amp;amp;T), I am sorry for what you have had to endure. &amp;nbsp;Don't let this phone drive you away from Android. &amp;nbsp;There's a light at the end of this tunnel; it's cyan-colored.&lt;br /&gt;&lt;br /&gt;To be fair here, there are some good things about the Captivate.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;The screen. &amp;nbsp;The Super AMOLED screen on the Captivate is beautiful. &amp;nbsp;Maybe it's because Samsung makes TVs, but they know how to do color, contrast, etc.&lt;/li&gt;&lt;li&gt;Photo and video quality. &amp;nbsp;The camera is a 5mp camera which takes good pictures (for a phone). &amp;nbsp;The HD video recording is also really great (for a phone). &amp;nbsp;It's made it possible to take lots of&amp;nbsp;decent quality, impromptu baby videos and that's really valuable. &amp;nbsp;The recording app itself is not that great, but the products are nice.&lt;/li&gt;&lt;li&gt;And of course, there's Android. &amp;nbsp;This phone has definitely done a poor job of "porting" it over, but I do love the Android OS. &amp;nbsp;I can run Python scripts on my phone, build apps without paying for a developer license, run Samba for sharing files to my desktop, view content in Flash, use the latest &amp;amp; greatest Google apps, etc. &amp;nbsp;And I believe in open software.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;... but there are some things that are not so great:&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Performance. &amp;nbsp;This phone with the official Android builds is SLOW! &amp;nbsp;The stock phone came with Eclaire (2.1), which was acceptable&amp;nbsp;but missing enough capabilities that it was a little hard to be slow at anything. &amp;nbsp;Everyone was waiting eagerly for Froyo (2.2). &amp;nbsp;I ran a "leaked" Froyo for a little bit, but it was horribly slow. &amp;nbsp;For example, it would take multiple attempts with the swipe pattern to unlock my phone because it was not recognizing touches; answering calls was hit or miss for the same reason. &amp;nbsp;Swiping between screens was jerky. &amp;nbsp;Apps would get stuck in unresponsive states. &amp;nbsp;It was a disaster. &amp;nbsp;I tried one of the "aftermarket" kernels that did improve speed but at the cost of a battery that only lasted half a day. &amp;nbsp;I downgraded to Eclaire. &amp;nbsp;Then official Froyo came out and I upgraded. &amp;nbsp;It was better than the leaked version, but not a lot. &amp;nbsp;Less jerky movements, but still lots of applications hanging for a long time. &amp;nbsp;Games like Need for Speed were jerky while playing. &amp;nbsp;If I was honest, it was pretty crappy.&lt;/li&gt;&lt;li&gt;Bluetooth. &amp;nbsp;So probably half of my phone calls are made in the car and I use my car's Bluetooth so that I don't have to be driving dangerously (or illegally) . &amp;nbsp;Well, the Samsung pairs fine with my car, but it would disconnect after being used for a couple of minutes. &amp;nbsp;Then it would reconnect. &amp;nbsp;So imagine driving down the highway at 65mph and your car saying "the bluetooth connection has been lost". &amp;nbsp;I'm trying to get the phone of out my pocket and yelling "hang on a sec" so that the other party doesn't say anything important that only my nether region could hear. As soon as I get it out of my pocket, the car picks up the link again. &amp;nbsp;What a piece of crap.&lt;/li&gt;&lt;li&gt;Samsung's Android skin and crapware. &amp;nbsp;Samsung made some sort of iPhone-like skin. &amp;nbsp;It's not really all that surprising that Apple is suing them. &amp;nbsp;Their app list was a cheap knock-off of the iPhone screens. &amp;nbsp;They provided a bunch of really junky applications that you couldn't uninstall. &amp;nbsp;The camera and video app worked but were pretty annoying to use. There are ways to work around this and replace some of the stock behaviors with Android (the beauty of Android), but the out-of-the-box OS experience was pretty disheartening. &amp;nbsp;If I was a less technically savvy or less adventurous user, I'm sure I would have returned this phone within a week.&lt;/li&gt;&lt;li&gt;Abysmal update schedule. &amp;nbsp;Samsung is way behind with their software updates. &amp;nbsp;Gingerbread was released in Dec 2010 and still hasn't been released for the Captivate (it's now Oct 2011).&lt;/li&gt;&lt;li&gt;GPS is horrible. &amp;nbsp;This is probably partly hardware related, but the functionality of the GPS has also been greatly affected by the OS version. &amp;nbsp;Some versions of the OS have worked better than others (Froyo was an improvement), but it's bad enough that it makes Google Navigation frequently useless (it keeps thinking I've left the route when I haven't, for example).&lt;/li&gt;&lt;li&gt;Locked down by AT&amp;amp;T. &amp;nbsp;They disabled "Untrusted Sources" for installing apps. &amp;nbsp; So you can only use the official market. &amp;nbsp;You can obviously work around this with rooting, but one shouldn't have to void their warranty to install something from Amazon's market or from an open-source google code project.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;On the whole, I would say that the negatives outweighed the positives quite significantly. &amp;nbsp;So if you had any less self-discipline-- or faster automatic windows in your car -- your Captivate is probably in a river or the ditch alongside some stretch of highway. &amp;nbsp;I'm sure you don't regret that decision. &amp;nbsp;If you do still have your Captivate, though, I have some good news: &lt;a href="http://www.cyanogenmod.com/"&gt;CyanogenMod&lt;/a&gt;.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://www.cyanogenmod.com/"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-OkOyT41yA_8/TpocuxzQwfI/AAAAAAAAAQQ/0g0s3U-b_Yw/s1600/cyanogen-logo-242x242.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;CyanogenMod is an open-source, aftermarket firmware for numerous smartphones; and as of version 7.1.0, the Samsung Captivate is on the list of supported phones. &amp;nbsp;(In theory the official ROMs are also open-source, since they have to be by [GPL] license, but they are not developed as open-source projects.) &amp;nbsp;CyanogenMod was not &lt;i&gt;trivial&lt;/i&gt; to install (more below) but was well worth it. &amp;nbsp;It is a huge improvement and basically makes this feel like a new, and vastly superior phone. &amp;nbsp;(I've been running this for around a month now.)&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;The Bluetooth now works. &amp;nbsp;I was worried since I assumed it uses the BlueZ Linux BT stack, which doesn't have the best reputation, but it works flawlessly in my car. &amp;nbsp;No more rummaging in my pocket, while yelling at the person on the other end.&lt;/li&gt;&lt;li&gt;The camera/video recorder app is vastly superior to Samsung's.&lt;/li&gt;&lt;li&gt;I have the latest (Gingerbread) version of the Android OS, with all the improvements that brings (not least of which is a much better keyboard).&lt;/li&gt;&lt;li&gt;Performance is fantastic. &amp;nbsp;Feels twice as fast as stock Froyo build. &amp;nbsp;No more jerky unlocking or call answering; no more jerkiness in Need for Speed car racing :)&lt;/li&gt;&lt;li&gt;CyanogenMod uses ADWLauncher as it's "skin" which is &amp;nbsp;also vastly superior to Samsung's.&lt;/li&gt;&lt;li&gt;No more crap applications I can't delete.&lt;/li&gt;&lt;li&gt;GPS seems to work better than any other OS/version I've tried. &amp;nbsp;It is still slow to lock on to exact position, but seems to track correctly and not randomly jump a few neighborhoods over. &amp;nbsp;It is definitely usable now.&lt;/li&gt;&lt;li&gt;Lots of developer-friendly features. &amp;nbsp;Obviously things like "Unsigned Sources" can be enabled. &amp;nbsp;There's also tethering and a while lot more. I haven't scratched the surface of this yet.&lt;/li&gt;&lt;li&gt;Battery life did not degrade. I would have expected that a phone that runs much faster would suck down battery faster, but I get the same battery life as before (which admittedly is only a day of few calls and moderate network use).&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;The downside is that &lt;b&gt;you'll void your warranty&lt;/b&gt;. &amp;nbsp;Of course, at this point Samsung's warranty is likely expired for me and even it it was still active, it was only going to guarantee me the crappy performance, poor hardware support, and inferior OS spin of the stock system. &amp;nbsp;Yeah, that was a pretty easy decision.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;Getting it installed is not hard if you have some patience, a strong stomach (you may need to descend into the bowels of the internet or wade through troll-infested forums for help or obscure workarounds). If things go wrong, it may help to have some understanding of Linux systems, but you're going to be following some forum steps either way. &amp;nbsp;There is a &lt;a href="http://wiki.cyanogenmod.com/wiki/Samsung_Captivate:_Full_Update_Guide"&gt;good installation guide on Cyanogen's wiki&lt;/a&gt;;&amp;nbsp;follow that and it may just work for you without a hitch. &amp;nbsp;Basically you will need to install a new kernel which supports ClockworkMod Recovery using Heimdall, then install ClockworkMod Recovery itself, and finally install the CyanogenMod ROM using ClockworkMod recovery application. &amp;nbsp;So you will first have a stock-ish system running on ClockworkMod Recovery before you then install CyanogenMod. &amp;nbsp;The beauty of this is that ClockworkMod will let you backup your current ROM completely in case something goes wrong. &amp;nbsp;Before you start you'll also want to make sure your apps (and their data) are all backed up. &amp;nbsp;I use Titanium Backup; it does this well.&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;I had one significant problem following the instructions. &amp;nbsp;If you are running (stock) Froyo, as I was, you will need to ensure that your "recovery" application is downgraded to version 2e. &amp;nbsp;The 3e recovery utility will only run/install signed packages, which prevented me from getting the ClockworkMod Recovery boot loader installed. &amp;nbsp;&lt;a href="http://forum.xda-developers.com/showthread.php?t=1243106"&gt;This thread&lt;/a&gt; may help. &amp;nbsp;Or Google for "2e recovery Captivate" and wade through the results. &amp;nbsp;When I originally had this problem I was able to find a thread where someone had posted just the 2e recovery utility, and I was able to overwrite the utility through the phone (needed root and a file utility that supported remounting partitions rw -- I told you this wasn't for weak stomachs) -- I'm not able to find that thread right now. &amp;nbsp;I was happy that I didn't have to boot into Windows to do any of the upgrade steps (I'm running Ubuntu 64-bit).&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;Anyway, hopefully this proves useful to someone. &amp;nbsp;If you haven't already thrown away your Captivate, don't; rescue it with CyanogenMod!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8457759433255973831-19917485280023547?l=snakesthatbite.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://snakesthatbite.blogspot.com/feeds/19917485280023547/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://snakesthatbite.blogspot.com/2011/10/cyanogenmod-you-saved-my-captivate.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/19917485280023547'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/19917485280023547'/><link rel='alternate' type='text/html' href='http://snakesthatbite.blogspot.com/2011/10/cyanogenmod-you-saved-my-captivate.html' title='CyanogenMod, you saved my Captivate.'/><author><name>Hans</name><uri>http://www.blogger.com/profile/17473998616519034255</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-3zINAqkS-pM/TpocrKRyYMI/AAAAAAAAAQI/QH6_hla16Xo/s72-c/captivate.jpeg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8457759433255973831.post-3848680327970862912</id><published>2010-11-05T20:19:00.000-07:00</published><updated>2010-11-06T05:18:52.675-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='stomp'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>stompclient: another STOMP client for the Python community</title><content type='html'>Astute readers (as if I have enough readers to actually differentiate) may remember that I've previously entered the python STOMP world with &lt;a href="http://snakesthatbite.blogspot.com/2010/02/stomp-and-coilmq.html"&gt;my CoilMQ project&lt;/a&gt;. And now I'm at it again, but this time from the client side with my (perhaps presumptuously named) &lt;a href="http://bitbucket.org/hozn/stompclient"&gt;stompclient&lt;/a&gt; project. Yes, there are other clients out there --&amp;nbsp;&lt;a href="http://pypi.python.org/pypi/stomp.py"&gt;stomp.py&lt;/a&gt;, &lt;a href="http://pypi.python.org/pypi/stomppy"&gt;stompy&lt;/a&gt;, and &lt;a href="http://pypi.python.org/pypi/stomppy"&gt;stomper&lt;/a&gt; to name the main players. So why a new one? &amp;nbsp;Well, we've been using a couple of these in various stages of production, and based on my experiences I'd decided that there was room for an additional player in this game.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;I wanted a STOMP client that would be easy to use without getting in the way. &amp;nbsp;For example, spawning listener threads from within the class (as stomp.py does) is easy; however, if you want to exercise some more control over how received messages are handled, it's in the way.&lt;/li&gt;&lt;li&gt;I wanted a STOMP client that would support a publish-only usage model. &amp;nbsp;Most of my needs to interact with a stomp server from Python have involved writing clients that need to just push messages onto topics/queues (e.g. from python WSGI web applications).&lt;/li&gt;&lt;li&gt;I wanted to also provide a helpful set of STOMP library utilities for other projects.  Specifically, I wanted to flesh out &amp; clean up the &lt;tt&gt;Frame&lt;/tt&gt; classes that I had started implementing for CoilMQ and add in my fixed version of stomper's &lt;tt&gt;FrameBuffer&lt;/tt&gt; that would traffic natively in frames.&lt;/li&gt;&lt;li&gt;I really wanted a better-documented, better-tested, and generally cleaner, more pythonic (pep-8) codebase.&lt;/li&gt;&lt;li&gt;And finally, this was really another opportunity to learn more about sockets and multi-threaded application design (&amp;amp; testing) in Python.&lt;br /&gt;&lt;/ol&gt;&lt;div&gt;Unlike HTTP, the STOMP protocol is not a serial request-response protocol. &amp;nbsp;This actually makes it non-trivial to write a client that can both send and receive messages, since you can't simply &lt;tt&gt;sock.send()&lt;/tt&gt; a frame and then &lt;tt&gt;sock.recv()&lt;/tt&gt; a frame and expect that to the response to your sent frame. &amp;nbsp; &amp;nbsp;Of course, this makes sense since a subscribing client also needs to be able to receive message frames from the server (without being in response to any request). &amp;nbsp;So to have a client that can both send and receive messages, there needs to be some sort of receiver loop constantly running.I chose to take inspiration from the way that the stompy does this and use queues. &amp;nbsp;My approach was a little simpler in that there is simply a listener loop (expected to be run in its own frame) that enqueues any received frames on the appropriate queue (e.g. message frames go on the message queue, receipt frames on the receipt queue, etc.). &amp;nbsp;Very simple, but seems quite effective.This approach is also flexible, since it means you could create pool of worker threads that all pull from the appropriate queue(s) to process messages concurrently. &amp;nbsp;(It probably wouldn't be too difficult to also provide a &lt;tt&gt;multiprocessing&lt;/tt&gt; implementation for the worker pool.)Here's the simple publish-only example that pushes some binary content (a pickled python object) onto a queue:&lt;pre class="brush:python;"&gt;import pickle&lt;br /&gt;from datetime import datetime&lt;br /&gt;&lt;br /&gt;from stompclient import PublishClient&lt;br /&gt;&lt;br /&gt;client = PublishClient(&amp;#39;127.0.0.1&amp;#39;, 61613)&lt;br /&gt;client.connect()&lt;br /&gt;payload = {&amp;#39;key&amp;#39;: &amp;#39;value&amp;#39;, &amp;#39;counter&amp;#39;: 0, &amp;#39;list&amp;#39;: [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;], &amp;#39;date&amp;#39;: datetime.now()}&lt;br /&gt;client.send(&amp;#39;/queue/example&amp;#39;, pickle.dumps(payload, protocol=pickle.HIGHEST_PROTOCOL))&lt;br /&gt;client.disconnect()&lt;br /&gt;&lt;/pre&gt;Head on over to the &lt;a href="http://bitbucket.org/hozn/stompclient"&gt;project website&lt;/a&gt; for more examples, documentation, and download links.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8457759433255973831-3848680327970862912?l=snakesthatbite.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://snakesthatbite.blogspot.com/feeds/3848680327970862912/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://snakesthatbite.blogspot.com/2010/11/stompclient-another-stomp-client-for.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/3848680327970862912'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/3848680327970862912'/><link rel='alternate' type='text/html' href='http://snakesthatbite.blogspot.com/2010/11/stompclient-another-stomp-client-for.html' title='stompclient: another STOMP client for the Python community'/><author><name>Hans</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://2.bp.blogspot.com/_z-KCnQBKy0M/S2x2JQhK8ZI/AAAAAAAAAAM/7wTLbAwGoX0/S220/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8457759433255973831.post-7064053225137980699</id><published>2010-09-12T15:04:00.000-07:00</published><updated>2010-09-15T07:15:57.810-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='threading'/><title type='text'>CPython Threading: Interrupting</title><content type='html'>I've decided to kick off a return to blogging with a series on multi-threaded development in Python (CPython, to be specific). &amp;nbsp;Yes, we all know &lt;a href="http://www.dabeaz.com/python/UnderstandingGIL.pdf"&gt;there's a GIL in the water&lt;/a&gt;, but multi-threading is still an extremely useful concurrency strategy in Python for i/o-bound activities ... which tends to characterize most of my use cases for concurrency. &amp;nbsp;But there are lots of things that make multi-threaded programming tricky and there aren't quite as many resources out there for Python (as there are for Java, say).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Disclaimer: &lt;/b&gt; I am not an expert at multi-threaded programming in Python (or any other language).  Most of this has been trial &amp;amp; error, some help from the Google, and a lot of foundation from the &lt;i&gt;excellent&lt;/i&gt; book on the subject by Brian Goetz: &lt;a href="http://www.javaconcurrencyinpractice.com/"&gt;Java Concurrency in Practice&lt;/a&gt; (despite the title, the principles in the book apply to Python too).  If you know of a better way or better explanation, please leave a comment so we can all benefit.&lt;br /&gt;&lt;br /&gt;After getting over some of the challenges of mutable state and atomicity of operations, I think one of the things that probably bit me next in Python specifically was handling of&amp;nbsp;asynchronous&amp;nbsp;exceptions (like &lt;tt&gt;KeyboardInterrupt&lt;/tt&gt; and in some cases &lt;tt&gt;SystemExit&lt;/tt&gt;) -- and specifically how one goes about actually &lt;i&gt;stopping&lt;/i&gt;&amp;nbsp;a multi-threaded application. &amp;nbsp;Too many times I&amp;nbsp;would end up with a script that would just hang when I hit CTRL-C (and I'd have to explicitly kill it). &amp;nbsp;So let's start there.&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;Asynchronous Exceptions&lt;/h1&gt;&lt;br /&gt;The &lt;tt&gt;KeyboardInterrupt&lt;/tt&gt; exception is actually an OS signal; specifically, the &lt;a href="http://docs.python.org/library/signal.html"&gt;signal module&lt;/a&gt; translates &lt;tt&gt;SIGINT&lt;/tt&gt; into the &lt;tt&gt;KeyboardInterrupt&lt;/tt&gt; exception.  The rule is that on platforms where&amp;nbsp;this &lt;tt&gt;signal&lt;/tt&gt; module is present, these signal exceptions will be raised in the &lt;i&gt;main thread&lt;/i&gt;. The &lt;tt&gt;SystemExit&lt;/tt&gt; exception is similar, in that no matter which thread raises it, it will always be raised in the main thread. &amp;nbsp;On other platforms, apparently they may be raised anywhere (see the "Caveats" section of&amp;nbsp;the &lt;a href="http://docs.python.org/library/thread.html"&gt;thread module reference documentation&lt;/a&gt; for more info); for the sake of focus here, we will assume that you are working on a platform with the &lt;tt&gt;signal&lt;/tt&gt; module present.&lt;br /&gt;&lt;br /&gt;Let's start out with a simple example of a multi-threaded program that you cannot abort with CTRL-C:&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: python;"&gt;import time&lt;br /&gt;import threading&lt;br /&gt;&lt;br /&gt;def dowork():&lt;br /&gt;  while True:&lt;br /&gt;    time.sleep(1.0)&lt;br /&gt;&lt;br /&gt;def main():&lt;br /&gt;  t = threading.Thread(target=dowork, args=(), name='worker')&lt;br /&gt;  t.start()&lt;br /&gt;&lt;br /&gt;  # Block until the thread completes.&lt;br /&gt;  t.join()&lt;br /&gt;&lt;br /&gt;if __name__ == '__main__':&lt;br /&gt;  main()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The problem here is that the &lt;i&gt;worker&lt;/i&gt;&amp;nbsp;thread will not exit when the main thread receives the &lt;tt&gt;KeyboardInterrupt&lt;/tt&gt; "signal". &amp;nbsp;So even though the &lt;tt&gt;KeyboardInterrupt&lt;/tt&gt; will be raised (almost certainly in the t.join() call), there's nothing to make the activity in the &lt;i&gt;worker&lt;/i&gt; thread stop.&amp;nbsp;As a result, you'll have to go kill that python process manually, sorry.&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;Stopping Worker Threads&lt;/h1&gt;&lt;br /&gt;&lt;h2&gt;Solution 1: Kill with Prejudice&lt;/h2&gt;&lt;br /&gt;So the quick fix here is to make the&amp;nbsp;&lt;i&gt;worker&lt;/i&gt;&amp;nbsp;thread a &lt;i&gt;daemon&lt;/i&gt;&amp;nbsp;thread. &amp;nbsp;From the &lt;a href="http://docs.python.org/library/threading.html"&gt;threading reference documentation&lt;/a&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;span class="Apple-style-span" style="font-family: sans-serif; font-size: 16px; line-height: 20px;"&gt;A thread can be flagged as a “daemon thread”. The significance of this flag is that the entire Python program exits when only daemon threads are left.&lt;/span&gt;&lt;/blockquote&gt;So in practice here, if you stop your main thread, your daemon thread will just stop in the middle of whatever it was doing &amp; exit.  In many cases this abrupt termination of any worker threads may be appropriate; however, there may also be cases where you actually want to manage what happens when your threads terminate; maybe they need to commit (or rollback) a transaction, save their state, etc.  For this a more thoughtful approach is required.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Solution 2: Instruct Politely&lt;/h2&gt;&lt;br /&gt;The alternative to just killing them is to instruct the thread to stop using some agreed-upon system.&amp;nbsp;You are probably aware (or have guessed) by now that there is no &lt;tt&gt;Thread.stop()&lt;/tt&gt; method in Python (and the one in Java is deprecated and generally considered a Bad Idea™). &amp;nbsp;So what you must do is to implement a "thread interruption policy" which in our case is basically a signaling mechanism that the main thread can use to tell the worker thread to stop. &amp;nbsp;Python provides a &lt;a href="http://docs.python.org/library/threading.html#event-objects"&gt;threading.Event&lt;/a&gt; class that is for exactly this type of inter-thread signaling.&lt;br /&gt;&lt;br /&gt;The &lt;tt&gt;threading.Event&lt;/tt&gt; objects are very simple two-state (on/off) flags that can be used without any additional locking to pass "messages" between threads. Here is a basic stratagy for using a &lt;tt&gt;threading.Event&lt;/tt&gt; to communicate a 'shutdown' message to a worker thread:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;You share a "shutdown" &lt;tt&gt;threading.Event&lt;/tt&gt; instance between the threads (i.e. you either pass it to the threads or put it in a mutually accessible place).&lt;/li&gt;&lt;li&gt;You &lt;i&gt;set&lt;/i&gt; the event from the main thread when you receive the appropriate signal.  Here we're focused on &lt;tt&gt;KeyboardInterrupt&lt;/tt&gt;, but presumably users could also take some action within your application (e.g. "stop" button) to stop your application, i.e.&lt;br /&gt;&lt;pre class="brush: python;"&gt;shutdown_event.set()&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;You check it (frequently) in another thread and take the appropriate action once it has been set.&lt;pre class="brush: python;"&gt;while not shutdown_event.is_set():&lt;br /&gt;   do_some_work()&lt;br /&gt;do_some_cleanup()&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;It is probably worth pointing out here that this system is really just some conventions that you've established between your main thread and the workers.  If the workers don't periodically check the shutdown event, then they won't stop their work -- and CTRL-C still won't work.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Putting it Together&lt;/h3&gt;&lt;br /&gt;After applying the &lt;tt&gt;threading.Event&lt;/tt&gt; model to our example, we are able to have our CTRL-C respected relatively quickly (as quickly as the worker thread gets around to checking the event).&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:python"&gt;import time&lt;br /&gt;import threading&lt;br /&gt;&lt;br /&gt;shutdown_event = threading.Event()&lt;br /&gt;&lt;br /&gt;def dowork():&lt;br /&gt;  while not shutdown_event.is_set():&lt;br /&gt;    time.sleep(1.0)&lt;br /&gt;&lt;br /&gt;def main():&lt;br /&gt;  """ Start some threads &amp;amp; stuff. """&lt;br /&gt;&lt;br /&gt;  t = threading.Thread(target=dowork, args=(), name='worker')&lt;br /&gt;  t.start()&lt;br /&gt;&lt;br /&gt;  try:&lt;br /&gt;    while t.is_alive():&lt;br /&gt;      t.join(timeout=1.0)&lt;br /&gt;  except (KeyboardInterrupt, SystemExit):&lt;br /&gt;    shutdown_event.set()&lt;br /&gt;&lt;br /&gt;if __name__ == '__main__':&lt;br /&gt;  main()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;Working around uninterruptable Thread.join()&lt;/h4&gt;&lt;br /&gt;You may have noticed that we changed how we called &lt;tt&gt;Thread.join()&lt;/tt&gt;.  Calling the &lt;tt&gt;join()&lt;/tt&gt; method on a thread without a timeout will block until that thread returns/completes.  As I understand it, this is due to a mutex in the &lt;tt&gt;join()&lt;/tt&gt; method which has the implication that you &lt;i&gt;cannot interrupt it&lt;/i&gt; with &lt;tt&gt;KeyboardInterrupt&lt;/tt&gt;. &amp;nbsp;You can work around this, though, by essentially checking in a loop until the thread does exit:&lt;br /&gt;&lt;pre class="brush: python;"&gt;while t.is_alive():&lt;br /&gt;      t.join(timeout=0.1)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;Other Events and Exceptions&lt;/h3&gt;&lt;br /&gt;You may notice that in the compiled example that I am also catching the &lt;tt&gt;SystemExit&lt;/tt&gt; for sake of completeness. In a more complex app, you would need to make sure that other exceptions were also handled so that they would result in the shutdown message going to the worker threads.&lt;br /&gt;&lt;br /&gt;You could also choose to register a signal handler (in your main thread) for other OS signals and raise an appropriate exception (e.g. &lt;tt&gt;SystemExit&lt;/tt&gt;) or take other actions.  The important point here is that these would all need to be handled in your main thread and communicated by some sort of convention to the worker thread(s).&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;In Summary&lt;/h1&gt;&lt;br /&gt;Dealing with these "asynchronous events" in multi-threaded applications can be a little confusing (and sometimes a little frustrating when your app refuses to exit).  Understanding the key points here will hopefully help make this a bit clearer:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Signals are handled by the main thread.  This means that &lt;tt&gt;KeyboardInterrupt&lt;/tt&gt; is always raised in the main thread.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Daemon&lt;/i&gt; threads will exit automatically when the main thread exits.&lt;/li&gt;&lt;li&gt;For cases where you need more control over thread termination, use &lt;tt&gt;threading.Event&lt;/tt&gt; objects to "signal" threads to exit.&lt;/li&gt;&lt;li&gt;Be aware that &lt;tt&gt;Thread.join()&lt;/tt&gt; calls will block and cannot be interrupted!  Use an alternative while-loop strategy for joining instead.&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8457759433255973831-7064053225137980699?l=snakesthatbite.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://snakesthatbite.blogspot.com/feeds/7064053225137980699/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://snakesthatbite.blogspot.com/2010/09/cpython-threading-interrupting.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/7064053225137980699'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/7064053225137980699'/><link rel='alternate' type='text/html' href='http://snakesthatbite.blogspot.com/2010/09/cpython-threading-interrupting.html' title='CPython Threading: Interrupting'/><author><name>Hans</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://2.bp.blogspot.com/_z-KCnQBKy0M/S2x2JQhK8ZI/AAAAAAAAAAM/7wTLbAwGoX0/S220/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8457759433255973831.post-7754308722074621924</id><published>2010-02-27T04:45:00.000-08:00</published><updated>2010-11-05T20:20:20.450-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='stomp'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='coilmq'/><title type='text'>STOMP and CoilMQ</title><content type='html'>I've just pushed out my third release (v0.3) of &lt;a href="http://code.google.com/p/coilmq/"&gt;CoilMQ&lt;/a&gt;, a simple STOMP server written in Python.&amp;nbsp; (You can see &lt;a href="http://code.google.com/p/coilmq/wiki/Roadmap"&gt;the roadmap&lt;/a&gt; or &lt;a href="http://code.google.com/p/coilmq/issues/list?can=1&amp;amp;q=label%3AMilestone-0.3"&gt;the issue tracker&lt;/a&gt; to learn about the changes.)&amp;nbsp; It's been a fun project, so I decided to write about why I've been working on it and why you might want to consider Stomp for message passing.&lt;br /&gt;&lt;br /&gt;&lt;b&gt; &lt;/b&gt;&lt;br /&gt;&lt;b&gt;Project Background: A &lt;strike&gt;Twisted&lt;/strike&gt; Snake-y Road &lt;/b&gt;&lt;br /&gt;&lt;br /&gt;A few months back I started a simple project to learn more about writing a basic network server in Python.&amp;nbsp; I decided I would create a &lt;a href="http://stomp.codehaus.org/"&gt;STOMP&lt;/a&gt; server in Python, because there weren't any -- &lt;i&gt;well, none that I could get to actually work&lt;/i&gt;.&amp;nbsp; And there was this fantastic &amp;amp; simple &lt;a href="http://rubyforge.org/projects/stompserver/"&gt;stompserver Ruby project&lt;/a&gt; out there just taunting Python with its simple, concise implementation.&lt;br /&gt;&lt;br /&gt;So, I plowed into the project and immediately resolved, as all aspiring Python developers do, that this was perfectly suited to &lt;a href="http://twistedmatrix.com/"&gt;Twisted&lt;/a&gt;.&amp;nbsp; Afterall, the Ruby Stompserver project was built on &lt;a href="http://rubyeventmachine.com/"&gt;EventMachine&lt;/a&gt;. Nevermind that &lt;a href="http://www.morbidq.com/"&gt;MorbidQ &lt;/a&gt;seemed to have had the same idea (and was firmly on my "not-working" list).&amp;nbsp; Nevermind that I'd been there before, waist-deep in Twisted entrails trying to grab some slimy Deffered; I made myself write "Twisted is harder than &lt;i&gt;dataReceived&lt;/i&gt;" two thousand times.&amp;nbsp; Nevermind that &lt;a href="http://teddziuba.com/2009/09/twisted-vs-tornado-youre-both.html"&gt;Ted Dziuba wrote a brilliant tirade on Twisted&lt;/a&gt; which anyone that's had to wrestle this Gorgon can appreciate.&amp;nbsp; Nevermind all that.&lt;br /&gt;&lt;br /&gt;Well, that didn't last long.&amp;nbsp; Sure, I had an original version written using Twisted.&amp;nbsp; Very simple.&amp;nbsp; Everything in memory; no blocking I/O.&amp;nbsp; It looked architecturally like the Ruby EventMachine code.&amp;nbsp; Then I thought, &lt;i&gt;what about using a database for queue storage?&lt;/i&gt;&amp;nbsp; And that's when the romance ended.&amp;nbsp; Well, I don't have a database API that returns Deffereds; I just have one that'll stop my app while the query runs.&amp;nbsp; I know this was really just an educational project &amp;amp; proof of concept, but I didn't feel right doing it wrong.&lt;br /&gt;&lt;br /&gt;So, I decided that I'd write a my server using threads for concurrency.&amp;nbsp; I know it's not what the cool kids are doing, but -- despite the &lt;a href="http://blip.tv/file/2232410"&gt;GIL&lt;/a&gt; -- it's a great way to handle I/O concurrency in Python and I feel it's a really important thing for developers to understand -- because it comes up all the time.&amp;nbsp; (I'm no expert, don't get me wrong; this was a learning exercise.)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Why Stomp?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://stomp.codehaus.org/"&gt;Stomp&lt;/a&gt; is a really simple protocol for publisher-subscriber message passing.&amp;nbsp; It's loosely HTTP-like in its syntax and it works for passing text-based or binary data -- so you can pass whatever data type your consumers care about (JSON, XML, AMF, pickled objects, etc.).&amp;nbsp; The protocol itself doesn't explicitly prescribe topic or queue implementations, but there is a convention that destinations that begin with "/topic/" are topics (broadcast to all subscribers, no persistence or reliability) and destinations that begin with "/queue/" are queues (sent only to one subscriber, persisted).&amp;nbsp; Stomp does go beyond the basics to provide support for transactions and &lt;i&gt;reliable subscribers&lt;/i&gt; (that must ack receipt).&amp;nbsp; Some implementations (e.g. ActiveMQ) have also added the concept of &lt;i&gt;durable topics&lt;/i&gt;. &lt;br /&gt;&lt;br /&gt;There are lots of uses for Stomp because it's been implemented in lots of different languages.&amp;nbsp; For example, you could use simply use this as a way to pass data (e.g. pickled objects) between disparate web applications written in Python (e.g. using &lt;a href="http://pypi.python.org/pypi/stompy/"&gt;stompy&lt;/a&gt; client).&amp;nbsp; Or you could use Stomp to provide for passing messages between Python application servers and Flash/Flex clients (e.g. using &lt;a href="http://code.google.com/p/as3-stomp/%20"&gt;as3-stomp&lt;/a&gt; client); that's an easy way to implement server-push for Flex.&amp;nbsp; To build on that example, you could just as easily push AMF-encoded (binary) messages between different Flash clients.&amp;nbsp; So Stomp is really quite basic, and very versatile.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Why CoilMQ?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;This is a tricker question to answer.&amp;nbsp; Honestly, if you've got a project out there that needs to push messages at scale, you probably want to look at some of the enterprise brokers.&amp;nbsp; I've had great luck using &lt;a href="http://activemq.apache.org/"&gt;ActiveMQ&lt;/a&gt; and I've heard people that need to scale to massive throughput suggest using &lt;a href="http://www.rabbitmq.com/"&gt;RabbitMQ&lt;/a&gt;.&amp;nbsp; But if you &lt;i&gt;don't&lt;/i&gt; need to handle thousands of requests per second, and you &lt;i&gt;do&lt;/i&gt; want to understand the software you're using, then I would suggest that a project like CoilMQ is a great choice.&lt;br /&gt;&lt;br /&gt;Plus, it'd be great to have some feedback!&amp;nbsp; I've had a number of downloads &amp;amp; no bug reports (or complaints).&amp;nbsp; I'd love to think that means it just works perfectly for everyone, but I suspect the truth is that I just need more users.&amp;nbsp; I should add that I'd also be very pleased to accept any suggestions or patches (the former is greatly helped by the latter).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8457759433255973831-7754308722074621924?l=snakesthatbite.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://snakesthatbite.blogspot.com/feeds/7754308722074621924/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://snakesthatbite.blogspot.com/2010/02/stomp-and-coilmq.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/7754308722074621924'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/7754308722074621924'/><link rel='alternate' type='text/html' href='http://snakesthatbite.blogspot.com/2010/02/stomp-and-coilmq.html' title='STOMP and CoilMQ'/><author><name>Hans</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://2.bp.blogspot.com/_z-KCnQBKy0M/S2x2JQhK8ZI/AAAAAAAAAAM/7wTLbAwGoX0/S220/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8457759433255973831.post-2897107452269725266</id><published>2010-02-24T19:13:00.000-08:00</published><updated>2010-02-26T15:32:55.070-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='pycon'/><title type='text'>Notes from PyCon 2010</title><content type='html'>&lt;a href="http://us.pycon.org/2010/about/"&gt;PyCon&lt;/a&gt; just wrapped up; it was geektastic!&amp;nbsp; This was certainly the largest group of Python developers I'd ever witnessed, and I came away with some startling realizations about Pythonistas.&amp;nbsp; For example, I learned that, like Samon's strength, a Python developer's social standing is directly proportional to the length of his (or her) locks and beard.&amp;nbsp; I have stopped shaving and today I had an oprah-ah-ha moment with metaclasses and slots.&amp;nbsp; If I can make it through this awkward in-between phase, I can only imagine the revelations that lie ahead.&lt;br /&gt;&lt;br /&gt;Atlanta was interesting.&amp;nbsp; I mean we were in a very corporate, rich downtown: lots of big buildings housing banks and lots of people rolling on chromed 20s.&amp;nbsp; I'd never seen so much chrome.&amp;nbsp; The juxtaposition of Atlanta with Pycon was probably at its most poignant on Saturday night, when apparently the Hyatt hosts an &lt;i&gt;Old School Saturdays&lt;/i&gt; dance party in their downstairs ball rooms.&amp;nbsp; That happened to be right next to the Python open spaces.&amp;nbsp; They had some ropes to keep the groups separate.&amp;nbsp; I'm sure mayhem would have ensued if party goers had wandered into the Unladen Swallow open space.&lt;br /&gt;&lt;br /&gt;Anyway, back to the conference.&amp;nbsp; Some of the videos and slides are &lt;a href="http://python.mirocommunity.org/"&gt;being aggregated online&lt;/a&gt;.&amp;nbsp; The talks were great; I'm hoping that selection becomes more comprehensive.&lt;br /&gt;&lt;br /&gt;I definitely came away with a bunch of notes, things to try, etc&lt;br /&gt;&lt;ul&gt;&lt;li&gt; &lt;a href="http://us.pycon.org/media/2010/talkdata/PyCon2010/014/jimmysch-python-in-the-browser.pdf"&gt;Python in the browser&lt;/a&gt; (Silverlight) looked cool, but it's not really a viable option for Linux users.&amp;nbsp; Even the &lt;a href="http://go-mono.com/moonlight/prerelease.aspx"&gt;latest preview of Moonlight&lt;/a&gt; doesn't run the &lt;a href="http://ironpython.net/try/"&gt;IronPython REPL&lt;/a&gt; without errors.&amp;nbsp; (Stable version doesn't run it at all.)&amp;nbsp; Oh well, I'll try back later.&lt;/li&gt;&lt;li&gt;&lt;a href="http://python.mirocommunity.org/video/1226/changes-to-the-gil-in-python-3"&gt;The GIL on multi-core isn't fixed &lt;i&gt;yet&lt;/i&gt; in 3.2&lt;/a&gt; -- and this problem is relevant even when dealing with IO-bound instead of CPU-bound apps.&amp;nbsp; David Beazley's presentation was fantastic.&amp;nbsp; Of course, threading still makes sense for handling IO load, but it's good to have this limitation commonly understood.&lt;/li&gt;&lt;li&gt;&lt;a href="http://us.pycon.org/media/2010/talkdata/PyCon2010/128/PyCon2010_Eventlet_.pdf"&gt;Donovan Preston's talk&lt;/a&gt; definitely made me want to check out &lt;a href="http://www.eventlet.net/"&gt;Eventlet&lt;/a&gt;.&amp;nbsp; One of the biggest drawbacks of frameworks like Twisted is that you have to write code in a special way (i.e. using deffereds, or yield) and the implication of this is that you also cannot just take other people's code and include it in your project.&amp;nbsp; That's a huge problem.&amp;nbsp; However, even with Eventlet you do have the drawbacks of it being a non-blocking server.&amp;nbsp; So I was even &lt;i&gt;more&lt;/i&gt; excited to learn about &lt;a href="http://pypi.python.org/pypi/Spawning/"&gt;Spawning&lt;/a&gt;.&amp;nbsp; From what I understood, Spawning combines the power of the async server with actual processes, that make it easy to offload those blocking IO calls -- i.e. your database queries won't pause your entire application.&lt;/li&gt;&lt;li&gt;&lt;a href="http://us.pycon.org/2010/conference/schedule/event/27/"&gt;The State of Packaging&lt;/a&gt; (slides aren't up at time of writing) was a great look at improvements that are being made to the packaging metadata in Python.&amp;nbsp; &lt;a href="http://ziade.org/"&gt;Tarek Ziadé&lt;/a&gt; is to be commended for tackling this problem.&amp;nbsp; And oh what a problem it is!&lt;/li&gt;&lt;li&gt;The &lt;a href="http://docs.python.org/dev/library/unittest.html"&gt;new unittest&lt;/a&gt;&amp;nbsp; has some &lt;i&gt;really useful &lt;/i&gt;improvements (like multi-line text diffs for assertEqual!).&amp;nbsp; And it's been backported as &lt;a href="http://pypi.python.org/pypi/unittest2/"&gt;unittest2&lt;/a&gt; for us folks still stuck on 2.6.&lt;/li&gt;&lt;li&gt;I will use &lt;a href="http://pypi.python.org/pypi/Dozer/"&gt;Dozer&lt;/a&gt;!&amp;nbsp; (It's memory profiling WSGI middleware.&amp;nbsp; How simple!)&lt;/li&gt;&lt;li&gt;I will use &lt;a href="http://pypi.python.org/pypi/repoze.profile"&gt;repoze.profile&lt;/a&gt;!&amp;nbsp; (It's a performance profiling middleware.)&lt;/li&gt;&lt;li&gt;When I think that I have a problem best solved by &lt;a href="http://wiki.nginx.org/Main"&gt;Nginx&lt;/a&gt;, I will also evaluate &lt;a href="http://haproxy.1wt.eu/"&gt;HAProxy&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Apparently people love &lt;a href="http://munin.projects.linpro.no/"&gt;Munin&lt;/a&gt;.&amp;nbsp; They say it's much better than &lt;a href="http://www.nagios.org/"&gt;Nagios&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;I'm sure there was a lot more.&amp;nbsp; I got bored of scouring down my notes and looking up links.&amp;nbsp; It was a fantastic conference, though.&amp;nbsp; If you didn't go this year, go next year.&amp;nbsp; I hear it's in Atlanta again; bring a purple suit and they might let you into &lt;i&gt;Old School Saturdays&lt;/i&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8457759433255973831-2897107452269725266?l=snakesthatbite.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://snakesthatbite.blogspot.com/feeds/2897107452269725266/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://snakesthatbite.blogspot.com/2010/02/notes-from-pycon-2010.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/2897107452269725266'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/2897107452269725266'/><link rel='alternate' type='text/html' href='http://snakesthatbite.blogspot.com/2010/02/notes-from-pycon-2010.html' title='Notes from PyCon 2010'/><author><name>Hans</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://2.bp.blogspot.com/_z-KCnQBKy0M/S2x2JQhK8ZI/AAAAAAAAAAM/7wTLbAwGoX0/S220/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8457759433255973831.post-4172234473596430647</id><published>2010-02-15T11:55:00.001-08:00</published><updated>2010-02-15T11:55:44.783-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='frameworks'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>Coming from PHP: Share Something</title><content type='html'>This is the first in a series of posts I'd like to do about Python from the perspective of someone coming from PHP development.  Others have certainly &lt;a href="http://www.sitepoint.com/blogs/2008/05/09/a-php-guy%E2%80%99s-look-at-python/"&gt;posted&lt;/a&gt; &lt;a href="http://breadandpepper.com/blog/2009/jun/8/switching-php-pythondjango/"&gt;articles&lt;/a&gt; on similar topics; heck, there's even &lt;a href="http://www.php2python.com/"&gt;a fantastic site dedicated to providing the Python equivalent of PHP functionality&lt;/a&gt;.&amp;nbsp; While I'm sure that I'll talk a bit about some of the building-blocks in the Python language, these posts will focus on language features, interpreter implementations, and deployment platforms that lend to Python's use in large-scale applications.&lt;br /&gt;&lt;br /&gt;What do we mean by &lt;i&gt;large-scale&lt;/i&gt; applications?&lt;br /&gt;&lt;br /&gt;Well, I don't know who "we" is, but &lt;i&gt;I&lt;/i&gt; mean applications big enough to require thinking about how the application is architected.&amp;nbsp; Of course, it's more than just thinking about how the application will &lt;i&gt;work&lt;/i&gt;, but also how it will be tested, how it will be maintained, how it will grow, how security policies will be implemented, and how this can all be done as efficiently (and &lt;a href="http://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;non-repetitively&lt;/a&gt;) as possible.&amp;nbsp; These concerns typically push developers in the direction of an existing framework or into the typically-under-estimated effort of writing their own.&lt;br /&gt;&lt;br /&gt;Web application frameworks traditionally start with a high-level look at how requests are handled by the server and turn that into abstraction points.&amp;nbsp; Typically this ends up in one of the many interpretations of Model-View-Controller and the general phases in the processing look something like this:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Incoming request is dispatched (maybe via mod_rewrite) to a single handler script.&amp;nbsp; (&lt;a href="http://www.phppatterns.com/docs/design/the_front_controller_and_php"&gt;FrontController&lt;/a&gt; pattern)&lt;/li&gt;&lt;li&gt;A routing sub-system looks at the request (usually the requested &lt;i&gt;path&lt;/i&gt;) and determines what piece of server-side code should handle that. &lt;/li&gt;&lt;li&gt;The request is probably further processed for things like authentication requirements and then (if other checks pass) handed off to the server-side processing code (sometimes called an &lt;i&gt;Action&lt;/i&gt; sometimes the &lt;i&gt;Controller&lt;/i&gt; sometimes a &lt;i&gt;View&lt;/i&gt;).&lt;/li&gt;&lt;li&gt;The processing code will perform the "meat" of the processing (a typical application will probably query the database, for example) and produce some sort of &lt;i&gt;response&lt;/i&gt; that should get sent back to the client.&lt;/li&gt;&lt;li&gt;Typically there is a final phase where a more abstract response is encoded into the format that the client expects (e.g. JSON or XML); alternatively, for more traditional HTML applications, the response data may get passed as the context for a template.&lt;/li&gt;&lt;/ol&gt;There's a lot of boilerplate code there and a lot of resources that need to get loaded to process a request -- routing, authentication &amp;amp; session management, logging, business logic, model, template rendering, encoding, etc.&amp;nbsp; Even when not doing any work, the typical framework application will result in the loading of scores of classes, connecting to the database, opening file handles for logging.&amp;nbsp; Heaven forbid your webapp needs to do anything like open a socket connection. &lt;br /&gt;&lt;br /&gt;And this is why I think that &lt;a href="http://zef.me/883/the-share-nothing-architecture"&gt;PHP's share-nothing architecture&lt;/a&gt; is really a pretty dubious "feature".&amp;nbsp; Practically, it just means that all those resources that have to be setup for your framework have to be setup with &lt;i&gt;every single request&lt;/i&gt;.&amp;nbsp; Let's be honest here, this is not a feature; this is is a pretty severe limitation.&amp;nbsp; This is doublespeak to turn fundamental problems like "not thread-safe" and "leaks memory like a wet paper bag" into &lt;i&gt;features&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;Now, there is a real &lt;a href="http://en.wikipedia.org/wiki/Shared_nothing_architecture"&gt;shared-nothing architecture&lt;/a&gt; that describes an approach to develop scalable &amp;amp; concurrent software.&amp;nbsp; This really has very little to do with how this term has been used to describe PHP's architecture.&amp;nbsp; Furthermore, there's nothing about PHP that makes it uniquely able to "support" [its understanding of] share-nothing architecture.&amp;nbsp; It simply doesn't have the language or interpreter platform support to do anything else.&amp;nbsp; It's like saying that a single-speed bicycle is better than a geared bicycle because it's easier to understand.&lt;br /&gt;&lt;br /&gt;So, enter Python.&amp;nbsp; Python is certainly not unique in its deployment paradigm, but it does provide a healthy contrast to PHP.&amp;nbsp; To the point here, In Python you &lt;i&gt;can&lt;/i&gt; share stuff.&amp;nbsp; So, if you are using Apache with &lt;a href="http://code.google.com/p/modwsgi/"&gt;mod_wsgi&lt;/a&gt; (a popular Python hosting option, especially for frameworks), you can run Apache in multi-threaded &lt;a href="http://httpd.apache.org/docs/2.2/mod/worker.html"&gt;worker MPM&lt;/a&gt; and all those overhead resources only need to be initialized once per process -- not &lt;i&gt;per request&lt;/i&gt;.&amp;nbsp; Of course, if you wanted to, you could make it behave like PHP, but no one would do that; that'd be inefficient. &lt;br /&gt;&lt;br /&gt;So what is the price to pay for this sharing?&amp;nbsp; Well, there &lt;i&gt;is&lt;/i&gt; some additional complexity.&amp;nbsp; If you are running a multi-threaded environment (e.g. Apache worker MPM or another multi-threaded Python server) you do need to make sure that those resources (db connections, log handles, etc.) are thread-safe.&amp;nbsp; Typically in Python they are, but one does need to understand what that means.&amp;nbsp; So it will demand a little more, but for those of you developing full-stack frameworks in PHP, you know that you've already left the Green Zone.&lt;br /&gt;&lt;br /&gt;So, while we're all excited about applying the &lt;a href="http://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;DRY&lt;/a&gt; mantra to our software design, I think it's worth stopping to consider whether maybe there's a similar principle that could be applied to the server architecture.&amp;nbsp; Sharing resources between requests is a powerful feature.&amp;nbsp; In single-process (multi-threaded) systems is makes it possible to share state without persisting to an external store; in multi-process &amp;amp; mulit-threaded systems, it provides a huge efficiency improvement by handling the parsing and app setup / resource initialization only once per process.&lt;br /&gt;&lt;br /&gt;Share.&amp;nbsp; You'll feel better.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Of course, sometimes simpler &lt;/i&gt;is&lt;i&gt; better.  If you don't need an application framework, then you probably aren't concerned with eliminating repetitive overhead code.&amp;nbsp; To go back to our bicycle analogy, I actually do ride a single-speed bicycle to work because my commute is relatively flat and fewer mechanical parts fewer parts to replace.&lt;/i&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8457759433255973831-4172234473596430647?l=snakesthatbite.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://snakesthatbite.blogspot.com/feeds/4172234473596430647/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://snakesthatbite.blogspot.com/2010/02/coming-from-php-share-something.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/4172234473596430647'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/4172234473596430647'/><link rel='alternate' type='text/html' href='http://snakesthatbite.blogspot.com/2010/02/coming-from-php-share-something.html' title='Coming from PHP: Share Something'/><author><name>Hans</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://2.bp.blogspot.com/_z-KCnQBKy0M/S2x2JQhK8ZI/AAAAAAAAAAM/7wTLbAwGoX0/S220/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8457759433255973831.post-1314613684733468759</id><published>2010-02-11T13:22:00.000-08:00</published><updated>2010-02-11T13:32:05.362-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>Hobgoblins: Anonymous Functions in PHP 5.3</title><content type='html'>&lt;div style="text-align: left;"&gt;Among other things, PHP &lt;a href="http://php.net/manual/en/functions.anonymous.php"&gt;introduces anonymous functions&lt;/a&gt; (which they also call &lt;i&gt;closures&lt;/i&gt;) in PHP 5.3.&amp;nbsp; This is interesting, because normal functions or methods in PHP are not &lt;a href="http://en.wikipedia.org/wiki/First-class_function"&gt;first-class citizens&lt;/a&gt; and yet anonymous functions kinda &lt;i&gt;are&lt;/i&gt; -- or at least &lt;i&gt;look like&lt;/i&gt; they are.&amp;nbsp; And this is interesting, because I find first-class functions one of the really nice things about languages like Javascript and Python.&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;So, how does this work exactly in PHP?&amp;nbsp; Well, let's see if we can deduce how this works from some trial (&amp;amp; error).&lt;/div&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;&lt;span style="font-size: large;"&gt;The Basics&lt;/span&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;So, anonymous functions in PHP are most frequently used in callbacks.&amp;nbsp; As such, that's probably the first example you'll see:&lt;/div&gt;&lt;br /&gt;&lt;script class="brush: php" type="syntaxhighlighter"&gt;&lt;![CDATA[$a = array('z', 'd', 'e', 'g');usort($a, function($a, $b) {    if ($a == $b) {        return 0;    }    return ($a &lt; $b) ? -1 : 1;});// After usort(): $a = array('d', 'e', 'g', 'z'])]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;Ah, so that's all well &amp;amp; good, but the interesting thing is that this anonymous function can also be assigned to a variable (and that's good, because it's filling in for what would normally be a variable):&lt;br /&gt;&lt;br /&gt;&lt;script class="brush: php" type="syntaxhighlighter"&gt;&lt;![CDATA[$fmt = function($datestr) {    $dt = new DateTime($datestr);    return $dt-&gt;format("Y-m-d);};echo $fmt("now") . "\n";// Outputs (something like):// 2010-02-10]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;What may come as a surprise (unless you've read the manual page):&lt;br /&gt;&lt;br /&gt;&lt;script class="brush: php" type="syntaxhighlighter"&gt;&lt;![CDATA[$fmt = function() {};echo gettype($fmt) . "\n"; // "object"echo get_class($fmt) . "\n"; // "Closure"]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;Yes, it's a special internal class.&amp;nbsp; And no, you cannot instantiate a Closure yourself:&lt;br /&gt;&lt;br /&gt;&lt;script class="brush: php" type="syntaxhighlighter"&gt;&lt;![CDATA[$c = new Closure();// PHP Catchable fatal error:  Instantiation of 'Closure' is not allowed in ... ]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;... or extend it (it's &lt;i&gt;final&lt;/i&gt;).&amp;nbsp; But wouldn't it be cool if you could, because then you could maybe find out how they've made a &lt;i&gt;callable&lt;/i&gt; object.&amp;nbsp; AFAIK, PHP doesn't support that otherwise, after all.&amp;nbsp; Now, if they had provided &lt;i&gt;that&lt;/i&gt; feature, I think that would have been a lot more generally useful.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Closures and Scope&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt; &lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt; &lt;/span&gt;&lt;/div&gt;&lt;br /&gt;It is also possible to pass in variables from the current scope in to the anonymous function.&amp;nbsp; This provides a closure-like mechanism.&amp;nbsp; I say "closure-like" because unlike other languages (e.g. Javascript, Python, Ruby) PHP is not actually providing access to the parent scope; rather, it is injecting variables into the anonymous function's scope.&amp;nbsp; While that is different, that probably makes "sense" for PHP, since PHP provide no way to access parent scopes in any other contexts.&lt;br /&gt;&lt;br /&gt;For (a very contrived) example:&lt;br /&gt;&lt;br /&gt;&lt;script class="brush: php" type="syntaxhighlighter"&gt;&lt;![CDATA[$value = "some-value";$func = function($tag) use ($value) {  echo "&lt;$tag&gt;" . $value . "&lt;/$tag&gt;";};echo $func('a');// Outputs:// &lt;a&gt;some-value&lt;/a&gt;]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;But, for people who have experience closure elsewhere, this next example might come as a surprise:&lt;br /&gt;&lt;br /&gt;&lt;script class="brush: php" type="syntaxhighlighter"&gt;&lt;![CDATA[$value = "original-value";$func = function() use ($value) {  $value = "new-value";};$func();echo $value;// Outputs:// original-value]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;So, the function did not change $value.&amp;nbsp; But the syntax does look like we're &lt;i&gt;passing in&lt;/i&gt; the $value, and that seems to be the right way to think about it:&lt;br /&gt;&lt;br /&gt;&lt;script class="brush: php" type="syntaxhighlighter"&gt;&lt;![CDATA[$value = "original-value";$func = function() use (&amp;$value) {  $value = "new-value";};$func();echo $value;// Outputs:// new-value]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;Compare with Javascript:&lt;br /&gt;&lt;br /&gt;&lt;script class="brush: js" type="syntaxhighlighter"&gt;&lt;![CDATA[function greeter(name) {    document.writeln("Old name: " + name);    var changeName = function () {        name = name + "2";    };    changeName();    document.writeln("New name: " + name);}greeter("John");// Outputs:// Old name: John// New name: John2]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;&lt;span style="font-size: large;"&gt;The Fine Print&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;So the actual behavior seems fairly consistent so far.&amp;nbsp; There certainly are a few types of variables that can't be imported.&amp;nbsp; For example, and despite &lt;a href="http://www.ibm.com/developerworks/opensource/library/os-php-5.3new2/index.html"&gt;some suggestion otherwise&lt;/a&gt;, it seems that you cannot reference $this from within an anonymous function defined within a class method:&lt;/div&gt;&lt;br /&gt;&lt;script class="brush: php" type="syntaxhighlighter"&gt;&lt;![CDATA[class Greeter {    public $name = "John";    function greet($greeting) {        $f = function() use ($greeting) {            printf("%s, %s", $greeting, $this-&gt;name);        };        $f();    }}$g = new Greeter();$g-&gt;greet("Hi");// PHP Fatal error:  Using $this when not in object context in ]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;And, no, you cannot pass it in to the closure either:&lt;/div&gt;&lt;br /&gt;&lt;script class="brush: php" type="syntaxhighlighter"&gt;&lt;![CDATA[class Greeter {    public $name = "John";    function greet($greeting) {        $f = function() use ($greeting, $this) {            printf("%s, %s", $greeting, $this-&gt;name);        };        $f();    }}$g = new Greeter();$g-&gt;greet("Hi");// PHP Fatal error:  Cannot use $this as lexical variable in]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;We're getting some interesting errors, though, no?&lt;/div&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;If you're thinking, ah, but what if the closure itself is defined as a member variable.&amp;nbsp; Ok, I think you're getting abusive, but let's give that a shot:&lt;/div&gt;&lt;br /&gt;&lt;script class="brush: php" type="syntaxhighlighter"&gt;&lt;![CDATA[class Greeter {    public $name = "John";    public $greet = null;}$g = new Greeter();$g-&gt;greet = function($greeting) {  printf("%s, %s", $greeting, $this-&gt;name);};$f = $g-&gt;greet;$f("Hi");  // kludgy two-step is necessary// PHP Fatal error:  Using $this when not in object context in ...]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;So, maybe there's a workaround for this, but suffice it to say that it doesn't work the way I would &lt;i&gt;expect&lt;/i&gt; it to work. &lt;/div&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;&lt;span style="font-size: large;"&gt;Another Half-Baked Solution&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;One of my biggest gripes with PHP is how it adds half-baked features to the language.&amp;nbsp; This is true of Exceptions, the OO model, interfaces &amp;amp; type hinting in signatures, namespaces, etc.&amp;nbsp; Well, anonymous functions seem to be simply another example in this litany of offenses to computer science.&amp;nbsp; Now, the concept of first-class functions is a powerful one and leads to some extremely useful design patterns.&amp;nbsp; But PHP didn't actually make functions first-class, they just made a new type of "function" that is first-class (by virtue of being an instance of Closure class).&lt;/div&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;Anonymous functions are suppose to be functions.&amp;nbsp; So why are they fundamentally different from non-anonymous functions?&lt;/div&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;To me, this next block of code is just confusing:&lt;/div&gt;&lt;br /&gt;&lt;script class="brush: php" type="syntaxhighlighter"&gt;&lt;![CDATA[class Greeter {    function greet() { print "Hello OO lovers.\n"; }}$g = new Greeter();$g-&gt;greet = function() { print "Hello monkey-patchers.\n"; };$g-&gt;greet(); // "Hello OO lovers."$f = $g-&gt;greet;$f(); // "Hello monkey-patchers."]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;To be fair, this anonymous function feature is butting up against the basic design of PHP.&amp;nbsp; PHP has special syntax for declaring variables (namely, a $ prefix), which makes the new anonymous functions syntactically incompatible with traditional functions in PHP.&amp;nbsp; In the case of object instance variables, the syntax is ambiguous but one finds out very quickly that PHP is going to err on the side of tradition when it comes to interpreting method invocations:&lt;/div&gt;&lt;br /&gt;&lt;script class="brush: php" type="syntaxhighlighter"&gt;&lt;![CDATA[$g = new Greeter();$g-&gt;greet = function($name) { print "Hello, $name\n"; };$g-&gt;greet();// PHP Fatal error:  Call to undefined method Greeter::greet() in  ...]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;While syntax may just be a function of the completeness of the parser, to the programmer this is what gives a language a feeling of coherence or consistency.&amp;nbsp; We expect computer programming languages to be predictable and logical; for example, when values are equivalent, we expect to be able to substitute one value for another consistently.&lt;/div&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;Look at how consistently Javascript behaves:&lt;/div&gt;&lt;script class="brush: js" type="syntaxhighlighter"&gt;&lt;![CDATA[// Of course, we can assign them to variables (like PHP)var func = function(name) { document.writeln("Hello, " + name); };func("Joe");// since func = function, we can shortcut that:(function(name) { document.writeln("Hello world."); })("Joe");// And of course these are the same as ordinary functions.  And// you can redefine existing functions.function greeter(name) {    document.writeln("Hello, " + name);}document.writeln(typeof(greeter)); // "function"greeter = function(name) {    document.writeln("Hi, " + name);};document.writeln(typeof(greeter)); // "function"// Or add them to objects.var f = {}f.greeter = function() { document.writeln("Hello world."); };f.greeter();]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;Ok, PHP, your turn.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8457759433255973831-1314613684733468759?l=snakesthatbite.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://snakesthatbite.blogspot.com/feeds/1314613684733468759/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://snakesthatbite.blogspot.com/2010/02/hobgoblins-anonymous-functions-in-php.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/1314613684733468759'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/1314613684733468759'/><link rel='alternate' type='text/html' href='http://snakesthatbite.blogspot.com/2010/02/hobgoblins-anonymous-functions-in-php.html' title='Hobgoblins: Anonymous Functions in PHP 5.3'/><author><name>Hans</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://2.bp.blogspot.com/_z-KCnQBKy0M/S2x2JQhK8ZI/AAAAAAAAAAM/7wTLbAwGoX0/S220/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8457759433255973831.post-7932537283257034897</id><published>2010-02-05T16:44:00.000-08:00</published><updated>2010-02-06T05:18:33.187-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>HipHop: Stop What You're Doing</title><content type='html'>So the PHP proletariat is foaming at the mouth with &lt;a href="http://developers.facebook.com/news.php?blog=1&amp;amp;story=358"&gt;Facebook's announcement of&amp;nbsp; HipHop&lt;/a&gt;.&amp;nbsp; Now, there are certainly some great discussions of the topic -- take &lt;a href="http://terrychay.com/article/hiphop-for-faster-php.shtml"&gt;Terry  Chay's multi-page inside look&lt;/a&gt; or, for a Python perspective, take a look at &lt;a href="http://alexgaynor.net/2010/feb/02/thoughts-hiphop-php/"&gt;Alex Gaynor's observations&lt;/a&gt;. These guys are pretty qualified to comment.&amp;nbsp; But there's a lot of crazy talk out there.&amp;nbsp; Crazy talk?&amp;nbsp; Well, damn, I wanna talk crazy too!&lt;br /&gt;&lt;br /&gt;Am I qualified to have an opinion about this?&amp;nbsp; Well, I certainly haven't used it yet or looked at the source (both related to the fact that the source is still not posted to github); I certainly haven't worked at Facebook and don't personally know anyone that does; it is completely irrelevant to me (as it is to most PHP developers).&amp;nbsp; ... So, I think I've got what it takes to voice an opinion that is unencumbered by fact or first-hand experience. &lt;br /&gt;&lt;br /&gt;I felt so qualified on the topic that I joined in loudly on &lt;a href="http://groups.google.com/group/washington-dcphp-group/browse_thread/thread/b41c85e76653aae0?hl=en"&gt;a discussion in the local DC PHP group&lt;/a&gt;.&amp;nbsp; My comments elicited quite a response on the list.&amp;nbsp; I'm sure my poorly masked distaste for PHP doesn't exactly endear me to the group.&amp;nbsp; (&lt;i&gt;Why, you may ask, am I on the PHP list in the first place?&amp;nbsp; Well, I would say "for the ladies", but I'm worried my wife will find this blog!&lt;/i&gt;)&amp;nbsp; My comments were apparently so poignant that they were &lt;a href="http://technosailor.com/2010/02/04/hiphop-php-and-the-evolution-of-language/comment-page-1/#comment-262393"&gt;selectively quoted in a &lt;i&gt;real life&lt;/i&gt; DC-area blog&lt;/a&gt;.&amp;nbsp; Not just quoted, but actually described as "puritanical" and "naive" (in the same sentence), which I consider the high-water mark of my inter-personal communications career.&amp;nbsp;&amp;nbsp; Not to pick on Aaron (well, maybe a little, but that certainly seems fair), but this post has some unfortunate bits that have been a little too characteristic of the PHP community's hip-hopped-up responses: &lt;br /&gt;&lt;blockquote&gt;Personally, I wish we had HipHop when [...] &amp;nbsp;We had a ton of scaling problems  with PHP and we were running fully clustered Apache servers (25 deep, if  I recall), sharded MySQL across 6ish database servers, and we had  massive I/O bottlenecks. &lt;/blockquote&gt;Ok, look, now I haven't used HipHop either, but if we're to believe &lt;a href="http://developers.facebook.com/news.php?blog=1&amp;amp;story=358"&gt;the author&lt;/a&gt;, then HipHop reduces &lt;i&gt;CPU load&lt;/i&gt;&lt;i&gt;.&lt;/i&gt;&amp;nbsp; This, by definition, isn't relevant to I/O problems.&amp;nbsp; I know; it's hard not to get swept away with the excitement.&amp;nbsp; This is, after all, &lt;i&gt;the evolution of language.&lt;/i&gt;&amp;nbsp; I am a little disappointed to have been the spark for that post.&amp;nbsp; Now, I'm sure that this was an honest mistake and that certainly if Aaron was managing clusters of 30+ servers, he knows that I/O bottlenecks are not the same as CPU bottlenecks.&amp;nbsp; But it does tarnish the argument a little. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;My favorite (so far) is the article on Sitepoint, which I &lt;i&gt;thought&lt;/i&gt; had presumptions of being a trusted source of knowledge in the PHP world.&amp;nbsp; Check this out: &lt;a href="http://www.sitepoint.com/blogs/2010/02/04/boost-php-performance-with-hiphop/"&gt;Boost your PHP Performance 50% with HipHop&lt;/a&gt;.&amp;nbsp; That's right, not only is this article also oblivious to what makes PHP apps slow, but the author appears to be tripping up on some basic consumer mathematics.&amp;nbsp; Yes, HipHop on Facebook showed a &lt;i&gt;reduction&lt;/i&gt; of CPU usage by 50%.&amp;nbsp; That means it used &lt;i&gt;half&lt;/i&gt; as much CPU ... so those scripts perform &lt;i&gt;twice&lt;/i&gt; as fast (w.r.t. CPU).&amp;nbsp; Now, bear with me.&amp;nbsp; A &lt;i&gt;50% boost&lt;/i&gt; means 1.5 times as fast.&amp;nbsp; 2 != 1.5.&amp;nbsp; Well, since this is PHP there's always that danger that unequal things will evaluate as equal, so let me be more precise: 2 !== 1.5. &lt;br /&gt;&lt;br /&gt;Ok, but before I went off on all this &lt;i&gt;ad hominem&lt;/i&gt; venting.&amp;nbsp; I actually had a more constructive point I wanted to make.&amp;nbsp; It took a few forms (some more enraging than others), but the gist was this: &lt;b&gt;reconsider&lt;/b&gt;.&amp;nbsp; If you need to compile PHP into C++ and run it on a custom-designed server platform because you've maxed out your CPU, maybe PHP wasn't the right choice.&amp;nbsp; I'm just sayin'.&amp;nbsp;&amp;nbsp; &lt;br /&gt;&lt;br /&gt;I'm not saying that changing platforms is easy.&amp;nbsp; I've been involved in migrations to Python (from PHP) for the past couple years and can certainly attest that it's hard and sometimes it takes time, but sometimes it's the right thing to do.&amp;nbsp; We sat down and looked at some of the things that we were doing and realized that we were jumping through some crazy hoops to work around deficiencies in language &amp;amp; platform.&amp;nbsp; After reviewing our options, we decided that Python offered the quality, community, broad  applicability, extensibility, and scalability that suited our needs.&amp;nbsp; A big part of this decision was the scaling path.&amp;nbsp; We knew there were proven ways to extend Python with Java (via&amp;nbsp;&lt;a href="http://jython.org/"&gt;Jython&lt;/a&gt;) or C/C++. &lt;br /&gt;&lt;br /&gt;There's was a lot of suggestion &lt;a href="http://groups.google.com/group/washington-dcphp-group/browse_thread/thread/b41c85e76653aae0?hl=en"&gt;in our discussion&lt;/a&gt; that it didn't make sense for Facebook to ever reconsider PHP.&amp;nbsp; But as &lt;a href="http://terrychay.com/article/hiphop-for-faster-php.shtml/4"&gt;Terry Chay noted&lt;/a&gt;, there were several efforts to move Facebook to Python and to C++; they just never had the resources allocated to actually accomplish it.&amp;nbsp; And looking at the array of technologies Facebook employs, it's pretty obvious that they understand the basics of comparative advantage.&lt;br /&gt;&lt;br /&gt;I am not a curator of startup history, but it does seem that there are plenty of cases to be found where applications can evolve to use new technologies successfully.&amp;nbsp; Twitter looks like an example where this worked quite successfully.&amp;nbsp; They didn't have to throw all their Rails code away, but they did start rewriting pieces in Scala (on the JVM).&amp;nbsp; The &lt;a href="http://www.artima.com/scalazine/articles/twitter_on_scala.html"&gt;interview on Artima&lt;/a&gt; is a good read.&amp;nbsp; And Google recently was rumoured to be encouraging new development to use Java instead of Python.&amp;nbsp; I know I read about it in &lt;a href="http://groups.google.com/group/unladen-swallow/browse_thread/thread/4edbc406f544643e/fdfb549767617fe0?lnk=gst"&gt;an Unladen Swallow thread&lt;/a&gt;, and while I obviously like Python, I think this is a mature stance.&amp;nbsp; Choose the right tool for the job.&lt;br /&gt;&lt;br /&gt;So, at the end of the day, I'm just kinda baffled by all this HipHop hubbub.&amp;nbsp; Not only has it been done before, but for almost every application and for all new development it just seems completely irrelevant.&amp;nbsp; Why would you choose to write something in PHP?&amp;nbsp; Not for the language, right?&amp;nbsp; Not for the wealth of libraries.&amp;nbsp; Not for the culture of quality.&amp;nbsp; Not for the extensibility.&amp;nbsp; No, you'd use it because it's drop-dead easy to deploy and the PHP+Apache(+MySQL) platform is ubiquitous.&amp;nbsp; So, HipHop renders inapplicable the only reasons I would choose PHP.&lt;br /&gt;&lt;br /&gt;Oh, but I do like the name.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8457759433255973831-7932537283257034897?l=snakesthatbite.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://snakesthatbite.blogspot.com/feeds/7932537283257034897/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://snakesthatbite.blogspot.com/2010/02/hiphop-stop-what-youre-doing.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/7932537283257034897'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/7932537283257034897'/><link rel='alternate' type='text/html' href='http://snakesthatbite.blogspot.com/2010/02/hiphop-stop-what-youre-doing.html' title='HipHop: Stop What You&apos;re Doing'/><author><name>Hans</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://2.bp.blogspot.com/_z-KCnQBKy0M/S2x2JQhK8ZI/AAAAAAAAAAM/7wTLbAwGoX0/S220/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8457759433255973831.post-6367458258725692942</id><published>2010-02-05T11:40:00.000-08:00</published><updated>2010-02-14T17:30:20.895-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='dreamhost'/><category scheme='http://www.blogger.com/atom/ns#' term='zine'/><title type='text'>Installing Zine on Dreamhost</title><content type='html'>&lt;i&gt;Yes, until recently I was running my blog on Zine (on dreamhost).&amp;nbsp; While it was a good exercise to get it working -- and it certainly was working fine as a hosting platform, I had made up my mind that I'd like to use hosted services more.&amp;nbsp; So, even though I'm not actually hosting this on Zine, I still think this is useful information.&amp;nbsp; And so I'm reposting it.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;I've just installed &lt;a href="http://zine.pocoo.org/"&gt;Zine&lt;/a&gt; on my &lt;a href="http://dreamhost.com/"&gt;Dreamhost&lt;/a&gt; shared host account.  In a fit of existentialism, I decided to write my first blog post on how to install Zine on Dreamhost.  Granted, this &lt;i&gt;is&lt;/i&gt; pretty straightforward, and if you're willing to surf around a bit, you probably will have figured these things out yourself.  But I'm all for encouraging more Zine usage (since that might give me more theme options), so I figured I'd help make this easier.&lt;br /&gt;&lt;br /&gt;You may be wondering, &lt;i&gt;"Why didn't you use Passenger?"&lt;/i&gt;. Good question! &lt;a href="http://wiki.dreamhost.com/Passenger_WSGI"&gt;Dreamhost does support Passenger for WSGI applications&lt;/a&gt;, but this uses the default system python interpreter (python 2.4, with none of the needed deps).  I wanted to run this in a virtual environment and not be forced to use a kludgy &lt;i&gt;os.exec()&lt;/i&gt; hack to use my own interpreter.  And FastCGI seems to work fine and doesn't carry the "beta!" warning.&lt;br /&gt;&lt;br /&gt;Here were the key ingredients for this recipe.&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Use Python 2.5 (not default)&lt;/li&gt;&lt;li&gt;Use Virtualenv&lt;/li&gt;&lt;li&gt;Use FastCGI (and Flup)&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;Dreamhost Setup&lt;/h1&gt;&lt;ol&gt;&lt;li&gt;Setup the (sub)domain for your blog.  Or create a new sub-directory on existing site.  This will be your "blog directory" (you'll need to tell Zine where this later).&lt;/li&gt;&lt;li&gt;Setup a MySQL database for your blog.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h1&gt;Zine Install&lt;/h1&gt;&lt;br /&gt;&lt;h2&gt;Step 1: Libxml2 Prereqs&lt;/h2&gt;You will need to install the &lt;tt&gt;lxml&lt;/tt&gt; python module, which is going to want development libs of libxml2.  On Dreamhost you'll need to build your own libxml and libxslt libraries to make this all possible.  This isn't as painful as it ounds. I used &lt;a href="http://gsnedders.com/installing-lxml-on-dreamhost"&gt;this blogpost&lt;/a&gt; as the basis for my approach.  I changed those steps to use &lt;tt&gt;${HOME}/usr&lt;/tt&gt; for the &lt;tt&gt;--prefix&lt;/tt&gt; flags (rather than the &lt;tt&gt;~/.local&lt;/tt&gt; in that article); I added &lt;tt&gt;~/usr/bin&lt;/tt&gt; to my PATH variable so that Python will find the &lt;tt&gt;xslt-config&lt;/tt&gt; when it goes to install &lt;tt&gt;lxml&lt;/tt&gt;.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Step 2: Setup Virtualenv&lt;/h2&gt;I chose to use &lt;a href="http://pypi.python.org/pypi/virtualenv"&gt;virtualenv&lt;/a&gt; for this exercise, since 1) I'm on a shared host and 2) I typically use virtualenv anyway to isolate environments for specific applications.  It makes life easier; give it a try if you haven't yet.&lt;br /&gt;&lt;br /&gt;&lt;script class="brush: bash" type="syntaxhighlighter"&gt;&lt;![CDATA[shell$ wget http://pypi.python.org/packages/source/v/virtualenv/virtualenv-1.4.3.tar.gzshell$ tar zxvf virtualenv-1.4.3.tar.gz ]]&gt;&lt;/script&gt;&lt;br /&gt;Now create the virtual environment somewhere.  Name appropriately (we'll need the name again later).&lt;br /&gt;&lt;script class="brush: bash" type="syntaxhighlighter"&gt;&lt;![CDATA[shell$ python2.5 virtualenv-1.4.3/virtualenv.py --no-site-packages ~/myblog-env]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;b&gt;Note:&lt;/b&gt; I'm using Python 2.5, which is &lt;i&gt;not&lt;/i&gt; the default on Dreamhost (at time of writing).  I found that Python 2.4 did not work.  (I &lt;a href="http://dev.pocoo.org/projects/zine/ticket/232"&gt;created a ticket&lt;/a&gt;.)&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Activate Virtualenv&lt;/h3&gt;&lt;br /&gt;&lt;script class="brush: bash" type="syntaxhighlighter"&gt;&lt;![CDATA[shell$ source ~/myblog-env/bin/activate(myblog-env) shell$]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Step 3: Setup Zine&lt;/h2&gt;Note that all of these steps assume that you are operating with an activated virtualenv.&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Download and unpack:&lt;br /&gt;&lt;script class="brush: bash" type="syntaxhighlighter"&gt;&lt;![CDATA[(myblog-env) shell$ wget http://zine.pocoo.org/releases/Zine-0.1.2.tar.gz(myblog-env) shell$ tar zxvf Zine-0.1.2.tar.gz]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Install, using our Python virtual env:&lt;br /&gt;&lt;script class="brush: bash" type="syntaxhighlighter"&gt;&lt;![CDATA[(myblog-env) shell$ cd Zine-0.1.2(myblog-env) shell$ ./configure --prefix=${HOME}/myblog-env &amp;amp;&amp;amp; make install]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Install Zine prereqs:&lt;br /&gt;&lt;script class="brush: bash" type="syntaxhighlighter"&gt;&lt;![CDATA[(myblog-env) shell$ easy_install Werkzeug Jinja2 MySQL-python SQLAlchemy simplejson pytz Babel lxml html5lib]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Install Flup:&lt;br /&gt;&lt;script class="brush: bash" type="syntaxhighlighter"&gt;&lt;![CDATA[(myblog-env) shell$ easy_install flup]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h2&gt;Step 4: Setup FastCGI handler&lt;/h2&gt;&lt;br /&gt;&lt;h3&gt;Zine Handler&lt;/h3&gt;&lt;br /&gt;Copy the installed &lt;tt&gt;zine.fcgi&lt;/tt&gt; handler into your blog web root (adjust paths accordingly):&lt;br /&gt;&lt;br /&gt;&lt;script class="brush: bash" type="syntaxhighlighter"&gt;&lt;![CDATA[shell$ ~/blog.yoursite.comshell$ cp ~/myblog-env/share/zine/servers/zine.fcgi .shell$ chmod +x zine.fcgi]]&gt;&lt;/script&gt;&lt;br /&gt;&lt;br /&gt;Edit the &lt;tt&gt;~/myblog-env/share/zine/servers/zine.fcgi&lt;/tt&gt; file to ensure that the INSTANCE_FOLDER points to your blog public dir (~/blog.yoursite.com, e.g.).&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;.htaccess&lt;/h3&gt;Create an &lt;tt&gt;.htaccess&lt;/tt&gt; file in your blog public directory with these contents:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;RewriteEngine On&lt;br /&gt;RewriteBase /&lt;br /&gt;RewriteRule ^zine\.fcgi/ - [L]&lt;br /&gt;RewriteRule ^(.*)$ zine.fcgi/$1 [L]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h1&gt;Final Steps&lt;/h1&gt;&lt;br /&gt;Visit your Zine URL in your browser to start the installation wizard.&lt;br /&gt;&lt;br /&gt;When you are finished, you may wish to edit &lt;tt&gt;~/blog.yoursite.com/zine.ini&lt;/tt&gt;, to change the Zine URL to not include the &lt;tt&gt;zine.fcgi&lt;/tt&gt; component of the URL (it isn't necessary, given the rewrite rules defined).&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;References&lt;/h1&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://wiki.dreamhost.com/Python"&gt;Dreamhost: Python&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://wiki.dreamhost.com/Python_FastCGI"&gt;Dreamhost: Python FastCGI&lt;/a&gt;&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/8457759433255973831-6367458258725692942?l=snakesthatbite.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://snakesthatbite.blogspot.com/feeds/6367458258725692942/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://snakesthatbite.blogspot.com/2010/02/installing-zine-on-dreamhost.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/6367458258725692942'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8457759433255973831/posts/default/6367458258725692942'/><link rel='alternate' type='text/html' href='http://snakesthatbite.blogspot.com/2010/02/installing-zine-on-dreamhost.html' title='Installing Zine on Dreamhost'/><author><name>Hans</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='28' height='32' src='http://2.bp.blogspot.com/_z-KCnQBKy0M/S2x2JQhK8ZI/AAAAAAAAAAM/7wTLbAwGoX0/S220/photo.jpg'/></author><thr:total>0</thr:total></entry></feed>
