<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
  <title type="text">A. Jesse Jiryu Davis</title>
  <id>http://emptysquare.net/blog/feed/</id>
  <updated>2013-05-23T02:49:38Z</updated>
  <link href="http://emptysquare.net/blog/" />
  
  <author>
    <name>A. Jesse Jiryu Davis</name>
    <email>ajdavis@cs.oberlin.edu</email>
  </author>
  <icon>http://emptysquare.net/blog/theme/static/%2Ftheme%2Fstatic%2Fsquare96.png</icon>
  <generator uri="https://github.com/ajdavis/motor-blog" version="0.1">Motor-Blog</generator>
  <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/emptysquare" /><feedburner:info uri="emptysquare" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">Street Retreat Recap</title>
    <id>http://emptysquare.net/blog/street-retreat-recap/</id>
    <updated>2013-05-23T02:48:54Z</updated>
    <published>2013-05-23T02:48:54Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/V4HFNzlkrN0/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;A couple weeks ago I went on my third Zen street retreat, led by Roshi Grover Genro Gauntt. Here's my notes on what happened.&lt;/p&gt;
&lt;h1&gt;Thursday&lt;/h1&gt;
&lt;p&gt;We met in Washington Square Park in the afternoon to start the retreat. We had no wallets, cellphones, toothbrushes, books. There were eleven of us. We sat in a circle and meditated. A little jazz combo was playing nearby. We introduced ourselves to each other. Most were on their first retreat, except for Genro, Eileen, me, and Batman. That's his nickname because he was an extra in a Batman movie once. He's a garbage man. He's a long-time student of Bernie Glassman and he's been homeless thirty years. He just got into an apartment this year.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Batman" border="0" src="http://emptysquare.net/blog/media/2013/05/batman-1.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="batman-1.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;Dinner at the Bowery Mission. We sat through a service first. Indifferent and nearly inaudible Christian guitar rock. We were supposed to sing along, but the lyrics were on a little screen at the front that no one could read, even if we cared to. Much of the congregation was asleep or reading newspapers.&lt;/p&gt;
&lt;p&gt;They served beef stroganoff, steamed spinach, nectarines. Fag&amp;#233; yogurt containers were stacked on a table. I stuck with the spinach and a nectarine.&lt;/p&gt;
&lt;p&gt;Outside were demolition dumpsters, related to a new New Museum expansion. They were filled with carpet remnants, cut into manageable sizes and conveniently rolled up. All eleven of us chose a roll each and attached them to our bags if possible, or carried them with us. We'd keep them for the rest of the retreat.&lt;/p&gt;
&lt;p&gt;We walked to Masjid Farah, the sufi mosque on West Broadway. In past retreats we've wandered looking for it&amp;#8212;no cellphones, after all&amp;#8212;but Batman knows where everything is and he led us straight there. "Do you want to go the scenic way or the short way?" he asked each time I made a wrong turn.&lt;/p&gt;
&lt;p&gt;The streets in the West Village were set up for a big film shoot. Ari and I noticed that we passed right by Fred Armisen standing on a corner talking to a member of the film crew.&lt;/p&gt;
&lt;p&gt;The zikr ceremony at the mosque is always a trip. They sing and shout and chant Allah's name until it sounds like gasping. Unlike in past years, we stayed only a couple hours. When everyone got up to dance, we left.&lt;/p&gt;
&lt;p&gt;Our plan was to find a place in Battery Park to sleep. We walked with our carpets down to the park. Along the Canyon of Heroes, Jonathan read the name and date of every celebrity ever given a ticker-tape parade in NYC.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Walking" border="0" src="http://emptysquare.net/blog/media/2013/05/walking.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="walking.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;We found the park almost entirely fenced off for construction. Weirdly, there was a wide opening in the fence, from which led a fenced corridor to a round, fenced area in the middle of the construction zone. We debated a little: was this illegal? Would we be rousted? Signs on the fence warned of rats and rat poison: were they any concern to us? We bedded down and slept.&lt;/p&gt;
&lt;p&gt;Or tried to. I was too cold. The wind was up for much of the night and we were unsheltered. Mike had a T-shirt and his carpet and froze all night. Batman had come with a huge backpack and produced from it a yoga mat and a fancy sleeping bag; he slept well. I had my usual leather jacket and hoodie, and I'd packed a new wool army blanket that I bought for the retreat. I got to sleep a few hours despite the cold. The worst for me was that my blanket shed wool-lint into my eyes, while the carpet from the demolition debris puffed plaster dust up my nose every time I shifted. I was glad to finally hear the birds start to chirp, and to pull the blanket from my face and see the sky brightening.&lt;/p&gt;
&lt;p&gt;(Note: Now that I've washed this blanket once, it's not only lint-free but several times softer. Lesson learned.)&lt;/p&gt;
&lt;h1&gt;Friday&lt;/h1&gt;
&lt;p&gt;We walked up to McAuley's (New York Rescue Mission) at 6 or so in the morning. Breakfast was a revolting little stack of ham slices and a few pancakes, I think, glued with corn syrup to the bottom of a styrofoam bowl. Strong coffee, though.&lt;/p&gt;
&lt;p&gt;We walked to Washington Square Park. Along the way, Batman saw a food cart vendor he knew. He banged on the window and talked to the man a moment, then announced to us: "Anybody want coffee? Tell him how you like it! Step up and get some!" Batman is old friends with everyone.&lt;/p&gt;
&lt;p&gt;In Washington Square Park, some sections were soaked, or were being soaked with big sprinklers. We found a dry spot and bedded down for a nap. Still cold, even with the sun up. A parks person came up in a buggy and called out, "Parks! What's going on here?"&lt;/p&gt;
&lt;p&gt;Genro: "We're resting."&lt;/p&gt;
&lt;p&gt;Parks: "How long are you going to be here?"&lt;/p&gt;
&lt;p&gt;Genro: "Maybe an hour."&lt;/p&gt;
&lt;p&gt;Parks: "Well okay, we can hold off watering this section for a bit. But just to let you know: water is coming."&lt;/p&gt;
&lt;p&gt;Surprisingly accommodating.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Roshi Genro, Washington Square Park" border="0" src="http://emptysquare.net/blog/media/2013/05/genro-washington-square.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="genro-washington-square.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Washington Square Park" border="0" src="http://emptysquare.net/blog/media/2013/05/washington-square-park.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="washington-square-park.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Sam, Washington Square Park" border="0" src="http://emptysquare.net/blog/media/2013/05/sam-washington-square-park.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="sam-washington-square-park.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;Brunch at The Catholic Worker in the East Village, the usual homemade bread and vegetable soup, and good coffee. This is always my favorite meal on retreat. A bottle of hot sauce was on each table: actual flavor!&lt;/p&gt;
&lt;p&gt;We walked up to Tompkins Square Park to nap, do meditation and service, and council.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Roshi Genro, Tompkins Square Park" border="0" src="http://emptysquare.net/blog/media/2013/05/genro-tompkins-square-park.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="genro-tompkins-square-park.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Batman" border="0" src="http://emptysquare.net/blog/media/2013/05/batman-2.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="batman-2.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;Dinner at the Bowery Mission again. Friday nights they have a pickup jazz band; I've heard it once or twice before, but Friday night they were great. Some big-band flair and wild solos, playing their hearts out, shaking the walls. The drummer, Uros Markovic, has led this band for years. He alternates jazz pieces with bits of preaching. The rest of the band was a sax, a bass trombone, piano, and a woman on the tambourine. I forget her name, but we'd spent time with her on the last retreat, and Batman knows her very well. She seems to have cleaned up, calmed down, put on weight, and become a much better musician since the last time we saw her.&lt;/p&gt;
&lt;p&gt;Markovic gave a sermon in which he criticized TV programs that purport to tell us about the real Jesus, that show him as an ordinary man. Jesus wasn't an ordinary man, after all, he was special. We know because the Bible tells us. Muslims get Jesus wrong, too: they don't believe he rose from the dead after three days. Those who know Jesus will live forever in Heaven. Everyone else goes to Hell.&lt;/p&gt;
&lt;p&gt;The band did a jazz version of "Nothing but the Blood" by Robert Lowry, which is incredibly weird:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Glory! Glory! This I sing&amp;#8212;&lt;/em&gt;&lt;br/&gt;
&lt;em&gt;Nothing but the blood of Jesus,&lt;/em&gt;&lt;br/&gt;
&lt;em&gt;All my praise for this I bring&amp;#8212;&lt;/em&gt;&lt;br/&gt;
&lt;em&gt;Nothing but the blood of Jesus.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Dinner was pasta, and some slices of meat that the servers were calling chicken but looked like ham. As is my habit on street retreat, I refused the non-vegetables, so I just had salad. "You want a double portion then? You like avocado? Here, let me put some dressing on. Is this enough? Tell me when! You want some grated cheese?" I love this kind of bustling generosity you find sometimes.&lt;/p&gt;
&lt;p&gt;We stood outside the Mission, and a fight broke out between two men, a small black guy and one about my size. The crowd stood back to make room for them. A woman was yelling, egging them on as they assumed boxers' stances and shifted around the sidewalk. Across the street, some men at a bus stop cheered. I thought of intervening but doubted I was the right person for the job. The smaller man threw the first punch, knocked the other down, and raised his foot to stomp on his opponent's head. The man on the ground deflected the stomp with his hands. Batman and a few others rushed in to end the fight. Batman grabbed the winner and pushed him back, arguing with him to leave it be, it's over. He and Batman have been friends for years.&lt;/p&gt;
&lt;p&gt;Where should we sleep? Returning to Battery Park would be a long walk in exchange for an uncomfortable night. The wind was up again, and the temperature down.&lt;/p&gt;
&lt;p&gt;Batman had two recommendations. The first, an entryway to the Hebrew Union College at West 4th and Mercer. The police treat it like holy ground, Batman said, a refuge: if we stayed just off the sidewalk, in front of the door to the college, we'd be allowed to sleep in plain sight. The alternative was a park in an apartment complex a few more blocks to the south. We found there a big bench-like thing in the middle of the park, a spiral of concrete. It was perfect for 11 people. We walked into the spiral and put down our bedding, cozying up to the shelter of the bench. Residents were passing, walking their dogs, talking on their phones, glancing at us. I wondered what they thought, and if one of them would call the cops on us. And yet, we were out of the wind, and I fell asleep quickly.&lt;/p&gt;
&lt;p&gt;Perhaps half an hour later, I heard someone slapping on the concrete. "Hello. Security." I pulled my blanket from my face. A pretty young black woman in a blue nylon jacket was standing outside the spiral. She said that she'd been called with complaints from residents about people sleeping in the park and we needed to move out. If we didn't, a lot more security people would come and it would be a big mess. She talked gently. "I need to stand here until you go." I asked if she knew anywhere else we could sleep. "I'm sorry, I'm not from around here." &lt;/p&gt;
&lt;p&gt;We rolled up our carpets and walked a couple blocks to the Hebrew Union College entryway. It's a triangle, a few yards on a side, off the Mercer Street sidewalk. It was enough room for all of us if we slept side by side. Nearby was a small and very warm heating vent, continuously blowing out enough air to warm a half-dozen people standing around it. Batman says that vent is the tribal campfire. He has years of stories of things that have happened around that vent. We warmed up at the vent, then bedded down for the night. It was windier than in the spiral bench, and noisier and brighter, but Batman was right: no one bothered us all night long.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Hebrew Union College entryway" border="0" src="http://emptysquare.net/blog/media/2013/05/hebrew-union-college.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="hebrew-union-college.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Batman rolling up his yoga mat" border="0" src="http://emptysquare.net/blog/media/2013/05/batman-yoga-mat.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="batman-yoga-mat.jpg"/&gt;&lt;/p&gt;
&lt;h1&gt;Saturday&lt;/h1&gt;
&lt;p&gt;We got to the University Community Soup Kitchen, a.k.a. Diane's, a.k.a. the Meatloaf Kitchen, for breakfast at 10am. We sat in rows of plastic chairs and ate open-faced peanut butter and jelly sandwiches on thick slices of white bread, with milky sweet coffee in styrofoam cups. (Homeless services must be the last great consumer of styrofoam in America.) Then we sat on the chairs and waited: lunch wasn't served until 1pm. The regulars who knew each other passed the time talking. Two young white men had a conversation about travel that lasted for hours. Where do you eat in S&amp;#227;o Paulo? Where do you stay in Bangkok? Once you've bought a ticket to France, how far can you stretch the money you have left once you get there?&lt;/p&gt;
&lt;p&gt;An hour into the wait, someone dropped a stack of New York Posts and a couple copies of the New York Times on the table at the front and there was a rush for them. As people finished the papers, they swapped. Eileen talked to someone who'd gotten a Times, and negotiated twenty minutes to read it. We learned Israel had made an airstrike in Syria. Genro and I talked the situation over with a beautiful, chic young black man sitting next to us. As the conversation moved on he told us about the Bowery Residents Committee shelter, on 25th Street. Apparently it's a nice modern-looking place with relaxed rules. He stayed there a few months before "losing his bed," whatever that means. He told a story about sharing a room with a heroin addict and a giant ex-con. The addict came home confused one night. He leaned slowly over the sleeping ex-con, farther and farther, and pissed on him. The ex-con woke up, felt the piss on his face, and beat down the addict. Good times.&lt;/p&gt;
&lt;p&gt;A guy I recognized from my last retreat was still there, a big black man with curly hair and a beard. He'd be played by Forrest Whitaker. Last time, he made the lunch wait hard by hunching in his chair and screaming rhythmically all morning. "Give it a rest!" people shouted at him. This time his tic was kind of fun: he'd periodically walk up to someone, touch his throat and sing a few jazzy syllables, nod gravely, and walk away. It was a way to say "hello" to the newcomers: over the course of the morning he seemed particularly to connect with members of our group this way.&lt;/p&gt;
&lt;p&gt;Around 1pm the famous meal was served: meatloaf, meatballs in tomato sauce, salad, rolls, coffee. We had real table service. Volunteers walked around refilling our coffee and offering seconds. I make no claim to connoisseurship, but the meatloaf was pretty good.&lt;/p&gt;
&lt;p&gt;I was tired of being cold at night, and regretting that I hadn't asked for a coat at the Mission the day before. I asked a volunteer if there was a coat I could have. She was a white woman named Fatima, dressed in an abaya, with a headscarf that covered her piled dreadlocks. On the back of the abaya she had sewn a black Occupy Wall Street patch that covered the width of her back. Her hood left visible a few inches of forehead above her eyes, which was tattooed with green lines in a North African style. She asked whether I wanted a blanket or a coat. What kind of coat would I prefer? I just said, "Whatever's warmest."&lt;/p&gt;
&lt;p&gt;Fatima was in the donations room for ten minutes trying to find me the perfect coat. She came out with a green nylon pullover. "Look," she said, "this has a fleece lining, and deep pockets you can put stuff in." She held up the coat and turned it so I could see the lining and the pockets. "You can wear it zipped, or unzipped, and you can tie it around your waist during the day." She demonstrated tying it around her waist. "This should keep you warm tonight." She held up her hand for a high-five.  &lt;/p&gt;
&lt;p&gt;She gave me the schedule for some homeless advocacy group and insisted I show up. She seemed very concerned about me, and perhaps also she saw in me a possible advocate for the homeless. I was uncomfortable. This had gone on long past the point where I should have explained about the retreat, but it seemed too late to suddenly come clean. This was the only time on the retreat that I misjudged the balance between being honest and blending in.&lt;/p&gt;
&lt;p&gt;I rejoined the group with my fancy pullover and we walked up to Tompkins Square Park for naps and council.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Ari, Tompkins Square Park" border="0" src="http://emptysquare.net/blog/media/2013/05/ari-tompkins-square-park.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="ari-tompkins-square-park.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Roshi Genro, Tompkins Square Park" border="0" src="http://emptysquare.net/blog/media/2013/05/genro-sleeping-tompkins-square-park.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="genro-sleeping-tompkins-square-park.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Dylan, Tompkins Square Park" border="0" src="http://emptysquare.net/blog/media/2013/05/dylan-tompkins-square-park.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="dylan-tompkins-square-park.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Batman, Tompkins Square Park" border="0" src="http://emptysquare.net/blog/media/2013/05/batman-tompkins-square-park.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="batman-tompkins-square-park.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;Dinner at the Bowery Mission again: this time no music or preaching, and the kitchen moved sluggishly. We are professionals at waiting by now. We sat in the pews for an hour talking to the other guests or listening to their conversations. A man behind me asked his friend to take a look at his eye. "Oh, you definitely have pinkeye. Don't do that! That. Don't rub it, you'll make it spread."&lt;/p&gt;
&lt;p&gt;Their conversation moved on to the death of a rapper from Kris Kross. "He was doing coke and heroin together. What do they call that?" "Speedball." "Do you snort it or shoot it?" "Either way, man. Shooting it's dangerous, though."&lt;/p&gt;
&lt;p&gt;I forget what was the main course for dinner. I had sickly-sweet iced tea, salad, and an apricot.&lt;/p&gt;
&lt;p&gt;We slept in the entryway to Hebrew Union College again. This final night was the coldest yet. I was much better equipped than at the start of the retreat, with my new fleece from Fatima, on top of my leather jacket, and three layers beneath that, but I was still shivering too hard to sleep more than a few hours. But even so: I'd lost any fear of being cold. I was prepared to lie down and shiver until the morning, so that's what I did.&lt;/p&gt;
&lt;h1&gt;Sunday&lt;/h1&gt;
&lt;p&gt;I woke up from the cold. I'd learned how the bird songs mark the stages of dawn. When I heard the kind of bird that starts calling around 6, I stopped trying to sleep and got up. There was a party going on around the heating vent: Others had been up since 4. Batman had coffee for us from one of his contacts somewhere&amp;#8212;he was acting mysterious about where it came from.&lt;/p&gt;
&lt;p&gt;The others had received a box with more cups of coffee, plus cupcakes and a whole cucumber. Some street fair had ended in the late night or early morning, and the organizers had gone around in a van, looking to give their leftovers to some homeless people. Who they found was us.&lt;/p&gt;
&lt;p&gt;We stood around the vent talking until 10, drinking coffee, smoking butts we found on the street. What a lovely way to spend a morning.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Mercer Street" border="0" src="http://emptysquare.net/blog/media/2013/05/mercer-street.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="mercer-street.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Kristi and Marco" border="0" src="http://emptysquare.net/blog/media/2013/05/kristi-marco.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="kristi-marco.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Sam" border="0" src="http://emptysquare.net/blog/media/2013/05/sam.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="sam.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Dylan and Genro" border="0" src="http://emptysquare.net/blog/media/2013/05/dylan-and-genro.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="dylan-and-genro.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Eileen" border="0" src="http://emptysquare.net/blog/media/2013/05/eileen.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="eileen.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;We ditched our carpets, with gratitude for their service to us, and gratitude that we needn't lug them any farther. We skipped breakfast and had a long and emotional closing council in Washington Square Park.&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=2070044556&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/street-retreat-recap/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D1463145050.1381605228.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D1463145050%3B%2B__utmc%3D1463145050%3B%2B__utmz%3D1463145050.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D1463145050.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/V4HFNzlkrN0" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/street-retreat-recap/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">Wasp's Nest: The Read-Copy-Update Pattern In Python</title>
    <id>http://emptysquare.net/blog/wasps-nest-read-copy-update-python/</id>
    <updated>2013-05-09T02:47:45Z</updated>
    <published>2013-05-09T02:47:45Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/-hgi2neQCfg/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;In recent work on PyMongo, I used a concurrency-control pattern that solves a variety of &lt;a href="http://en.wikipedia.org/wiki/Readers-writers_problem"&gt;reader-writer problem&lt;/a&gt; without mutexes. It's similar to the &lt;a href="http://en.wikipedia.org/wiki/Read-copy-update"&gt;read-copy-update&lt;/a&gt; technique used extensively in the Linux kernel. I'm dubbing it the Wasp's Nest. Stick with me&amp;#8212;by the end of this post you'll know a neat concurrency pattern, and have a good understanding of how PyMongo handles replica set failovers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; In this post's first version I didn't know how close my code is to "ready-copy-update". &lt;a href="http://emptysquare.net/blog/wasps-nest-read-copy-update-python/#comment-890288132"&gt;Robert Moore schooled me&lt;/a&gt; in the comments. I also named it "a lock-free concurrency pattern" and &lt;a href="http://emptysquare.net/blog/wasps-nest-read-copy-update-python/#comment-892664861"&gt;Steve Baptiste pointed out&lt;/a&gt; that I was using the term wrong. My algorithm merely solves a race condition without adding a mutex, it's not lock-free. I love this about blogging: in exchange for a little humility I get a serious education.&lt;/p&gt;
&lt;hr/&gt;
&lt;ul&gt;
&lt;li&gt; &lt;a href="#the-mission"&gt;The Mission&lt;/a&gt;&lt;/li&gt;
&lt;li&gt; &lt;a href="#the-bugs"&gt;The Bugs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt; &lt;a href="#fixing-with-a-mutex"&gt;Fixing With A Mutex&lt;/a&gt;&lt;/li&gt;
&lt;li&gt; &lt;a href="#and-why-its-not-ideal"&gt;...and why it's not ideal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt; &lt;a href="#the-wasps-nest-pattern"&gt;The Wasp's Nest Pattern&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Paper Wasp" border="0" src="http://emptysquare.net/blog/media/2013/05/paper-wasp-closeup.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="paper-wasp-closeup.jpg"/&gt;
&lt;a href="http://www.mzephotos.com/gallery/insects/paper-wasp.html" style="color: gray; font-style: italic"&gt;&amp;#169; MzePhotos.com, Some Rights Reserved&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="the-mission"&gt;The Mission&lt;/h1&gt;
&lt;p&gt;MongoDB is deployed in "replica sets" of identical database servers. A replica set has one primary server and several read-only secondary servers. Over time a replica set's state can change. For example, if the primary's cooling fans fail and it bursts into flames, a secondary takes over as primary a few seconds later. Or a sysadmin can add another server to the set, and once it's synced up it becomes a new secondary.&lt;/p&gt;
&lt;p&gt;I help maintain PyMongo, the Python driver for MongoDB. Its &lt;code&gt;MongoReplicaSetClient&lt;/code&gt; is charged with connecting to the members of a set and knowing when the set changes state. Replica sets and PyMongo must avoid any single points of failure in the face of unreliable servers and networks&amp;#8212;we must never assume any particular members of the set are available.&lt;/p&gt;
&lt;p&gt;Consider this very simplified sketch of a &lt;code&gt;MongoReplicaSetClient&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style="color: #008000; font-weight: bold"&gt;class&lt;/span&gt; &lt;span style="color: #0000FF; font-weight: bold"&gt;Member&lt;/span&gt;(&lt;span style="color: #008000"&gt;object&lt;/span&gt;):
    &lt;span style="color: #BA2121; font-style: italic"&gt;"""Represents one server in the set."""&lt;/span&gt;
    &lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;__init__&lt;/span&gt;(&lt;span style="color: #008000"&gt;self&lt;/span&gt;, pool):
        &lt;span style="color: #408080; font-style: italic"&gt;# The connection pool.&lt;/span&gt;
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;pool &lt;span style="color: #666666"&gt;=&lt;/span&gt; pool

&lt;span style="color: #008000; font-weight: bold"&gt;class&lt;/span&gt; &lt;span style="color: #0000FF; font-weight: bold"&gt;MongoReplicaSetClient&lt;/span&gt;(&lt;span style="color: #008000"&gt;object&lt;/span&gt;):
    &lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;__init__&lt;/span&gt;(&lt;span style="color: #008000"&gt;self&lt;/span&gt;, seeds):
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;primary &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #008000"&gt;None&lt;/span&gt;
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;members &lt;span style="color: #666666"&gt;=&lt;/span&gt; {}
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;refresh()

        &lt;span style="color: #408080; font-style: italic"&gt;# The monitor calls refresh() every 30 sec.&lt;/span&gt;
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;monitor &lt;span style="color: #666666"&gt;=&lt;/span&gt; MonitorThread(&lt;span style="color: #008000"&gt;self&lt;/span&gt;)

    &lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;refresh&lt;/span&gt;(&lt;span style="color: #008000"&gt;self&lt;/span&gt;):
        &lt;span style="color: #408080; font-style: italic"&gt;# If we're already connected, use our list of known&lt;/span&gt;
        &lt;span style="color: #408080; font-style: italic"&gt;# members. Otherwise use the passed-in list of&lt;/span&gt;
        &lt;span style="color: #408080; font-style: italic"&gt;# possible members, the 'seeds'.&lt;/span&gt;
        seeds &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;members&lt;span style="color: #666666"&gt;.&lt;/span&gt;keys() &lt;span style="color: #AA22FF; font-weight: bold"&gt;or&lt;/span&gt; &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;seeds

        &lt;span style="color: #408080; font-style: italic"&gt;# Try seeds until first success.&lt;/span&gt;
        ismaster_response &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #008000"&gt;None&lt;/span&gt;
        &lt;span style="color: #008000; font-weight: bold"&gt;for&lt;/span&gt; seed &lt;span style="color: #AA22FF; font-weight: bold"&gt;in&lt;/span&gt; seeds:
            &lt;span style="color: #008000; font-weight: bold"&gt;try&lt;/span&gt;:
                &lt;span style="color: #408080; font-style: italic"&gt;# The 'ismaster' command gets info&lt;/span&gt;
                &lt;span style="color: #408080; font-style: italic"&gt;# about the whole set.&lt;/span&gt;
                ismaster_response &lt;span style="color: #666666"&gt;=&lt;/span&gt; call_ismaster(seed)
                &lt;span style="color: #008000; font-weight: bold"&gt;break&lt;/span&gt;
            &lt;span style="color: #008000; font-weight: bold"&gt;except&lt;/span&gt; socket&lt;span style="color: #666666"&gt;.&lt;/span&gt;error:
                &lt;span style="color: #408080; font-style: italic"&gt;# Host down / unresolvable, try the next.&lt;/span&gt;
                &lt;span style="color: #008000; font-weight: bold"&gt;pass&lt;/span&gt;

        &lt;span style="color: #008000; font-weight: bold"&gt;if&lt;/span&gt; &lt;span style="color: #AA22FF; font-weight: bold"&gt;not&lt;/span&gt; ismaster_response:
            &lt;span style="color: #008000; font-weight: bold"&gt;raise&lt;/span&gt; ConnectionFailure()

        &lt;span style="color: #408080; font-style: italic"&gt;# Now we can discover the whole replica set.&lt;/span&gt;
        &lt;span style="color: #008000; font-weight: bold"&gt;for&lt;/span&gt; host &lt;span style="color: #AA22FF; font-weight: bold"&gt;in&lt;/span&gt; ismaster_response[&lt;span style="color: #BA2121"&gt;'hosts'&lt;/span&gt;]:
            pool &lt;span style="color: #666666"&gt;=&lt;/span&gt; ConnectionPool(host)
            member &lt;span style="color: #666666"&gt;=&lt;/span&gt; Member(pool)
            &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;members[host] &lt;span style="color: #666666"&gt;=&lt;/span&gt; member

        &lt;span style="color: #408080; font-style: italic"&gt;# Remove down members from dict.&lt;/span&gt;
        &lt;span style="color: #008000; font-weight: bold"&gt;for&lt;/span&gt; host &lt;span style="color: #AA22FF; font-weight: bold"&gt;in&lt;/span&gt; &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;members&lt;span style="color: #666666"&gt;.&lt;/span&gt;keys():
            &lt;span style="color: #008000; font-weight: bold"&gt;if&lt;/span&gt; host &lt;span style="color: #AA22FF; font-weight: bold"&gt;not&lt;/span&gt; &lt;span style="color: #AA22FF; font-weight: bold"&gt;in&lt;/span&gt; ismaster_response[&lt;span style="color: #BA2121"&gt;'hosts'&lt;/span&gt;]:
                &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;members&lt;span style="color: #666666"&gt;.&lt;/span&gt;pop(host)

        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;primary &lt;span style="color: #666666"&gt;=&lt;/span&gt; ismaster_response&lt;span style="color: #666666"&gt;.&lt;/span&gt;get(&lt;span style="color: #BA2121"&gt;'primary'&lt;/span&gt;)

    &lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;send_message&lt;/span&gt;(&lt;span style="color: #008000"&gt;self&lt;/span&gt;, message):
        &lt;span style="color: #408080; font-style: italic"&gt;# Send an 'insert', 'update', or 'delete'&lt;/span&gt;
        &lt;span style="color: #408080; font-style: italic"&gt;# message to the primary.&lt;/span&gt;
        &lt;span style="color: #008000; font-weight: bold"&gt;if&lt;/span&gt; &lt;span style="color: #AA22FF; font-weight: bold"&gt;not&lt;/span&gt; &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;primary:
            &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;refresh()

        member &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;members[&lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;primary]
        pool &lt;span style="color: #666666"&gt;=&lt;/span&gt; member&lt;span style="color: #666666"&gt;.&lt;/span&gt;pool
        &lt;span style="color: #008000; font-weight: bold"&gt;try&lt;/span&gt;:
            send_message_with_pool(message, pool)
        &lt;span style="color: #008000; font-weight: bold"&gt;except&lt;/span&gt; socket&lt;span style="color: #666666"&gt;.&lt;/span&gt;error:
            &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;primary &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #008000"&gt;None&lt;/span&gt;
            &lt;span style="color: #008000; font-weight: bold"&gt;raise&lt;/span&gt; AutoReconnect()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We don't know which members will be available when our application starts, so we pass a "seed list" of hostnames to the &lt;code&gt;MongoReplicaSetClient&lt;/code&gt;. In &lt;code&gt;refresh&lt;/code&gt;, the client tries them all until it can connect to one and run the &lt;code&gt;isMaster&lt;/code&gt; command, which returns &lt;a href="http://docs.mongodb.org/manual/reference/command/isMaster/#replica-sets"&gt;information about all the members in the replica set&lt;/a&gt;. The client then makes a connection-pool for each member and records which one is the primary.&lt;/p&gt;
&lt;p&gt;Once &lt;code&gt;refresh&lt;/code&gt; finishes, the client starts a &lt;code&gt;MonitorThread&lt;/code&gt; which calls &lt;code&gt;refresh&lt;/code&gt; again every 30 seconds. This ensures that if we add a secondary to the set it will be discovered soon and participate in load-balancing. If a secondary goes down, &lt;code&gt;refresh&lt;/code&gt; removes it from &lt;code&gt;self.members&lt;/code&gt;. In &lt;code&gt;send_message&lt;/code&gt;, if we discover the primary's down, we raise an error and clear &lt;code&gt;self.primary&lt;/code&gt; so we'll call &lt;code&gt;refresh&lt;/code&gt; the next time &lt;code&gt;send_message&lt;/code&gt; runs.&lt;/p&gt;
&lt;h1 id="the-bugs"&gt;The Bugs&lt;/h1&gt;
&lt;p&gt;PyMongo 2.1 through 2.5 had two classes of concurrency bugs: race conditions and thundering herds.&lt;/p&gt;
&lt;p&gt;The race condition is easy to see. Look at the expression &lt;code&gt;self.members[self.primary]&lt;/code&gt; in &lt;code&gt;send_message&lt;/code&gt;. If the monitor thread runs &lt;code&gt;refresh&lt;/code&gt; and pops a member from &lt;code&gt;self.members&lt;/code&gt; while an application thread is executing the dictionary lookup, the latter could get a &lt;code&gt;KeyError&lt;/code&gt;. Indeed, that is &lt;a href="https://jira.mongodb.org/browse/PYTHON-467"&gt;exactly the bug report&lt;/a&gt; we received that prompted my whole investigation and this blog post.&lt;/p&gt;
&lt;p&gt;The other bug causes a big waste of effort. Let's say the primary server bursts into flames. The client gets a socket error and clears &lt;code&gt;self.primary&lt;/code&gt;. Then a bunch of application threads all call &lt;code&gt;send_message&lt;/code&gt; at once. They all find that &lt;code&gt;self.primary&lt;/code&gt; is &lt;code&gt;None&lt;/code&gt;, and all call &lt;code&gt;refresh&lt;/code&gt;. This is a duplication of work that only one thread need do. Depending how many processes and threads we have, it has the potential to create a connection storm in our replica set as a bunch of heavily-loaded applications lurch to the new primary. It also compounds the race condition because many threads are all modifying the shared state. I'm calling this duplicated work a &lt;a href="http://en.wikipedia.org/wiki/Thundering_herd_problem"&gt;thundering herd problem&lt;/a&gt;, although the official definition of thundering herd is a bit different.&lt;/p&gt;
&lt;h1 id="fixing-with-a-mutex"&gt;Fixing With A Mutex&lt;/h1&gt;
&lt;p&gt;We know how to fix race conditions: let's add a mutex! We could lock around the whole body of &lt;code&gt;refresh&lt;/code&gt;, and lock around the expression &lt;code&gt;self.members[self.primary]&lt;/code&gt; in &lt;code&gt;send_message&lt;/code&gt;. No thread sees &lt;code&gt;members&lt;/code&gt; and &lt;code&gt;primary&lt;/code&gt; in a half-updated state.&lt;/p&gt;
&lt;h2 id="and-why-its-not-ideal"&gt;...and why it's not ideal&lt;/h2&gt;
&lt;p&gt;This solution has two problems. The first is minor: the slight cost of acquiring and releasing a lock for every message sent to MongoDB, especially since it means only one thread can run that section of &lt;code&gt;send_message&lt;/code&gt; at a time. A &lt;a href="http://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock"&gt;reader-writer lock&lt;/a&gt; alleviates the contention by allowing many threads to run &lt;code&gt;send_message&lt;/code&gt; as long as no thread is running &lt;code&gt;refresh&lt;/code&gt;, in exchange for greater complexity and cost for the single-threaded case.&lt;/p&gt;
&lt;p&gt;The worse problem is the behavior such a mutex would cause in a very heavily multithreaded application. While one thread is running &lt;code&gt;refresh&lt;/code&gt;, all threads running &lt;code&gt;send_message&lt;/code&gt; will queue on the mutex. If the load is heavy enough our application could fail while waiting for &lt;code&gt;refresh&lt;/code&gt;, or could overwhelm MongoDB once they're all simultaneously unblocked. Better under most circumstances for &lt;code&gt;send_message&lt;/code&gt; to fail fast, saying "I don't know who the primary is, and I'm not going to wait for &lt;code&gt;refresh&lt;/code&gt; to tell me." Failing fast raises more errors but keeps the queues small.&lt;/p&gt;
&lt;h1 id="the-wasps-nest-pattern"&gt;The Wasp's Nest Pattern&lt;/h1&gt;
&lt;p&gt;There's a better way, one that requires no locks, is less error-prone, and fixes the thundering-herd problem too. Here's what I did for PyMongo 2.5.1, which we'll release next week.&lt;/p&gt;
&lt;p&gt;First, all information about the replica set's state is pulled out of &lt;code&gt;MongoReplicaSetClient&lt;/code&gt; and put into an &lt;code&gt;RSState&lt;/code&gt; object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style="color: #008000; font-weight: bold"&gt;class&lt;/span&gt; &lt;span style="color: #0000FF; font-weight: bold"&gt;RSState&lt;/span&gt;(&lt;span style="color: #008000"&gt;object&lt;/span&gt;):
    &lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;__init__&lt;/span&gt;(&lt;span style="color: #008000"&gt;self&lt;/span&gt;, members, primary):
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;members &lt;span style="color: #666666"&gt;=&lt;/span&gt; members
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;primary &lt;span style="color: #666666"&gt;=&lt;/span&gt; primary
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;MongoReplicaSetClient&lt;/code&gt; gets one &lt;code&gt;RSState&lt;/code&gt; instance that it puts in &lt;code&gt;self.rsstate&lt;/code&gt;. This instance is immutable: no thread is allowed to change the contents, only to make a modified copy. So if the primary goes down, &lt;code&gt;refresh&lt;/code&gt; doesn't just set &lt;code&gt;primary&lt;/code&gt; to &lt;code&gt;None&lt;/code&gt; and pop its hostname from the &lt;code&gt;members&lt;/code&gt; dict. Instead, it makes a deep copy of the &lt;code&gt;RSState&lt;/code&gt;, and updates the copy. Finally, it replaces the old &lt;code&gt;self.rsstate&lt;/code&gt; with the new one.&lt;/p&gt;
&lt;p&gt;Each of the &lt;code&gt;RSState&lt;/code&gt;'s attributes must be immutable and cloneable, too, which requires a very different mindset. For example, I'd been tracking each member's ping time using a 5-sample moving average and updating it with a new sample like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style="color: #008000; font-weight: bold"&gt;class&lt;/span&gt; &lt;span style="color: #0000FF; font-weight: bold"&gt;Member&lt;/span&gt;(&lt;span style="color: #008000"&gt;object&lt;/span&gt;):
    &lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;add_sample&lt;/span&gt;(&lt;span style="color: #008000"&gt;self&lt;/span&gt;, ping_time):
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;samples &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;samples[&lt;span style="color: #666666"&gt;-4&lt;/span&gt;:]
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;samples&lt;span style="color: #666666"&gt;.&lt;/span&gt;append(ping_time)
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;avg_ping &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #008000"&gt;sum&lt;/span&gt;(&lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;samples) &lt;span style="color: #666666"&gt;/&lt;/span&gt; &lt;span style="color: #008000"&gt;len&lt;/span&gt;(&lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;samples)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But if &lt;code&gt;Member&lt;/code&gt; is immutable, then adding a sample means cloning the whole &lt;code&gt;Member&lt;/code&gt; and updating it. Like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style="color: #008000; font-weight: bold"&gt;class&lt;/span&gt; &lt;span style="color: #0000FF; font-weight: bold"&gt;Member&lt;/span&gt;(&lt;span style="color: #008000"&gt;object&lt;/span&gt;):
    &lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;clone_with_sample&lt;/span&gt;(&lt;span style="color: #008000"&gt;self&lt;/span&gt;, ping_time):
        &lt;span style="color: #408080; font-style: italic"&gt;# Make a new copy of 'samples'&lt;/span&gt;
        samples &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;samples[&lt;span style="color: #666666"&gt;-4&lt;/span&gt;:] &lt;span style="color: #666666"&gt;+&lt;/span&gt; [ping_time]
        &lt;span style="color: #008000; font-weight: bold"&gt;return&lt;/span&gt; Member(samples)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any method that needs to access &lt;code&gt;self.rsstate&lt;/code&gt; more than once must protect itself against the state being replaced concurrently. It has to make a local copy of the reference. So the racy expression in &lt;code&gt;send_message&lt;/code&gt; becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsstate &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;rsstate  &lt;span style="color: #408080; font-style: italic"&gt;# Copy reference.&lt;/span&gt;
member &lt;span style="color: #666666"&gt;=&lt;/span&gt; rsstate&lt;span style="color: #666666"&gt;.&lt;/span&gt;members[rsstate&lt;span style="color: #666666"&gt;.&lt;/span&gt;primary]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the &lt;code&gt;rsstate&lt;/code&gt; cannot be modified by another thread, &lt;code&gt;send_message&lt;/code&gt; knows its local reference to the state is safe to read.&lt;/p&gt;
&lt;p&gt;A few summers ago I was on a Zen retreat in a rural house. We had paper wasps building nests under the eaves. The wasps make their paper from a combination of chewed-up plant fiber and saliva. The nest hangs from a single skinny petiole. It's precarious, but it seems to protect the nest from ants who want to crawl in and eat the larvae. The queen &lt;a href="http://link.springer.com/content/pdf/10.1007%2FBF01253903.pdf"&gt;periodically spreads an ant-repellant secretion around the petiole&lt;/a&gt;; its slenderness conserves her ant-repellant, and concentrates it in a small area.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Wasp's Nest in Situ" border="0" src="http://emptysquare.net/blog/media/2013/05/wasp-nest-bob-p.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="wasp-nest-bob-p.jpg"/&gt;
&lt;span style="color:gray; font-style: italic"&gt;&lt;a href="http://www.flickr.com/photos/pondapple/6134653740/"&gt;[Source]&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I think of the &lt;code&gt;RSState&lt;/code&gt; like a wasp's nest: it's an intricate structure hanging off the &lt;code&gt;MongoReplicaSetClient&lt;/code&gt; by a single attribute, &lt;code&gt;self.rsstate&lt;/code&gt;. The slenderness of the connection protects &lt;code&gt;send_message&lt;/code&gt; from race conditions, just as the thin petiole protects the nest from ants.&lt;/p&gt;
&lt;p&gt;Since I was fixing the race condition I fixed the thundering herd as well. Only one thread should run &lt;code&gt;refresh&lt;/code&gt; after a primary goes down, and all other threads should benefit from its labor. I nominated the monitor to be that one thread:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style="color: #008000; font-weight: bold"&gt;class&lt;/span&gt; &lt;span style="color: #0000FF; font-weight: bold"&gt;MonitorThread&lt;/span&gt;(threading&lt;span style="color: #666666"&gt;.&lt;/span&gt;Thread):
    &lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;__init__&lt;/span&gt;(&lt;span style="color: #008000"&gt;self&lt;/span&gt;, client):
        threading&lt;span style="color: #666666"&gt;.&lt;/span&gt;Thread&lt;span style="color: #666666"&gt;.&lt;/span&gt;__init__(&lt;span style="color: #008000"&gt;self&lt;/span&gt;)
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;client &lt;span style="color: #666666"&gt;=&lt;/span&gt; weakref&lt;span style="color: #666666"&gt;.&lt;/span&gt;proxy(client)
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;event &lt;span style="color: #666666"&gt;=&lt;/span&gt; threading&lt;span style="color: #666666"&gt;.&lt;/span&gt;Event()
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;refreshed &lt;span style="color: #666666"&gt;=&lt;/span&gt; threading&lt;span style="color: #666666"&gt;.&lt;/span&gt;Event()

    &lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;schedule_refresh&lt;/span&gt;(&lt;span style="color: #008000"&gt;self&lt;/span&gt;):
        &lt;span style="color: #BA2121; font-style: italic"&gt;"""Refresh immediately."""&lt;/span&gt;
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;refreshed&lt;span style="color: #666666"&gt;.&lt;/span&gt;clear()
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;event&lt;span style="color: #666666"&gt;.&lt;/span&gt;set()

    &lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;wait_for_refresh&lt;/span&gt;(&lt;span style="color: #008000"&gt;self&lt;/span&gt;, timeout_seconds):
        &lt;span style="color: #BA2121; font-style: italic"&gt;"""Block until refresh completes."""&lt;/span&gt;
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;refreshed&lt;span style="color: #666666"&gt;.&lt;/span&gt;wait(timeout_seconds)

    &lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;run&lt;/span&gt;(&lt;span style="color: #008000"&gt;self&lt;/span&gt;):
        &lt;span style="color: #008000; font-weight: bold"&gt;while&lt;/span&gt; &lt;span style="color: #008000"&gt;True&lt;/span&gt;:
            &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;event&lt;span style="color: #666666"&gt;.&lt;/span&gt;wait(timeout&lt;span style="color: #666666"&gt;=30&lt;/span&gt;)
            &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;event&lt;span style="color: #666666"&gt;.&lt;/span&gt;clear()

            &lt;span style="color: #008000; font-weight: bold"&gt;try&lt;/span&gt;:
                &lt;span style="color: #008000; font-weight: bold"&gt;try&lt;/span&gt;:
                    &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;client&lt;span style="color: #666666"&gt;.&lt;/span&gt;refresh()
                &lt;span style="color: #008000; font-weight: bold"&gt;finally&lt;/span&gt;:
                    &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;refreshed&lt;span style="color: #666666"&gt;.&lt;/span&gt;set()
            &lt;span style="color: #008000; font-weight: bold"&gt;except&lt;/span&gt; AutoReconnect:
                &lt;span style="color: #008000; font-weight: bold"&gt;pass&lt;/span&gt;
            &lt;span style="color: #008000; font-weight: bold"&gt;except&lt;/span&gt;:
                &lt;span style="color: #408080; font-style: italic"&gt;# Client was garbage-collected.&lt;/span&gt;
                &lt;span style="color: #008000; font-weight: bold"&gt;break&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(The weakref proxy prevents a reference cycle and lets the thread die when the client is deleted. The weird try-finally syntax is necessary in Python 2.4.)&lt;/p&gt;
&lt;p&gt;The monitor normally wakes every 30 seconds to notice changes in the set, like a new secondary being added. If &lt;code&gt;send_message&lt;/code&gt; discovers that the primary is gone, it wakes the monitor early by signaling the event it's waiting on:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsstate &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;rsstate
&lt;span style="color: #008000; font-weight: bold"&gt;if&lt;/span&gt; &lt;span style="color: #AA22FF; font-weight: bold"&gt;not&lt;/span&gt; rsstate&lt;span style="color: #666666"&gt;.&lt;/span&gt;primary:
    &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;monitor&lt;span style="color: #666666"&gt;.&lt;/span&gt;schedule_refresh()
    &lt;span style="color: #008000; font-weight: bold"&gt;raise&lt;/span&gt; AutoReconnect()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No matter how many threads call &lt;code&gt;schedule_refresh&lt;/code&gt;, the work is only done once.&lt;/p&gt;
&lt;p&gt;Any &lt;code&gt;MongoReplicaSetClient&lt;/code&gt; method that needs to block on &lt;code&gt;refresh&lt;/code&gt; can wait for the "refreshed" event:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsstate &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;rsstate
&lt;span style="color: #008000; font-weight: bold"&gt;if&lt;/span&gt; &lt;span style="color: #AA22FF; font-weight: bold"&gt;not&lt;/span&gt; rsstate&lt;span style="color: #666666"&gt;.&lt;/span&gt;primary:
    &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;monitor&lt;span style="color: #666666"&gt;.&lt;/span&gt;schedule_refresh()
    &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;monitor&lt;span style="color: #666666"&gt;.&lt;/span&gt;wait_for_refresh(timeout_seconds&lt;span style="color: #666666"&gt;=5&lt;/span&gt;)

&lt;span style="color: #408080; font-style: italic"&gt;# Get the new state.&lt;/span&gt;
rsstate &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;.&lt;/span&gt;rsstate
&lt;span style="color: #008000; font-weight: bold"&gt;if&lt;/span&gt; &lt;span style="color: #AA22FF; font-weight: bold"&gt;not&lt;/span&gt; rsstate&lt;span style="color: #666666"&gt;.&lt;/span&gt;primary:
    &lt;span style="color: #008000; font-weight: bold"&gt;raise&lt;/span&gt; AutoReconnect()

&lt;span style="color: #408080; font-style: italic"&gt;# Proceed normally....&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This pattern mitigates the connection storm from a heavily-loaded application discovering that the primary has changed: only the monitor thread goes looking for the new primary. The others can abort or wait.&lt;/p&gt;
&lt;p&gt;The wasp's nest pattern is a simple and high-performance solution to some varieties of reader-writer problem. Compared to mutexes it's easy to understand, and most importantly it's easy to program correctly. For further reading see &lt;a href="https://github.com/mongodb/mongo-python-driver/blob/02b318f9f2cac30c268aa94f2c3d71333409c41f/pymongo/mongo_replica_set_client.py#L109"&gt;my notes in the source code&lt;/a&gt;.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Paper wasp and nest" border="0" src="http://emptysquare.net/blog/media/2013/05/wasps-nest.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="wasps-nest.jpg"/&gt;
&lt;span style="color:gray; font-style: italic"&gt;&lt;a href="http://rescuebugblog.typepad.com/rescue_bugblog/2008/10/why-wednesday-1.html"&gt;[Source]&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=1166178844&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/wasps-nest-read-copy-update-python/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D207758773.1918822188.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D207758773%3B%2B__utmc%3D207758773%3B%2B__utmz%3D207758773.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D207758773.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/-hgi2neQCfg" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/wasps-nest-read-copy-update-python/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">Heading Out</title>
    <id>http://emptysquare.net/blog/heading-out/</id>
    <updated>2013-05-01T23:04:57Z</updated>
    <published>2013-05-01T23:04:57Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/L6EBajYoDMo/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;I'm heading out for a &lt;a href="http://emptysquare.net/blog/begging/"&gt;Zen street retreat&lt;/a&gt; tomorrow afternoon. I'll be back Sunday. So if you contact me I'll get back to you next week. And if you meet me on the street, can you spare a dollar?&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Street Meditation" border="0" src="http://emptysquare.net/blog/media/2013/05/street-meditation.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="street-meditation.jpg"/&gt;&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=1011171512&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/heading-out/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D365737209.1384503658.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D365737209%3B%2B__utmc%3D365737209%3B%2B__utmz%3D365737209.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D365737209.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/L6EBajYoDMo" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/heading-out/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">Another Thing About Python's Threadlocals</title>
    <id>http://emptysquare.net/blog/another-thing-about-pythons-threadlocals/</id>
    <updated>2013-04-26T02:24:16Z</updated>
    <published>2013-04-26T02:24:16Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/Tt2n4ZNkB-I/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p style="text-align: center"&gt;&lt;img alt="Dammit" border="0" src="http://emptysquare.net/blog/media/2013/04/dammit.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="dammit.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;As the maintainer of the connection pool for PyMongo, the official MongoDB driver for Python, I've gotten far more intimate knowledge of Python threads than I'd ever wanted.&lt;/p&gt;
&lt;p&gt;One of the challenges I face is: if the connect pool assigns a socket to a thread and the thread dies, how do we reclaim the socket for the general pool? I thought I nailed it last year, using a weakref callback to a threadlocal, but there's a bug in that method. &lt;a href="http://www.reversefold.com/"&gt;Justin Patrin&lt;/a&gt; of Idle Games discovered it while testing a PyMongo contribution he's making. I'm going to describe the bug, its impact, the cause, and the fix. I'll conclude by kvetching about supporting archaic versions of Python.&lt;/p&gt;
&lt;h1&gt;The Bug&lt;/h1&gt;
&lt;p&gt;Here's some code to start 1000 threads and register to be notified when they're kaput. At the end I assert no thread has died unmourned:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style="color: #008000; font-weight: bold"&gt;import&lt;/span&gt; &lt;span style="color: #0000FF; font-weight: bold"&gt;threading&lt;/span&gt;
&lt;span style="color: #008000; font-weight: bold"&gt;import&lt;/span&gt; &lt;span style="color: #0000FF; font-weight: bold"&gt;weakref&lt;/span&gt;

nthreads &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #666666"&gt;10000&lt;/span&gt;
ncallbacks &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #666666"&gt;0&lt;/span&gt;
ncallbacks_lock &lt;span style="color: #666666"&gt;=&lt;/span&gt; threading&lt;span style="color: #666666"&gt;.&lt;/span&gt;Lock()
local &lt;span style="color: #666666"&gt;=&lt;/span&gt; threading&lt;span style="color: #666666"&gt;.&lt;/span&gt;local()
refs &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #008000"&gt;set&lt;/span&gt;()

&lt;span style="color: #008000; font-weight: bold"&gt;class&lt;/span&gt; &lt;span style="color: #0000FF; font-weight: bold"&gt;Vigil&lt;/span&gt;(&lt;span style="color: #008000"&gt;object&lt;/span&gt;):
    &lt;span style="color: #008000; font-weight: bold"&gt;pass&lt;/span&gt;

&lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;run&lt;/span&gt;():
    &lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;on_thread_died&lt;/span&gt;(ref):
        &lt;span style="color: #008000; font-weight: bold"&gt;global&lt;/span&gt; ncallbacks
        ncallbacks_lock&lt;span style="color: #666666"&gt;.&lt;/span&gt;acquire()
        ncallbacks &lt;span style="color: #666666"&gt;+=&lt;/span&gt; &lt;span style="color: #666666"&gt;1&lt;/span&gt;
        ncallbacks_lock&lt;span style="color: #666666"&gt;.&lt;/span&gt;release()

    vigil &lt;span style="color: #666666"&gt;=&lt;/span&gt; Vigil()
    local&lt;span style="color: #666666"&gt;.&lt;/span&gt;vigil &lt;span style="color: #666666"&gt;=&lt;/span&gt; vigil
    refs&lt;span style="color: #666666"&gt;.&lt;/span&gt;add(weakref&lt;span style="color: #666666"&gt;.&lt;/span&gt;ref(vigil, on_thread_died))

threads &lt;span style="color: #666666"&gt;=&lt;/span&gt; [threading&lt;span style="color: #666666"&gt;.&lt;/span&gt;Thread(target&lt;span style="color: #666666"&gt;=&lt;/span&gt;run)
           &lt;span style="color: #008000; font-weight: bold"&gt;for&lt;/span&gt; _ &lt;span style="color: #AA22FF; font-weight: bold"&gt;in&lt;/span&gt; &lt;span style="color: #008000"&gt;range&lt;/span&gt;(nthreads)]
&lt;span style="color: #008000; font-weight: bold"&gt;for&lt;/span&gt; t &lt;span style="color: #AA22FF; font-weight: bold"&gt;in&lt;/span&gt; threads: t&lt;span style="color: #666666"&gt;.&lt;/span&gt;start()
&lt;span style="color: #008000; font-weight: bold"&gt;for&lt;/span&gt; t &lt;span style="color: #AA22FF; font-weight: bold"&gt;in&lt;/span&gt; threads: t&lt;span style="color: #666666"&gt;.&lt;/span&gt;join()
&lt;span style="color: #008000"&gt;getattr&lt;/span&gt;(local, &lt;span style="color: #BA2121"&gt;'c'&lt;/span&gt;, &lt;span style="color: #008000"&gt;None&lt;/span&gt;)  &lt;span style="color: #408080; font-style: italic"&gt;# Trigger cleanup in &amp;lt;= 2.7.0&lt;/span&gt;
&lt;span style="color: #008000; font-weight: bold"&gt;assert&lt;/span&gt; ncallbacks &lt;span style="color: #666666"&gt;==&lt;/span&gt; nthreads, \
    &lt;span style="color: #BA2121"&gt;'only &lt;/span&gt;&lt;span style="color: #BB6688; font-weight: bold"&gt;%d&lt;/span&gt;&lt;span style="color: #BA2121"&gt; callbacks run'&lt;/span&gt; &lt;span style="color: #666666"&gt;%&lt;/span&gt; ncallbacks
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the method I presented in &lt;a href="http://emptysquare.net/blog/knowing-when-a-python-thread-has-died/"&gt;"Knowing When A Python Thread Has Died"&lt;/a&gt;. Each thread creates a "vigil" object and sticks it in a threadlocal. Since only the threadlocal refers to the vigil, the vigil should be destroyed when the thread dies. I make a weakref to the vigil and register a &lt;a href="http://docs.python.org/2/library/weakref.html#weakref.ref"&gt;weakref callback&lt;/a&gt;. If all goes well, the callback is run as the thread dies. A quirk of Python 2.7.0 or lesser is that the callback is run when the &lt;strong&gt;next&lt;/strong&gt; thread accesses the threadlocal. This oddity is a consequence of &lt;a href="http://bugs.python.org/issue1868"&gt;Python Issue 1868&lt;/a&gt;, fixed by Antoine Pitrou in late 2010 and released in Python 2.7.1.&lt;/p&gt;
&lt;p&gt;Note also that I synchronize &lt;code&gt;ncallbacks += 1&lt;/code&gt; with a mutex, since &lt;code&gt;+=&lt;/code&gt; &lt;a href="http://emptysquare.net/blog/python-increment-is-weird/"&gt;is not atomic in Python&lt;/a&gt;. This innocent-looking mutex harbors a dark intent, as we shall soon discover.&lt;/p&gt;
&lt;p&gt;In Python 2.7.1 and newer, the code above works as expected: &lt;code&gt;ncallbacks&lt;/code&gt; is equal to 1000 immediately after all the threads are joined. In Python 2.7.0, &lt;code&gt;ncallbacks&lt;/code&gt; should be 999 after the threads are joined, and then 1000 after the main thread does the final &lt;code&gt;getattr&lt;/code&gt; to trigger cleanup.&lt;/p&gt;
&lt;p&gt;The bug is: In Python 2.7.0 and older, &lt;code&gt;ncallbacks&lt;/code&gt; is sometimes a few callbacks shy of a thousand. A few threads have been buried in unmarked graves....&lt;/p&gt;
&lt;h1&gt;Its Impact&lt;/h1&gt;
&lt;p&gt;I found that an application running Python 2.7.0 or older, if it creates and destroys very large numbers of threads continuously for a long time, and if each thread calls &lt;a href="http://api.mongodb.org/python/current/examples/requests.html"&gt;&lt;code&gt;end_request&lt;/code&gt;&lt;/a&gt; at least once and &lt;code&gt;start_request&lt;/code&gt; more times than &lt;code&gt;end_request&lt;/code&gt;, will occasionally leave a socket tied to a dead thread. These sockets will eventually exceed the process's ulimit or MongoDB's.&lt;/p&gt;
&lt;p&gt;This application pattern would be as weird and unusual as it sounds, which is why no one's complained of the bug.&lt;/p&gt;
&lt;h1&gt;The Fix&lt;/h1&gt;
&lt;p&gt;Once I'd written the test code above, I spent a few hours futzing with it&amp;#8212;Dammit, I thought this worked! I tried various techniques to force Python 2.7.0 to run the callback a thousand times reliably. Late in the day a divine voice intoned, "synchronize assignment to the threadlocal." So I added a lock:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;local_lock = threading.Lock()
# ...
    vigil = Vigil()
    local_lock.acquire()
    local.vigil = vigil
    local_lock.release()
    refs.add(weakref.ref(vigil, on_thread_died))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It worked! Now I was angrier. How can &lt;em&gt;assigning&lt;/em&gt; to a threadlocal not be thread-safe?&lt;/p&gt;
&lt;h1&gt;The Cause&lt;/h1&gt;
&lt;p&gt;Let's again consider the example code above. The bytecode for assigning &lt;code&gt;vigil&lt;/code&gt; to &lt;code&gt;local.vigil&lt;/code&gt; is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;28 LOAD_FAST        1 (vigil)
31 LOAD_GLOBAL      3 (local)
34 STORE_ATTR       4 (vigil)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;STORE_ATTR&lt;/code&gt; calls &lt;code&gt;PyObject_SetAttr&lt;/code&gt;, which calls &lt;code&gt;local_setattro&lt;/code&gt;, defined in Modules/threadmodule.c:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static &lt;span style="color: #008000"&gt;int&lt;/span&gt;
local_setattro(localobject &lt;span style="color: #666666"&gt;*&lt;/span&gt;&lt;span style="color: #008000"&gt;self&lt;/span&gt;, PyObject &lt;span style="color: #666666"&gt;*&lt;/span&gt;name, PyObject &lt;span style="color: #666666"&gt;*&lt;/span&gt;v)
{
    PyObject &lt;span style="color: #666666"&gt;*&lt;/span&gt;ldict;

&lt;span style="background-color: #ffffcc"&gt;    ldict &lt;span style="color: #666666"&gt;=&lt;/span&gt; _ldict(&lt;span style="color: #008000"&gt;self&lt;/span&gt;);
&lt;/span&gt;    &lt;span style="color: #008000; font-weight: bold"&gt;if&lt;/span&gt; (ldict &lt;span style="color: #666666"&gt;==&lt;/span&gt; NULL)
        &lt;span style="color: #008000; font-weight: bold"&gt;return&lt;/span&gt; &lt;span style="color: #666666"&gt;-1&lt;/span&gt;;

    &lt;span style="color: #008000; font-weight: bold"&gt;return&lt;/span&gt; PyObject_GenericSetAttr((PyObject &lt;span style="color: #666666"&gt;*&lt;/span&gt;)&lt;span style="color: #008000"&gt;self&lt;/span&gt;, name, v);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the highlighted line it calls &lt;code&gt;_ldict&lt;/code&gt;. The &lt;code&gt;_ldict&lt;/code&gt; function is, as I've known for some time, a pathetic piece of poo in Python 2.7.0 and older. Here's the turd, edited down a bit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static PyObject &lt;span style="color: #666666"&gt;*&lt;/span&gt;
_ldict(localobject &lt;span style="color: #666666"&gt;*&lt;/span&gt;&lt;span style="color: #008000"&gt;self&lt;/span&gt;)
{
    PyObject &lt;span style="color: #666666"&gt;*&lt;/span&gt;tdict, &lt;span style="color: #666666"&gt;*&lt;/span&gt;ldict;

    tdict &lt;span style="color: #666666"&gt;=&lt;/span&gt; PyThreadState_GetDict();
    ldict &lt;span style="color: #666666"&gt;=&lt;/span&gt; PyDict_GetItem(tdict, &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;-&amp;gt;&lt;/span&gt;key);
    &lt;span style="color: #008000; font-weight: bold"&gt;if&lt;/span&gt; (ldict &lt;span style="color: #666666"&gt;==&lt;/span&gt; NULL) {
        ldict &lt;span style="color: #666666"&gt;=&lt;/span&gt; PyDict_New(); &lt;span style="color: #666666"&gt;/*&lt;/span&gt; we own ldict &lt;span style="color: #666666"&gt;*/&lt;/span&gt;

        PyDict_SetItem(tdict, &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;-&amp;gt;&lt;/span&gt;key, ldict);
        Py_DECREF(ldict); &lt;span style="color: #666666"&gt;/*&lt;/span&gt; now ldict &lt;span style="color: #AA22FF; font-weight: bold"&gt;is&lt;/span&gt; borrowed &lt;span style="color: #666666"&gt;*/&lt;/span&gt;
        &lt;span style="color: #008000; font-weight: bold"&gt;if&lt;/span&gt; (i &lt;span style="color: #666666"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color: #666666"&gt;0&lt;/span&gt;)
            &lt;span style="color: #008000; font-weight: bold"&gt;return&lt;/span&gt; NULL;

&lt;span style="background-color: #ffffcc"&gt;        Py_CLEAR(&lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;-&amp;gt;&lt;/span&gt;&lt;span style="color: #008000"&gt;dict&lt;/span&gt;);
&lt;/span&gt;        Py_INCREF(ldict);
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;-&amp;gt;&lt;/span&gt;&lt;span style="color: #008000"&gt;dict&lt;/span&gt; &lt;span style="color: #666666"&gt;=&lt;/span&gt; ldict; &lt;span style="color: #666666"&gt;/*&lt;/span&gt; still borrowed &lt;span style="color: #666666"&gt;*/&lt;/span&gt;
    }

    &lt;span style="color: #666666"&gt;/*&lt;/span&gt; The call to tp_init above may have caused
       another thread to run&lt;span style="color: #666666"&gt;.&lt;/span&gt;
       Install our ldict again&lt;span style="color: #666666"&gt;.&lt;/span&gt; &lt;span style="color: #666666"&gt;*/&lt;/span&gt;
    &lt;span style="color: #008000; font-weight: bold"&gt;if&lt;/span&gt; (&lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;-&amp;gt;&lt;/span&gt;&lt;span style="color: #008000"&gt;dict&lt;/span&gt; &lt;span style="color: #666666"&gt;!=&lt;/span&gt; ldict) {
        Py_CLEAR(&lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;-&amp;gt;&lt;/span&gt;&lt;span style="color: #008000"&gt;dict&lt;/span&gt;);
        Py_INCREF(ldict);
        &lt;span style="color: #008000"&gt;self&lt;/span&gt;&lt;span style="color: #666666"&gt;-&amp;gt;&lt;/span&gt;&lt;span style="color: #008000"&gt;dict&lt;/span&gt; &lt;span style="color: #666666"&gt;=&lt;/span&gt; ldict;
    }

    &lt;span style="color: #008000; font-weight: bold"&gt;return&lt;/span&gt; ldict;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We haven't seen any use of the &lt;code&gt;Py_BEGIN_ALLOW_THREADS&lt;/code&gt; macro, so one thread's had the GIL the whole time. Locking around the assignment shouldn't have any effect, right?&lt;/p&gt;
&lt;p&gt;Well, take a look at the highlighted &lt;code&gt;Py_CLEAR(self-&amp;gt;dict)&lt;/code&gt; statement&amp;#8212;there's the perpetrator. That statement gets the &lt;code&gt;ldict&lt;/code&gt; of the last thread that accessed this threadlocal, swaps it with NULL and decrefs it. If this is the last reference to &lt;code&gt;ldict&lt;/code&gt; (because the last thread has died) then decref'ing destroys it, and the weakref callback to &lt;code&gt;vigil&lt;/code&gt; runs. The callback does &lt;code&gt;ncallbacks_lock.acquire&lt;/code&gt;, which releases the GIL before trying to get the mutex.&lt;/p&gt;
&lt;p&gt;So here's the kind of scenario I prevented by locking around assignment to the threadlocal:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Thread A starts, assigns to the threadlocal, dies.&lt;/li&gt;
&lt;li&gt;Thread A's &lt;code&gt;ldict&lt;/code&gt; is now the threadlocal's &lt;code&gt;self-&amp;gt;dict&lt;/code&gt; and has a refcount of 1.&lt;/li&gt;
&lt;li&gt;Thread B starts, begins assigning to the threadlocal, enters the &lt;code&gt;_ldict&lt;/code&gt; function.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_ldict&lt;/code&gt; sets &lt;code&gt;self-&amp;gt;dict&lt;/code&gt; to NULL and decrefs Thread A's &lt;code&gt;ldict&lt;/code&gt;, which runs &lt;code&gt;on_thread_died&lt;/code&gt;, which calls &lt;code&gt;ncallbacks_lock.acquire&lt;/code&gt; and releases the GIL.&lt;/li&gt;
&lt;li&gt;Now Thread C starts, begins assigning to the threadlocal, enters &lt;code&gt;_ldict&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Thread C finds &lt;code&gt;self-&amp;gt;dict&lt;/code&gt; is NULL, increfs its own local &lt;code&gt;ldict&lt;/code&gt; and assigns it to &lt;code&gt;self-&amp;gt;dict&lt;/code&gt;. It exits &lt;code&gt;_ldict&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Thread B resumes at &lt;code&gt;Py_CLEAR(self-&amp;gt;dict)&lt;/code&gt;, increfs its own &lt;code&gt;ldict&lt;/code&gt; and assigns it to &lt;code&gt;self-&amp;gt;dict&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Thread B has now replaced a pointer to Thread C's &lt;code&gt;ldict&lt;/code&gt; with a pointer to its own, but it didn't decref Thread C's &lt;code&gt;ldict&lt;/code&gt; first. (&lt;code&gt;_ldict&lt;/code&gt; wasn't written to survive interruption during &lt;code&gt;Py_CLEAR&lt;/code&gt;.) Thread C's &lt;code&gt;ldict&lt;/code&gt; will never be destroyed, and a weakref callback to its &lt;code&gt;vigil&lt;/code&gt; attribute will never be called.&lt;/p&gt;
&lt;p&gt;Locking around assignment to the threadlocal prevents &lt;code&gt;_ldict&lt;/code&gt; from running concurrently for any one threadlocal object, and prevents the refleak. In Python 2.7.1 and newer, the whole misguided &lt;code&gt;self-&amp;gt;dict&lt;/code&gt; system is removed from threadlocals and the lock's not needed.&lt;/p&gt;
&lt;p&gt;This scenario applies to PyMongo's connection pool because the pool &lt;strong&gt;does&lt;/strong&gt; need to acquire a lock in its weakref callback. Even if it didn't, there's a possibility of interruption whenever a thread is running Python code.&lt;/p&gt;
&lt;h1&gt;A Kvetch&lt;/h1&gt;
&lt;p&gt;This testing, the bug it revealed, the investigation, the fix: all this effort was spent to support entirely obsolete versions of Python. The Python core developers stopped maintaining them years ago, but PyMongo supports all Pythons going back to 2.4, mainly because there are "long-term support" Linux distros like Ubuntu and RHEL that once shipped with them. I have very savvy friends writing &lt;strong&gt;new&lt;/strong&gt; applications on Python 2.6. Our children will have flying cars before we're done debugging these steam-powered versions of Python.&lt;/p&gt;
&lt;p&gt;It's particularly frustrating because there's no point even filing bugs against Pythons before 2.7. "We fixed it," the developers will reply. "Upgrade." In Python 2.6, no one can hear you scream.&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=587667246&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/another-thing-about-pythons-threadlocals/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D655407731.1447065856.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D655407731%3B%2B__utmc%3D655407731%3B%2B__utmz%3D655407731.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D655407731.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/Tt2n4ZNkB-I" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/another-thing-about-pythons-threadlocals/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">April Street Portraits</title>
    <id>http://emptysquare.net/blog/april-street-portraits/</id>
    <updated>2013-04-21T02:41:19Z</updated>
    <published>2013-04-21T02:41:19Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/PvpWXlvHinc/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;I plan to continue my portrait project at transitional housing facilities. But scheduling those shoots is slow. Meanwhile, I need new pictures for the classes I'm taking, so I photographed some strangers in the East Village.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="April 2013 street portraits 2" border="0" src="http://emptysquare.net/blog/media/2013/04/april-2013-street-portraits-2.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="april-2013-street-portraits-2.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="April 2013 street portraits 1" border="0" src="http://emptysquare.net/blog/media/2013/04/april-2013-street-portraits-1.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="april-2013-street-portraits-1.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="April 2013 street portraits 4" border="0" src="http://emptysquare.net/blog/media/2013/04/april-2013-street-portraits-4.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="april-2013-street-portraits-4.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="April 2013 street portraits 3" border="0" src="http://emptysquare.net/blog/media/2013/04/april-2013-street-portraits-3.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="april-2013-street-portraits-3.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;I notice more than ever, in this set, how much I'm influenced by Hiroh Kikai's &lt;a href="http://www.lensculture.com/kikai2.html"&gt;Asakusa Portraits&lt;/a&gt;. Of course I'm not a fraction of the photographer he is. But like the poet Kenneth Koch &lt;a href="http://writing.upenn.edu/~afilreis/88/koch.html"&gt;said&lt;/a&gt;, I "like to be influenced."&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=1092471429&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/april-street-portraits/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D1904476858.1709658385.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D1904476858%3B%2B__utmc%3D1904476858%3B%2B__utmz%3D1904476858.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D1904476858.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/PvpWXlvHinc" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/april-street-portraits/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">Moraff's World</title>
    <id>http://emptysquare.net/blog/moraffs-world/</id>
    <updated>2013-04-20T04:28:02Z</updated>
    <published>2013-04-20T04:28:02Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/D6XhFdBz1p0/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;A long-quiescent memory got knocked loose. I recalled that I'd played Moraff's World obsessively as a kid, sneaking out of bed at night to play it on my mother's Tandy 3000. So I downloaded the game and played it for a few hours this week in a DOS emulator.&lt;/p&gt;
&lt;p&gt;Moraff's World is a fantasy role-playing game from 1991. It has the usual mechanics of the genre: You choose a race and a class like Fighter or Wizard, explore dungeons, and gain money and items by killing monsters. But Moraff's World is distinguished by its insane complexity. Characters can be one of eight races and seven classes. Killed monsters drop money in seven currencies. There are over a hundred distinct spells, and they come in books, scrolls, wands, and papers. Some characters can learn wizardly or priestly spells, some can learn both. The main UI looks like the bridge of a nuclear submarine:&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Moraff dungeon" border="0" src="http://emptysquare.net/blog/media/2013/04/moraff-dungeon.png" style="display:block; margin-left:auto; margin-right:auto;" title="moraff-dungeon.png"/&gt;&lt;/p&gt;
&lt;p&gt;You look in all four directions at once. There is also an overhead map, like this section of town:&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Moraff town" border="0" src="http://emptysquare.net/blog/media/2013/04/moraff-town.png" style="display:block; margin-left:auto; margin-right:auto;" title="moraff-town.png"/&gt;&lt;/p&gt;
&lt;p&gt;It is the player's job to memorize that yellow squares are temples, red are inns, and so forth.&lt;/p&gt;
&lt;p&gt;In a modern role-playing game like Diablo, the town feels alive: music plays, rain patters down, random characters walk around and talk. Moraff's town is vacant and still.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Moraff town 2" border="0" src="http://emptysquare.net/blog/media/2013/04/moraff-town-2.png" style="display:block; margin-left:auto; margin-right:auto;" title="moraff-town-2.png"/&gt;&lt;/p&gt;
&lt;p&gt;It is not characters who speak to you in Moraff's World, but the programmer Steve Moraff himself. When you enter a bank, the options include PRESS 4 TO ROB BANK. Do so, and the game replies, COME ON! DO YOU REALLY THINK I'D LET YOU ROB MY OWN BANK? PRESS ANY KEY TO CONTINUE.... You are not immersed. You are explicitly in a game designed by a single programmer.&lt;/p&gt;
&lt;p&gt;The experience does not resemble a fantasy movie like Lord of the Rings as much as it does reading a fantasy book. When I see a balrog in the movie, I see its fiery skull and its whip, pretty much the same as other viewers. How different from reading Tolkien's description: "a great shadow, in the middle of which was a dark form, of man-shape maybe, yet greater; and a power and terror seemed to be in it and to go before it." I make my own balrog from these words. In the same way, in the absence of any sound or animation, I supply the missing life to Moraff's static world.&lt;/p&gt;
&lt;p&gt;This is not to say that the game lacks charm. It is incredibly idiosyncratic. The monsters seem drawn in MS Paint by an exuberant child. Consider this werewolf, and what appears to be a Hawaiian zombie:&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Moraff monsters" border="0" src="http://emptysquare.net/blog/media/2013/04/moraff-monsters.png" style="display:block; margin-left:auto; margin-right:auto;" title="moraff-monsters.png"/&gt;&lt;/p&gt;
&lt;p&gt;The game's engineering is as primitive as its art. Graphics are drawn in layers with the Painter's Algorithm. With each step your character takes, the views in all four directions are re-rendered. The walls are slowly drawn on screen from farthest to nearest. Even on my modern laptop this can take some time when looking down a long hallway. But the technique lends itself to fun effects, like the translucent Shadow Minidragon, partially drawn over the walls behind him:&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Moraff minidragon" border="0" src="http://emptysquare.net/blog/media/2013/04/moraff-minidragon.png" style="display:block; margin-left:auto; margin-right:auto;" title="moraff-minidragon.png"/&gt;&lt;/p&gt;
&lt;p&gt;Moraff applied a similar method to the Wilderness. You may climb a ladder out of town to reach this randomly-generated landscape. It takes several seconds to calculate. (It took several &lt;em&gt;minutes&lt;/em&gt; on my mother's Tandy.) If you hit H for Help, Moraff tells you that there's no point exploring the wilderness. It only leads to other dungeons which are all the same. It's a sightseeing expedition.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Moraff wilderness" border="0" src="http://emptysquare.net/blog/media/2013/04/moraff-wilderness.png" style="display:block; margin-left:auto; margin-right:auto;" title="moraff-wilderness.png"/&gt;&lt;/p&gt;
&lt;p&gt;When I downloaded it this week, my first impression of this Shareware-era game was nostalgic. Back then, games were envisioned by a few people or, in the case of Moraff, one programmer-auteur. There was room for a folk genius to succeed with a very weird game. Nowadays he'd be drowned out by games with &lt;a href="http://www.gamesindustry.biz/articles/2013-02-01-gta-v-dev-costs-over-USD137-million-says-analyst"&gt;hundred-million-dollar budgets&lt;/a&gt; like Grand Theft Auto V.&lt;/p&gt;
&lt;p&gt;But on second thought, the industry is simply more mature, with a bigger audience and a broader range of games. There's still a place for avant-garde titles developed by small teams, like &lt;a href="http://braid-game.com/"&gt;Braid&lt;/a&gt; or &lt;a href="http://www.swordandsworcery.com/"&gt;Sword &amp;amp; Sworcery&lt;/a&gt;. Both use the vocabulary of the simple video games we played as children to evoke grown-up ideas.&lt;/p&gt;
&lt;p&gt;Braid is a platform-jumping game, explicitly an homage to Super Mario Brothers. There's even a princess to rescue. But the protagonist has mysterious powers to slow or reverse time, and the game asks: if you had these powers, what would you be? Does the princess want to be rescued? Are you playing the good guy or not?&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Braid" border="0" src="http://emptysquare.net/blog/media/2013/04/braid-game.png" style="display:block; margin-left:auto; margin-right:auto;" title="braid-game.png"/&gt;&lt;/p&gt;
&lt;p&gt;In the iPad game Sword &amp;amp; Sworcery, the graphics are deliberately archaic and pixellated, but the themes are innovative. The game makes surprising demands regarding its place in the player's life. After you beat a level, for example, Sword &amp;amp; Sworcery pauses for a minute and suggests you take a break and do something else. There are levels that can only be played near a full moon, or a new moon. I changed my iPad's date so I could play them immediately. The designers' goal&amp;#8212;to make me aware of my addictive game-love&amp;#8212;was accomplished.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Sword and Sworcery" border="0" src="http://emptysquare.net/blog/media/2013/04/sword-and-sworcery.png" style="display:block; margin-left:auto; margin-right:auto;" title="sword-and-sworcery.png"/&gt;&lt;/p&gt;
&lt;p&gt;The other striking idea of Sword &amp;amp; Sworcery is that one's character does not level up. Instead, with each victory she is weakened. She must keep fighting the same monsters but they grow tougher as the protagonist becomes more vulnerable. At the end, she beats the final boss, but she is retching blood, and flings herself into the river to die. By comparison, role-playing games where your character gains godlike powers seem like childish wish-fulfillment. If you were really a warrior come to save a town, this would be how you'd end up.&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=1784819297&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/moraffs-world/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D997231103.1585037443.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D997231103%3B%2B__utmc%3D997231103%3B%2B__utmz%3D997231103.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D997231103.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/D6XhFdBz1p0" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/moraffs-world/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">The Green Matrix</title>
    <id>http://emptysquare.net/blog/the-green-matrix/</id>
    <updated>2013-04-18T21:49:35Z</updated>
    <published>2013-04-18T21:49:35Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/NOQsVLyYevU/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;For a year and a half I've been part of the team maintaining &lt;a href="https://pypi.python.org/pypi/pymongo/"&gt;PyMongo&lt;/a&gt;, the Python MongoDB driver. It's one of the most widely used Python packages with &lt;a href="http://pypi-ranking.info/module/pymongo"&gt;1.5 million lifetime downloads&lt;/a&gt;. The code itself is only moderately complex; about 8300 source lines. What makes it a tiny horror to work on is the range of environments we support. Here's our test matrix in Jenkins:&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="PyMongo test matrix" border="0" src="http://emptysquare.net/blog/media/2013/04/pymongo-jenkins-matrix.png" style="display:block; margin-left:auto; margin-right:auto;" title="pymongo-jenkins-matrix.png"/&gt;&lt;/p&gt;
&lt;p&gt;That's 72 test configurations. (It looks like more than that, but we don't test Jython and PyPy with C extensions compiled since that currently doesn't make sense.) The dimensions are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Python version: We support CPython 2.4 through 3.3. On each commit we test just the highlight versions: 2.4, 2.7, and 3.3. We also support the latest Jython and PyPy. We test the intermediate versions like 2.5 and 2.6 before a release.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;C extensions: we have a few key parts of PyMongo implemented in C for speed, with pure-Python versions as a fallback. We test both modes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MongoDB Version: We test the latest development branch of MongoDB (2.5) plus the last two production versions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MongoDB Configuration: We set up a single server, a master-slave pair, and a three-node replica set, and run mostly the same tests against all.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In each test configuration, PyMongo's test suite has about 430 individual test functions.&lt;/p&gt;
&lt;p&gt;This covers the main test matrix, but there are some auxiliary tests we run in Jenkins on every commit. We have a mod_wsgi test that runs a few thousand web requests (first serial, then parallel) against a web app using mod_wsgi in a range of configurations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Python 2.4, 2.5, 2.6, and 2.7&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;mod_wsgi 2.8, 3.2, and 3.3&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The latest production MongoDB as a single server or replica set&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The mod_wsgi tests are there to ensure we never recreate a connection leak like the apocalyptic &lt;a href="https://jira.mongodb.org/browse/PYTHON-353"&gt;"unbounded connection growth with Apache mod_wsgi 2.x"&lt;/a&gt; bug to which I lost some of the best weeks of my life.&lt;/p&gt;
&lt;p&gt;I've also set up some tests for &lt;a href="http://motor.readthedocs.org/en/latest/"&gt;Motor&lt;/a&gt;, my non-blocking MongoDB driver for Tornado: I run in Python 2.6, 2.7, and 3.3 against a single MongoDB server and a replica set, running the three most recent versions of MongoDB. I have a separate Motor test that connects to MongoDB over SSL, and finally I have a test of "Synchro," which wraps Motor inside a resynchronization layer and checks it can pass all the same tests as PyMongo. In all, Jenkins runs 33 test configurations for each Motor commit.&lt;/p&gt;
&lt;p&gt;Jenkins automatically tests our main configurations, but we periodically hand-test some additional configurations, like sharded clusters, beta releases of Jython and PyPy, and Windows. We'll put some of these in Jenkins too.&lt;/p&gt;
&lt;p&gt;For a team of three people to build and maintain this volume of test infrastructure is a huge effort. It's clearly worth it, &lt;em&gt;because&lt;/em&gt; the test matrix is so large. But it's not much fun.&lt;/p&gt;
&lt;p&gt;Lessons learned:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test code is a liability:&lt;/strong&gt; Too much testing code is as bad as too much of any other kind of code. Write as few tests as possible to cover the cases you need to test. Over-testing comforts the novice but impedes agility. For example, when we &lt;a href="http://emptysquare.net/blog/pymongos-new-default-safe-writes/"&gt;renamed PyMongo's Connection class to MongoClient&lt;/a&gt;, I had to change over 1000 lines in 32 files in the test suite. A commit that huge is a barrier in the repository's history, across which no commit can be moved without conflicts. I hope to never do anything like it again. The test suite should be smaller and better factored.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tests must be very reliable:&lt;/strong&gt; It needs to be not only minimal but also very reliable. Tests should fail if and only if the behavior they test breaks. When I joined the team, PyMongo's tests often failed "just cuz." Fixing them all took months: We'd observe an intermittent failure in Jenkins due to some race condition that we couldn't reproduce on our laptops (an EC2 "medium" instance runs a three-node MongoDB cluster slower than you could possibly imagine). We'd think real hard and finally understand and fix the failure. Then we'd do the same for some other test. It was a costly exercise but necessary: It's not until our tests always passed that we took them seriously when they didn't.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are other dicta that I find negotiable: tests should be fast, sure, but I can live with a test suite that takes a few minutes to run per configuration. Perhaps test methods should include only one assert, but I can live with several asserts in some methods.&lt;/p&gt;
&lt;p&gt;I'm implacably opposed to mocking when it comes to testing PyMongo: what our tests verify is primarily our understanding of how to talk to MongoDB. If we mocked any aspect whatsoever of the MongoDB server, our tests would be worse than useless. Virtually every test of PyMongo is an integration test, so we make no distinction between "unit tests" and "integration tests."&lt;/p&gt;
&lt;p&gt;I'm curious what others have learned from maintaining a driver's test suite. It seems to be a lot of hard work no matter what.&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=721173539&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/the-green-matrix/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D1417361503.1941515021.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D1417361503%3B%2B__utmc%3D1417361503%3B%2B__utmz%3D1417361503.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D1417361503.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/NOQsVLyYevU" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/the-green-matrix/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">I Will Pick Up What Others Discard</title>
    <id>http://emptysquare.net/blog/i-will-pick-up-what-others-discard/</id>
    <updated>2013-04-17T18:13:58Z</updated>
    <published>2013-04-17T18:13:58Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/XWs99ydIEvI/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;My friend Jim Roberts emailed me this quote from &lt;a href="http://www.drba.org/dharma/hsuanhuabio.asp"&gt;Master Hua&lt;/a&gt;, a founder of Chan Buddhism in the West:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Those in search of the Way should bear this in mind: "I will pick up what others discard." What others do not want, I want; what others will not eat, I will eat; what others will not suffer, I will suffer; what others will not tolerate, I will tolerate; what others will not permit, I will permit; what others will not do, I will do. If you want to support others, you must do it from below. "Seeking the Way from a lower place" means starting from below, not standing up at the top of the mountain. You will never see the Way from the top of Mount Sumeru; but when you are at the very bottom of Mount Sumeru, there you will find the Way.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Jim says this reminds him of &lt;a href="http://emptysquare.net/blog/begging/"&gt;street retreat&lt;/a&gt;. Yes. Here's my friend Sh&amp;#333;in collecting cans during a street retreat last year:&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Shoin collecting cans" border="0" src="http://emptysquare.net/blog/media/2013/04/shoin-cans.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="shoin-cans.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;This non-rejecting mind, this mind of spiritual poverty, is the muscle we're training when we're on the street.&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=661738930&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/i-will-pick-up-what-others-discard/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D1877382134.1948357642.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D1877382134%3B%2B__utmc%3D1877382134%3B%2B__utmz%3D1877382134.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D1877382134.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/XWs99ydIEvI" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/i-will-pick-up-what-others-discard/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">DZone Interviews Me</title>
    <id>http://emptysquare.net/blog/dzone-interviews-me/</id>
    <updated>2013-04-17T15:30:29Z</updated>
    <published>2013-04-17T15:30:29Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/jYooc2eKBd0/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;The developer site DZone interviewed me for their "Dev of the Week" feature. I talked about Python, photography, and being teased by my girlfriend. &lt;a href="http://architects.dzone.com/articles/dev-week-jesse-jiryu-davis"&gt;Read the whole thing on DZone.com&lt;/a&gt;.&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=647975165&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/dzone-interviews-me/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D1607087171.1165861274.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D1607087171%3B%2B__utmc%3D1607087171%3B%2B__utmz%3D1607087171.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D1607087171.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/jYooc2eKBd0" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/dzone-interviews-me/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">Slides From My Talk On Python Coroutines</title>
    <id>http://emptysquare.net/blog/slides-from-my-talk-on-python-coroutines/</id>
    <updated>2013-04-17T02:27:38Z</updated>
    <published>2013-04-17T02:27:38Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/ZTo0oswmATA/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;Here's the slides from tonight's NYC Python Meetup talk on coroutines in Tornado and Tulip. The slides are a bit inscrutable on their own&amp;#8212;it's my style to just show code, then talk a lot to explain the code. Still, if you were there tonight you may find these useful.&lt;/p&gt;
&lt;p&gt;&lt;iframe allowfullscreen="None" frameborder="0" height="356" marginheight="0" marginwidth="0" mozallowfullscreen="None" scrolling="no" src="http://www.slideshare.net/slideshow/embed_code/18959564" style="border:1px solid #CCC;border-width:1px 1px 0;margin-bottom:5px" webkitallowfullscreen="None" width="427"&gt; &lt;/iframe&gt; &lt;div style="margin-bottom:5px"&gt; &lt;strong&gt; &lt;a href="http://www.slideshare.net/emptysquare/nyc-python-meetup-coroutines-2013-0416" target="_blank" title="Python Coroutines, Present and Future"&gt;Python Coroutines, Present and Future&lt;/a&gt; &lt;/strong&gt; from &lt;strong&gt;&lt;a href="http://www.slideshare.net/emptysquare" target="_blank"&gt;emptysquare&lt;/a&gt;&lt;/strong&gt; &lt;/div&gt;&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=1141002269&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/slides-from-my-talk-on-python-coroutines/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D1560301248.1159880945.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D1560301248%3B%2B__utmc%3D1560301248%3B%2B__utmz%3D1560301248.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D1560301248.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/ZTo0oswmATA" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/slides-from-my-talk-on-python-coroutines/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">Toro Rewritten for Tornado 3.0</title>
    <id>http://emptysquare.net/blog/toro-rewritten-for-tornado-3-0/</id>
    <updated>2013-04-12T20:27:58Z</updated>
    <published>2013-04-12T20:27:58Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/8Gvq2bquVmA/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p style="text-align: center"&gt;&lt;img alt="Toro" border="0" src="http://emptysquare.net/blog/media/2012/11/toro.png" style="display:block; margin-left:auto; margin-right:auto;" title="toro.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://emptysquare.net/blog/pycon-lightning-talk-about-toro/"&gt;Speaking of my package Toro&lt;/a&gt;, I've just released version 0.5. Toro provides semaphores, queues, and so on, for advanced control flows with Tornado coroutines. &lt;/p&gt;
&lt;p&gt;Version 0.5 is a rewrite, motivated by two recent events. First, the release of Tornado 3.0 has introduced a much more convenient coroutine API, and I wanted Toro to support the modern style. Second, I &lt;a href="http://code.google.com/p/tulip/source/detail?r=f83dba559f89"&gt;contributed a version of Toro's queues to Tulip&lt;/a&gt;, and the queues changed a bit in the process. As much as possible, I updated Toro to match the API of Tulip's locks and queues, for consistency's sake.&lt;/p&gt;
&lt;p&gt;In previous versions, most Toro methods had to be wrapped in &lt;code&gt;gen.Task&lt;/code&gt;, which made for weird-looking code. But using Toro is now quite graceful. For example, a producer-consumer pair:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;q &lt;span style="color: #666666"&gt;=&lt;/span&gt; toro&lt;span style="color: #666666"&gt;.&lt;/span&gt;Queue()

&lt;span style="color: #AA22FF"&gt;@gen.coroutine&lt;/span&gt;
&lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;producer&lt;/span&gt;():
    &lt;span style="color: #008000; font-weight: bold"&gt;for&lt;/span&gt; item &lt;span style="color: #AA22FF; font-weight: bold"&gt;in&lt;/span&gt; &lt;span style="color: #008000"&gt;range&lt;/span&gt;(&lt;span style="color: #666666"&gt;5&lt;/span&gt;):
        &lt;span style="color: #008000; font-weight: bold"&gt;print&lt;/span&gt; &lt;span style="color: #BA2121"&gt;'Sending'&lt;/span&gt;, item
        &lt;span style="color: #008000; font-weight: bold"&gt;yield&lt;/span&gt; q&lt;span style="color: #666666"&gt;.&lt;/span&gt;put(item)

&lt;span style="color: #AA22FF"&gt;@gen.coroutine&lt;/span&gt;
&lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;consumer&lt;/span&gt;():
    &lt;span style="color: #008000; font-weight: bold"&gt;while&lt;/span&gt; &lt;span style="color: #008000"&gt;True&lt;/span&gt;:
        item &lt;span style="color: #666666"&gt;=&lt;/span&gt; &lt;span style="color: #008000; font-weight: bold"&gt;yield&lt;/span&gt; q&lt;span style="color: #666666"&gt;.&lt;/span&gt;get()
        &lt;span style="color: #008000; font-weight: bold"&gt;print&lt;/span&gt; &lt;span style="color: #BA2121"&gt;'&lt;/span&gt;&lt;span style="color: #BB6622; font-weight: bold"&gt;\t\t&lt;/span&gt;&lt;span style="color: #BA2121"&gt;'&lt;/span&gt;, &lt;span style="color: #BA2121"&gt;'Got'&lt;/span&gt;, item

consumer()
producer()
IOLoop&lt;span style="color: #666666"&gt;.&lt;/span&gt;current()&lt;span style="color: #666666"&gt;.&lt;/span&gt;start()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another nice new feature: &lt;code&gt;Semaphore.acquire&lt;/code&gt; and &lt;code&gt;Lock.acquire&lt;/code&gt; can be used with the &lt;code&gt;with&lt;/code&gt; statement:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lock &lt;span style="color: #666666"&gt;=&lt;/span&gt; toro&lt;span style="color: #666666"&gt;.&lt;/span&gt;Lock()

&lt;span style="color: #AA22FF"&gt;@gen.coroutine&lt;/span&gt;
&lt;span style="color: #008000; font-weight: bold"&gt;def&lt;/span&gt; &lt;span style="color: #0000FF"&gt;f&lt;/span&gt;():
   &lt;span style="color: #008000; font-weight: bold"&gt;with&lt;/span&gt; (&lt;span style="color: #008000; font-weight: bold"&gt;yield&lt;/span&gt; lock&lt;span style="color: #666666"&gt;.&lt;/span&gt;acquire()):
       &lt;span style="color: #008000; font-weight: bold"&gt;print&lt;/span&gt; &lt;span style="color: #BA2121"&gt;"We're in the lock"&lt;/span&gt;

   &lt;span style="color: #008000; font-weight: bold"&gt;print&lt;/span&gt; &lt;span style="color: #BA2121"&gt;"Out of the lock"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;More &lt;a href="http://toro.readthedocs.org/en/stable"&gt;examples are in the docs&lt;/a&gt;. Enjoy!&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=1953036358&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/toro-rewritten-for-tornado-3-0/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D621168319.1106917110.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D621168319%3B%2B__utmc%3D621168319%3B%2B__utmz%3D621168319.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D621168319.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/8Gvq2bquVmA" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/toro-rewritten-for-tornado-3-0/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">My PyCon Lightning Talk About Toro</title>
    <id>http://emptysquare.net/blog/pycon-lightning-talk-about-toro/</id>
    <updated>2013-04-11T18:29:00Z</updated>
    <published>2013-04-11T18:29:00Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/urTqhW0hEIk/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;The lightning talk I gave at PyCon is now online. I talked for 4&amp;#189; minutes on &lt;a href="http://toro.readthedocs.org/en/stable/"&gt;Toro&lt;/a&gt;, the package I wrote to provide locks, events, conditions, semaphores, and queues for Tornado. Watch for a quick intro on advanced control flow with coroutines:&lt;/p&gt;
&lt;iframe allowfullscreen="None" frameborder="0" height="315" src="http://www.youtube.com/embed/pYZNLOKEE5I?rel=0" width="420"&gt;&lt;/iframe&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=303314708&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/pycon-lightning-talk-about-toro/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D1413874197.2019867142.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D1413874197%3B%2B__utmc%3D1413874197%3B%2B__utmz%3D1413874197.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D1413874197.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/urTqhW0hEIk" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/pycon-lightning-talk-about-toro/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">reStructuredText in PyCharm, Firefox, and Anger</title>
    <id>http://emptysquare.net/blog/restructuredtext-in-pycharm-firefox-and-anger/</id>
    <updated>2013-04-10T15:09:07Z</updated>
    <published>2013-04-10T15:09:07Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/MovTs9NVqJc/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;I spend a lot of time writing Python package documentation in reST. Nevertheless, I find reST's markup permanently unlearnable, so I format docs by trial and error: I type a few backticks and colons and angle-brackets and random crap, &lt;code&gt;sphinx-build&lt;/code&gt; the docs as HTML, and see if they look okay. &lt;/p&gt;
&lt;p&gt;Here's some tools to support this expert workflow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PyCharm&lt;/strong&gt;: &lt;a href="http://www.jetbrains.com/pycharm/"&gt;My favorite Python IDE&lt;/a&gt; has basic syntax-highlighting and auto-completion for reST. It's not much, but it far exceeds the amount of reStructuredText syntax that can fit in my tiny brain. It really shines when I'm embedding Python code examples in my docs: PyCharm gives me full IDE support, including automatically adding imports, auto-completing method names and parameters, and nearly all the help I get when editing normal Python files.&lt;/p&gt;
&lt;p&gt;There's &lt;a href="http://plugins.jetbrains.com/plugin?pr=idea&amp;amp;pluginId=7177"&gt;a file-watcher plugin for PyCharm&lt;/a&gt; that seems like a nice way to rebuild docs when the source files change, but it's not yet compatible with the latest version of PyCharm. So instead:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Watchdog&lt;/strong&gt;: I install the &lt;a href="https://pypi.python.org/pypi/watchdog"&gt;watchdog Python package&lt;/a&gt;, which watches files and directories for changes. Watchdog gives me a command-line tool called &lt;code&gt;watchmedo&lt;/code&gt;. (I find this fact unlearnable, too; why isn't the tool called &lt;code&gt;watchdog&lt;/code&gt; the same as the package?) I tell it to watch my package's files for changes and rebuild the docs whenever I save a file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;watchmedo shell-command --command&lt;span style="color: #666666"&gt;=&lt;/span&gt;&lt;span style="color: #BA2121"&gt;"sphinx-build doc build"&lt;/span&gt; .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that I can regenerate HTML automatically, I need a way to reload the browser window automatically:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;auto-reload&lt;/strong&gt; is a &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/auto-reload/"&gt;Firefox extension&lt;/a&gt; that detects any tab with a &lt;code&gt;file://&lt;/code&gt; URL and reloads it when the file changes. In my testing it seems to detect changes in linked files (CSS and Javascript) too. A nice little bar slides down to tell me when it's reloading. That way I know that the reason the page is still a mess is because my reST is still wrong, not because it hasn't reloaded:&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Auto reload" border="0" src="http://emptysquare.net/blog/media/2013/04/auto-reload.png" style="display:block; margin-left:auto; margin-right:auto;" title="auto-reload.png"/&gt;&lt;/p&gt;
&lt;p&gt;This little suite of tools deals well with invoking Sphinx and reloading my web page, so I can focus on the task at hand: trying to write reStructuredText, which is a loathsome afterbirth expelled from the same womb as XML and TeX.&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=678054857&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/restructuredtext-in-pycharm-firefox-and-anger/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D710064342.1394268497.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D710064342%3B%2B__utmc%3D710064342%3B%2B__utmz%3D710064342.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D710064342.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/MovTs9NVqJc" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/restructuredtext-in-pycharm-firefox-and-anger/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">Begging</title>
    <id>http://emptysquare.net/blog/begging/</id>
    <updated>2013-04-03T18:45:40Z</updated>
    <published>2013-04-03T18:45:40Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/P1bemO-UVUQ/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p style="text-align: center"&gt;&lt;img alt="7161960026 e92ea3c4bb" border="0" src="http://emptysquare.net/blog/media/2013/04/7161960026_e92ea3c4bb.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="7161960026_e92ea3c4bb.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;I periodically spend four days homeless, with a Zen teacher named Genro and a small group of fellow Buddhists. We live, sleep, and meditate on the streets together and eat at soup kitchens. I think the retreat has a triple purpose: First, briefly abandoning the comfort and certainty of my regular life helps me practice non-attachment, the same as it helped the first Buddhist monks. Second, it gives me a taste of what it's like to be homeless, so I can better understand the homeless people I meet in NYC. And finally, it's an opportunity to raise money for homeless services.&lt;/p&gt;
&lt;p&gt;The rule is that I must raise $500 by May 2. The money will be distributed among the organizations that help us while we're on the street, and it will support the social service activities of the &lt;a href="http://hudsonriverzencenter.org/"&gt;Hudson River Peacemaker Center&lt;/a&gt;. I have to beg for the money&amp;#8212;I'm not allowed to just donate $500 of my own.&lt;/p&gt;
&lt;p&gt;So I'm begging you: Will you please donate?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: I've now (April 16) exceeded my minimum, with $803. But donate anyway! Additional funds are divided the same as the first $500.&lt;/p&gt;
&lt;form action="https://www.paypal.com/cgi-bin/webscr" method="post" style="text-align: center" target="_top"&gt;
&lt;input name="cmd" type="hidden" value="_s-xclick"/&gt;
&lt;input name="encrypted" type="hidden" value="-----BEGIN PKCS7-----MIIHNwYJKoZIhvcNAQcEoIIHKDCCByQCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYB6BBc/JssZ6Ol9CUFYfFm6f5TfZ5Wr5N83wrb98m7O1HW8nhQi1f9RvPlGqvAq3Y+737zeuT+/DxShBYQRTbKEFncgytrurjBqqhJoCuostmAyeFoc04JjVFjtn371HaRljcj8AICLqTidOIqfr87qelKGP0jooHkG1I5buE/D2jELMAkGBSsOAwIaBQAwgbQGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIIq057c4vFKaAgZAN0cV14wDJzgBz57g5VQU2uNX1BBPfDioHsnaPBVCRw/21HplVuzq9FgR5m9hH0tmlAD2mYY0LgDnfTt5z2V2viMTWvZGQKfrRh9lQfXkhgcHisuonRTHYPz3pxPsCledLAHuv49VU2JxjtTW4qB4C7jcP7OIpwJ8zq48WmvpUAqoN9Tp9XhNFtlB3mKbXp6mgggOHMIIDgzCCAuygAwIBAgIBADANBgkqhkiG9w0BAQUFADCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20wHhcNMDQwMjEzMTAxMzE1WhcNMzUwMjEzMTAxMzE1WjCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMFHTt38RMxLXJyO2SmS+Ndl72T7oKJ4u4uw+6awntALWh03PewmIJuzbALScsTS4sZoS1fKciBGoh11gIfHzylvkdNe/hJl66/RGqrj5rFb08sAABNTzDTiqqNpJeBsYs/c2aiGozptX2RlnBktH+SUNpAajW724Nv2Wvhif6sFAgMBAAGjge4wgeswHQYDVR0OBBYEFJaffLvGbxe9WT9S1wob7BDWZJRrMIG7BgNVHSMEgbMwgbCAFJaffLvGbxe9WT9S1wob7BDWZJRroYGUpIGRMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAIFfOlaagFrl71+jq6OKidbWFSE+Q4FqROvdgIONth+8kSK//Y/4ihuE4Ymvzn5ceE3S/iBSQQMjyvb+s2TWbQYDwcp129OPIbD9epdr4tJOUNiSojw7BHwYRiPh58S1xGlFgHFXwrEBb3dgNbMUa+u4qectsMAXpVHnD9wIyfmHMYIBmjCCAZYCAQEwgZQwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tAgEAMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xMzA0MDMxODE5MDZaMCMGCSqGSIb3DQEJBDEWBBRy8Ifc/VpPpz4maD0djzBCWGpcpTANBgkqhkiG9w0BAQEFAASBgGWtdZUAQJRWbM1YHZ1WWPCyz7lTl/S9jQoFhrbzn+Jz3Qm/duRkWGfWxgRb3Ayegg2q1vtQ5BP0k6FhKmeXatZu+ouzUOA0ZEGsx/dRt9qgjH5GEcckfKpqBLAdXl2Q+WODLY1onelOlYiY9nb9/jlPv8eRLAYH7KICcJ76gA6J-----END PKCS7-----
"/&gt;
&lt;input alt="PayPal - The safer, easier way to pay online!" border="0" name="submit" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" type="image"/&gt;
&lt;img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif"/&gt;
&lt;/form&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=1381031725&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/begging/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D330657352.1423641616.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D330657352%3B%2B__utmc%3D330657352%3B%2B__utmz%3D330657352.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D330657352.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/P1bemO-UVUQ" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/begging/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">Shuso Hossen, Spring 2013</title>
    <id>http://emptysquare.net/blog/shuso-hossen-spring-2013/</id>
    <updated>2013-04-03T02:53:38Z</updated>
    <published>2013-04-03T02:53:38Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/iIhnzEPBivw/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;Two weeks ago the Village Zendo completed a week-long urban sesshin focused on our awareness of disabilities. We were blindfolded for part of one day, and wore earplugs for part of another. The retreat ended with the Shuso Hossen ceremony, in which R. Liam Oshin Jennings gave his first dharma talk.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Oshin shuso hossen 3" border="0" src="http://emptysquare.net/blog/media/2013/04/oshin-shuso-hossen-3.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="oshin-shuso-hossen-3.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Oshin shuso hossen 1" border="0" src="http://emptysquare.net/blog/media/2013/04/oshin-shuso-hossen-1.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="oshin-shuso-hossen-1.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Oshin shuso hossen 2" border="0" src="http://emptysquare.net/blog/media/2013/04/oshin-shuso-hossen-2.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="oshin-shuso-hossen-2.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Oshin shuso hossen 4" border="0" src="http://emptysquare.net/blog/media/2013/04/oshin-shuso-hossen-4.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="oshin-shuso-hossen-4.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Oshin shuso hossen 5" border="0" src="http://emptysquare.net/blog/media/2013/04/oshin-shuso-hossen-5.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="oshin-shuso-hossen-5.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Oshin shuso hossen 7" border="0" src="http://emptysquare.net/blog/media/2013/04/oshin-shuso-hossen-7.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="oshin-shuso-hossen-7.jpg"/&gt;&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Oshin shuso hossen 9" border="0" src="http://emptysquare.net/blog/media/2013/04/oshin-shuso-hossen-9.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="oshin-shuso-hossen-9.jpg"/&gt;&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=1638928583&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/shuso-hossen-spring-2013/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D2051475379.1235316494.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D2051475379%3B%2B__utmc%3D2051475379%3B%2B__utmz%3D2051475379.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D2051475379.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/iIhnzEPBivw" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/shuso-hossen-spring-2013/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">Review of "MongoDB Applied Design Patterns" by Rick Copeland</title>
    <id>http://emptysquare.net/blog/mongodb-applied-design-patterns/</id>
    <updated>2013-03-26T19:28:03Z</updated>
    <published>2013-03-26T19:28:03Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/0RncDyhVkBs/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;There's a lot of bad advice out there regarding MongoDB. &lt;a href="http://emptysquare.net/blog/building-node-applications-mongodb-backbone/"&gt;As I wrote in my last review&lt;/a&gt;, even smart sources can encourage risky methods. Soon, I hope, there will be as much good MongoDB instruction from experts outside 10gen as there is good third-party SQL instruction. For now, know that you can trust Rick Copeland.&lt;/p&gt;
&lt;p&gt;Copeland's new O'Reilly book on MongoDB complements O'Reilly's other five: the majestic &lt;em&gt;Definitive Guide&lt;/em&gt; (due for a second edition in June), &lt;em&gt;Scaling MongoDB&lt;/em&gt;, &lt;em&gt;50 Tips and Tricks&lt;/em&gt;, and the MongoDB books for Python and PHP.&lt;/p&gt;
&lt;p&gt;After you've read the &lt;em&gt;Definitive Guide&lt;/em&gt;, a good candidate for your second MongoDB book is &lt;em&gt;Applied Design Patterns&lt;/em&gt;. (Disclosure: I was paid to critique an early draft.) Copeland's intended audience has basic MongoDB competence and wants application examples that optimize either for scalability or maintainability, plus the principles to guide new designs. Copeland also assumes basic SQL knowledge, and presents most examples in contrast to conventional SQL solutions, a method I find distracting and irrelevant. He identifies some common application types (product catalog, CMS, analytics, etc.) and provides for each a schema and application logic. He goes far beyond prior works when he discusses performance, consistency guarantees, and sharding considerations for every application.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="MongoDB Applied Design Patterns" border="0" src="http://emptysquare.net/blog/media/2013/03/MongoDB-Applied-Design-Patterns.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="MongoDB-Applied-Design-Patterns.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;In Part 1, Copeland discusses the basic questions about MongoDB schemas. Right away, he identifies what makes nonrelational design different:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There is no longer a "garden path" of normalized database design to go down, and the go-to answer when faced with general schema design problems in MongoDB is "it depends".&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;MongoDB requires optimization up front, more often than SQL schema design does. (&lt;a href="http://lucumr.pocoo.org/2012/12/29/sql-is-agile/"&gt;Armin Ronacher noticed this too&lt;/a&gt; a few months ago.) Most often the question is whether to embed or to link, and what data should be normalized or denormalized. Copeland uses an extensive description of disk seek times to explain the motivations for embedding and denormalization, better than prior MongoDB schema-design materials have. &lt;/p&gt;
&lt;p&gt;Many presentations, my own included, have claimed that you can migrate your schema lazily with MongoDB: your application can start writing data in a new format, and read data in both new and old formats, while a batch job slowly migrates old data. &lt;em&gt;MongoDB Applied Design Patterns&lt;/em&gt; finally presents a complete example of lazy migration, including example code (in Python) for reading data in both formats while the migration is in progress.&lt;/p&gt;
&lt;p&gt;Without general-purpose transactions, MongoDB requires new techniques to guarantee that a series of changes is atomic: that is, to guarantee that in the long run your data either reflects all the changes or none of them. The simple approach is to put all related data in one document and use &lt;a href="http://docs.mongodb.org/manual/applications/update/#crud-update-update"&gt;update operators&lt;/a&gt; to modify all the data in one shot. If there's no way to restrict your atomic operation to one document, your next best bet is optimistic concurrency control: try to complete the operation, check if another process overwrote your changes, and if so retry them. There are a number of examples of this in the wild (&lt;a href="http://docs.mongodb.org/manual/tutorial/isolate-sequence-of-operations/"&gt;the MongoDB Manual&lt;/a&gt;, &lt;a href="http://late.am/post/2011/11/18/ensuring-write-your-own-reads-consistency-in-mongodb"&gt;Dan Crosta&lt;/a&gt;, &lt;a href="http://code.google.com/p/morphia/wiki/MongoNewsletterArticleDec2010"&gt;Scott Hernandez&lt;/a&gt;); Copeland's contribution is unusually complete, with example code for handling every case that can arise.&lt;/p&gt;
&lt;p&gt;Part 2 of the book is much longer, and covers six kinds of application in depth, both conventional (a social network) and unusual (a role-playing game). Here Copeland excels. Where he covers well-tread ground his designs are more detailed and better thought out than prior authors', and where he innovates he chooses interesting problems to solve. In the Operational Intelligence chapter he explains compound indexes clearly and correctly. He presents a complete design for an analytics application using the &lt;a href="http://docs.mongodb.org/manual/applications/aggregation/"&gt;MongoDB aggregation framework&lt;/a&gt;, and covers the interactions between aggregation, indexes, and sharding.&lt;/p&gt;
&lt;p&gt;The final example of the book is an online Zork-style game. This is less widely applicable than E-Commerce or content management, but way more fun. Copeland chooses to radically denormalize his schema: when a player enters a room, the room's entire data structure is copied into the player's document so the game can display the player's state without querying for the room again. As with the other examples, this application is considered in depth: each query is carefully indexed, and when a player picks up an item, Copeland's code prevents another player from picking it up concurrently. Most of the game's intelligence is expressed in Python code rather than in MongoDB queries. Developers using Oracle or Microsoft SQL Server tend to push all the logic and complexity into their schema, their queries, and stored procedures. With MongoDB's simpler feature-set, coders have to move more logic out of the database and into their application. If a SQL refugee hasn't yet learned this lesson, the gaming chapter will drive it home.&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=137337107&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/mongodb-applied-design-patterns/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D1646806758.1689035592.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D1646806758%3B%2B__utmc%3D1646806758%3B%2B__utmz%3D1646806758.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D1646806758.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/0RncDyhVkBs" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/mongodb-applied-design-patterns/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">Review of "Building Node Applications with MongoDB and Backbone"</title>
    <id>http://emptysquare.net/blog/building-node-applications-mongodb-backbone/</id>
    <updated>2013-03-23T16:42:26Z</updated>
    <published>2013-03-23T16:42:26Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/p44UPK522mM/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;Mike Wilson's O'Reilly book from December 2012 introduces some hip web development techniques by building a book-long example of a social networking app. Besides introducing MongoDB, Backbone, and Node, he shows the beauty and remarkable concision of &lt;a href="http://jade-lang.com/"&gt;Jade&lt;/a&gt;, &lt;a href="http://requirejs.org/"&gt;Require.js&lt;/a&gt;, and &lt;a href="http://mongoosejs.com/"&gt;Mongoose&lt;/a&gt;. He demonstrates good patterns for organizing your code in an application of substantial complexity, covers a lot of ground in few pages, and concludes with an unusually feature-complete chat-server example that weaves together all the layers of the stack. Wilson has some dangerous habits readers shouldn't emulate, but on balance his book teaches well.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Building node applications" border="0" src="http://emptysquare.net/blog/media/2013/03/building-node-applications.jpg" style="display:block; margin-left:auto; margin-right:auto;" title="building-node-applications.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;By necessity, the book jumps frequently between Node and Backbone, models and views, HTML and Javascript. It's the nature of web development that each new feature requires changes in many places, and it's hard to stay oriented. Wilson maintains a corrected version of each chapter's code &lt;a href="https://github.com/Swiftam/book-node-mongodb-backbone"&gt;on Github&lt;/a&gt;; use that instead of relying entirely on the examples in the book.&lt;/p&gt;
&lt;p&gt;I've built one large front-end Javascript application with &lt;a href="http://backbonejs.org/"&gt;Backbone&lt;/a&gt;, and I floundered at organizing it. Although Backbone is rigorous (hence the name) about separating models and views, higher-level questions are underspecified: how should the code be split among files? Whose responsibility is it to create the models and views? Wilson uses Require.js to neatly slice code into files and to declare the dependencies among them. In his example application, the Backbone router is responsible for instantiating all models and views. As the book progresses and his example application grows, the routes, models, and views remain focused and decoupled. It's a compelling design. I wish I'd known.&lt;/p&gt;
&lt;p&gt;Wilson spends an early chapter building a login system for his example app, before implementing any features. He even salts his password hashes to defend against rainbow tables. An author less secure in his convictions would fear losing his reader's attention, but Wilson insists on doing the right thing. And rightly so: readers will paste his examples and put them into production, so the examples should be complete.&lt;/p&gt;
&lt;p&gt;On the other hand, Wilson's introduction to MongoDB misses some marks. It's only 12 pages, so why did he spend two of them on MapReduce? MapReduce has always been intended for big batch processes, not web applications. MongoDB books and talks have long over-emphasized MapReduce, which should be confined to a niche. The &lt;a href="http://docs.mongodb.org/manual/applications/aggregation/"&gt;aggregation framework&lt;/a&gt;, on the other hand, is general-purpose and was released months before Wilson's book; it should have been covered instead.&lt;/p&gt;
&lt;p&gt;Wilson also shows a MongoDB pattern that risks losing updates and is needlessly slow: When a user adds a contact in his social-networking site, Wilson's code fetches the whole user document, adds the contact, and saves the whole document back:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.post(&lt;span style="color: #BA2121"&gt;'/accounts/:id/contact'&lt;/span&gt;, &lt;span style="color: #008000; font-weight: bold"&gt;function&lt;/span&gt;(req,res) {
  &lt;span style="color: #008000; font-weight: bold"&gt;var&lt;/span&gt; accountId &lt;span style="color: #666666"&gt;=&lt;/span&gt; req.params.id;
  &lt;span style="color: #008000; font-weight: bold"&gt;var&lt;/span&gt; contactId &lt;span style="color: #666666"&gt;=&lt;/span&gt; req.param(&lt;span style="color: #BA2121"&gt;'contactId'&lt;/span&gt;, &lt;span style="color: #008000; font-weight: bold"&gt;null&lt;/span&gt;);

  models.Account.findById(accountId, &lt;span style="color: #008000; font-weight: bold"&gt;function&lt;/span&gt;(account) {
    models.Account.findById(contactId, &lt;span style="color: #008000; font-weight: bold"&gt;function&lt;/span&gt;(contact) {
      models.Account.addContact(account, contact);
      account.save();
    });
  });

  &lt;span style="color: #408080; font-style: italic"&gt;// Note: Not in callback - this endpoint returns immediately and&lt;/span&gt;
  &lt;span style="color: #408080; font-style: italic"&gt;// processes in the background&lt;/span&gt;
  res.send(&lt;span style="color: #666666"&gt;200&lt;/span&gt;);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(I've edited for brevity; the &lt;a href="https://github.com/Swiftam/book-node-mongodb-backbone/blob/master/ch08/app.js#L161"&gt;whole code is on GitHub&lt;/a&gt;.) Note that if two requests are updating the same account, the first one's updates are lost. &lt;a href="http://docs.mongodb.org/manual/reference/operator/addToSet/"&gt;$addToSet&lt;/a&gt; would have solved this, and would be more efficient too.&lt;/p&gt;
&lt;p&gt;Equally worrisome is Wilson's tendency to drop errors on the floor instead of reporting them to the user, as shown at the bottom of this function. He argues "we are accepting the small but rare inconvenience in order to serve the majority of requests at an accelerated speed." This is a terrible argument for silencing errors, especially since the front-end framework needn't block the user from interacting with the UI while it waits for the server response.&lt;/p&gt;
&lt;p&gt;A book like this seems intended to show best practices, and patterns that encourage correctness. Some of the hardest patterns to learn are error-handling in Node and concurrency control in MongoDB. I wish Wilson had devoted half the attention he placed on security to these two topics.&lt;/p&gt;
&lt;p&gt;But I'm only mad at these flaws because the book they mar is a good one. As Wilson builds up his architecture piece by piece, the patterns appear both usable and elegant, and capable of staying clean as the app grows. Wilson uses &lt;a href="http://backbonejs.org/#Events"&gt;Backbone custom named events&lt;/a&gt; like "app:loggedin" or "chat:start" to coordinate his front-end code, instead of letting views directly call methods on other views. A novice Backbone user might not see the tremendous value of decoupling views this way, but take it from me&amp;#8212;it's a great idea.&lt;/p&gt;
&lt;p&gt;The book concludes with a long chat example. Chat examples with Socket.io and Node are legion&amp;#8212;indeed, obligatory&amp;#8212;but the completeness of this one, including its integration with Backbone, is a tour de force. If you plan to use either Node or Backbone this book has excellent recommendations for structuring a large app, and even if you're not building with any of the frameworks Wilson covers, his examples can inspire you to write more concise and decoupled code.&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=208500547&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/building-node-applications-mongodb-backbone/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D253300023.1583684019.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D253300023%3B%2B__utmc%3D253300023%3B%2B__utmz%3D253300023.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D253300023.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/p44UPK522mM" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/building-node-applications-mongodb-backbone/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">Slides from my PyCon lightning talk on Toro</title>
    <id>http://emptysquare.net/blog/pycon-talk-on-toro/</id>
    <updated>2013-03-16T18:48:21Z</updated>
    <published>2013-03-16T18:48:21Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/9UROWUQj3TU/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;Here's the 8 slides for my 4&amp;#189;-minute talk on &lt;a href="http://toro.readthedocs.org/"&gt;Toro&lt;/a&gt; this morning. Toro is a package I wrote last year that provides objects something like locks, events, conditions, semaphores, and queues for Tornado coroutines.&lt;/p&gt;
&lt;p&gt;&lt;iframe allowfullscreen="None" frameborder="0" height="356" marginheight="0" marginwidth="0" mozallowfullscreen="None" scrolling="no" src="http://www.slideshare.net/slideshow/embed_code/17264776" style="border:1px solid #CCC;border-width:1px 1px 0;margin-bottom:5px" webkitallowfullscreen="None" width="427"&gt; &lt;/iframe&gt; &lt;div style="margin-bottom:5px"&gt; &lt;strong&gt; &lt;a href="http://www.slideshare.net/emptysquare/toro-pycon-lightning" target="_blank" title="PyCon lightning talk on my Toro module for Tornado"&gt;PyCon lightning talk on my Toro module for Tornado&lt;/a&gt; &lt;/strong&gt; from &lt;strong&gt;&lt;a href="http://www.slideshare.net/emptysquare" target="_blank"&gt;emptysquare&lt;/a&gt;&lt;/strong&gt; &lt;/div&gt;&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=769522406&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/pycon-talk-on-toro/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D804567444.1631264164.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D804567444%3B%2B__utmc%3D804567444%3B%2B__utmz%3D804567444.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D804567444.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/9UROWUQj3TU" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/pycon-talk-on-toro/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">Plop: Python Profiler With Call Graphs</title>
    <id>http://emptysquare.net/blog/plop-python-profiler-with-call-graphs/</id>
    <updated>2013-03-11T19:37:35Z</updated>
    <published>2013-03-11T19:37:35Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/px-371jMRDo/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p&gt;Tornado's maintainer Ben Darnell released a &lt;a href="https://pypi.python.org/pypi/plop/"&gt;Python Low-Overhead Profiler&lt;/a&gt; or "Plop" last year, and I'm just now playing with it. Unlike &lt;a href="http://docs.python.org/2/library/profile.html#module-cProfile"&gt;cProfile&lt;/a&gt;, which records every function call at great cost to the running process, Plop promises that "profile collection can be turned on and off in a live process with minimal performance impact."&lt;/p&gt;
&lt;p&gt;A Plop &lt;code&gt;Collector&lt;/code&gt; samples the process's call stack periodically (every 10 milliseconds by default) until you call &lt;code&gt;Collector.stop()&lt;/code&gt;. Plop's profile viewer is a web application built on Tornado and d3.js, which uses a fun force-directed layout to display your process's call graph. You can use the demo scripts from Plop's &lt;a href="https://github.com/bdarnell/plop"&gt;repo&lt;/a&gt; to make an example profile:&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Call graph" border="0" src="http://emptysquare.net/blog/media/2013/03/call-graph.png" style="display:block; margin-left:auto; margin-right:auto;" title="call-graph.png"/&gt;&lt;/p&gt;
&lt;p&gt;Functions are shown as circles, sized according to the number of times they were executed and colored according to filename. Edges connect callers to callees. The visualization nearly freezes Firefox but runs well in Chrome.&lt;/p&gt;
&lt;p&gt;Plop isn't going to replace cProfile and RunSnakeRun, but that's not its intention. Better to think of it as a lightweight complement to the heavier machinery: Plop is nice for visualizing call graphs (which RunSnakeRun does badly) and for sampling a live process in a performance-critical environment.&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=291968507&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/plop-python-profiler-with-call-graphs/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D2121521827.1810494001.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D2121521827%3B%2B__utmc%3D2121521827%3B%2B__utmz%3D2121521827.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D2121521827.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/px-371jMRDo" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/plop-python-profiler-with-call-graphs/</feedburner:origLink></entry>
  <entry xml:base="http://emptysquare.net/blog/feed/">
    <title type="text">Review of Roman Vishniac Rediscovered</title>
    <id>http://emptysquare.net/blog/review-of-roman-vishniac-rediscovered/</id>
    <updated>2013-03-11T03:15:45Z</updated>
    <published>2013-03-11T03:15:45Z</published>
    <link href="http://feedproxy.google.com/~r/emptysquare/~3/4EASoRxWrkU/" />
    <author>
      <name>A. Jesse Jiryu Davis</name>
      <email>ajdavis@cs.oberlin.edu</email>
    </author>
    <content type="html">&lt;p style="text-align: center"&gt;&lt;img alt="Roman Vishniac, Salesmen" border="0" src="http://emptysquare.net/blog/media/2013/03/vishniac-salesmen.png" style="display:block; margin-left:auto; margin-right:auto;" title="vishniac-salesmen.png"/&gt;&lt;/p&gt;
&lt;p&gt;Today I saw the International Center of Photography's big retrospective, &lt;a href="http://vishniac.icp.org/exhibition/"&gt;"Roman Vishniac Rediscovered"&lt;/a&gt;. The show opens with Vishniac's Berlin street photography from the 1920s and 30s, in which he concentrates on form: shafts of light in a train station; a workman on a diagonal ladder amid diagonal shadows; four boys admiring a motorcycle, all dressed alike. The beauty and the visual coincidences he catches are delightful. The scene darkens as the Nazis rise to power, and the impact of the photos, unfortunately, wanes. Vishniac's photo of his daughter wearing a cute beret, standing in front of a Hitler poster, is ominous, but not particularly good.&lt;/p&gt;
&lt;p&gt;Vishniac's most prominent achievement is his photographs of Eastern European Jews in the late 1930s. The project was commissioned by an American Jewish relief fund to highlight the poverty of Jews in Eastern Europe, much in the same way (and at the same time) as the FSA commissioned Dorothea Lange and Walker Evans to photograph the Dust Bowl. ICP displays the work in fine new inkjet prints from Vishniac's negatives, and sometimes shows images Vishniac had originally edited out: Jewish women in secular dress, for example, or a prosperous-looking Jewish shop. The exhibit demonstrates how Vishniac selected his photos to accomplish a narrow view of Jewish life: poor, religious, medieval. When this world was wiped out by the Nazis a few years later, Vishniac's record of it became a twilit elegy, but the work as we've known it is not the whole scene Vishniac saw.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Roman Vishniac, Beggars" border="0" src="http://emptysquare.net/blog/media/2013/03/vishniac-beggars.png" style="display:block; margin-left:auto; margin-right:auto;" title="vishniac-beggars.png"/&gt;&lt;/p&gt;
&lt;p&gt;Propagandistic, too, are Vishniac's 1939 photographs of a Dutch "agrarian training camp" that prepared Zionist youth for emigration to Palestine. The images are posed, with clear inspiration from Socialist Realism. They're of their time: the age of statism, when individuals everywhere were subsumed in one ideology or another.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Roman Vishniac, Zionist Youth" border="0" src="http://emptysquare.net/blog/media/2013/03/vishniac-zionist-youth.png" style="display:block; margin-left:auto; margin-right:auto;" title="vishniac-zionist-youth.png"/&gt;&lt;/p&gt;
&lt;p&gt;It makes one nostalgic for the pictures made before all the polemics, when Vishniac was satisfied just to photograph stylish figures in slashing light. Unburdened by any message, these images are light, and the best in the show.&lt;/p&gt;
&lt;p style="text-align: center"&gt;&lt;img alt="Roman Vishniac, Train Station" border="0" src="http://emptysquare.net/blog/media/2013/03/vishniac-train-station.png" style="display:block; margin-left:auto; margin-right:auto;" title="vishniac-train-station.png"/&gt;&lt;/p&gt;
&lt;img src="http://www.google-analytics.com/__utm.gif?utmwv=1&amp;utmn=419017212&amp;utmsr=-&amp;utmsc=-&amp;utmul=-&amp;utmje=0&amp;utmfl=-&amp;utmdt=-&amp;utmhn=emptysquare.net&amp;utmr=-&amp;utmp=http://emptysquare.net/blog/review-of-roman-vishniac-rediscovered/rss&amp;utmac=UA-26577074-1&amp;utmcc=__utma%3D1236425227.1673562097.1369277574.1369277574.1369277574.2%3B%2B__utmb%3D1236425227%3B%2B__utmc%3D1236425227%3B%2B__utmz%3D1236425227.1369277574.2.2.utmccn%3D(direct)%7Cutmcsr%3D(direct)%7Cutmcmd%3D(none)%3B%2B__utmv%3D1236425227.-%3B" width="1px" height="1px"&gt;&lt;img src="http://feeds.feedburner.com/~r/emptysquare/~4/4EASoRxWrkU" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://emptysquare.net/blog/review-of-roman-vishniac-rediscovered/</feedburner:origLink></entry>
</feed>
