<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Labix Blog &#187; Article</title>
	<atom:link href="http://blog.labix.org/tag/article/feed" rel="self" type="application/rss+xml" />
	<link>http://blog.labix.org</link>
	<description>by Gustavo Niemeyer</description>
	<lastBuildDate>Mon, 16 Jan 2012 04:02:51 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Cut the rope, a tablet is NOT a laptop</title>
		<link>http://blog.labix.org/2011/07/19/cut-the-rope-a-tablet-is-not-a-laptop</link>
		<comments>http://blog.labix.org/2011/07/19/cut-the-rope-a-tablet-is-not-a-laptop#comments</comments>
		<pubDate>Tue, 19 Jul 2011 09:20:04 +0000</pubDate>
		<dc:creator>Gustavo Niemeyer</dc:creator>
				<category><![CDATA[Article]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[Puzzle]]></category>

		<guid isPermaLink="false">http://blog.labix.org/?p=662</guid>
		<description><![CDATA[Back in 2009 I quickly talked about the obvious revolution in computing that was rolling in the form of mobile phone as computer, and mentioned as well the fact that touch-based interfaces were going to dominate the marketplace because of &#8230; <a href="http://blog.labix.org/2011/07/19/cut-the-rope-a-tablet-is-not-a-laptop">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Back in 2009 I quickly <a href="http://blog.labix.org/2009/06/23/are-you-ready-for-the-mobile-revolution">talked about</a> the obvious revolution in computing that was rolling in the form of <i>mobile phone as computer</i>, and mentioned as well the fact that touch-based interfaces were going to dominate the marketplace because of that.</p>
<p>Move forward a couple of years, and last week I got my first tablet, running Android (a Samsung Galaxy Tab 10.1, if you&#8217;re curious).  I didn&#8217;t know exactly why I needed one, but being in the tech industry I always have that nice excuse for myself of buying things early on for learning about the experience of using them. Last night, I could clearly see this can be a real claim in some cases (in others it&#8217;s just an excuse for the wife).</p>
<p><span id="more-662"></span>After getting the tablet last week, I&#8217;ve started by experimenting with the usual stuff any person would (email, browser, etc), and then downloaded a few games to take on board a longish flight.  Some of them were pretty good.. a vertical scrolling shooter, a puzzle-solver, and so on.  On all of them, though, it took just a few minutes before the novelty of holding the screen in my hands for interacting with the game got old, and the interest went away with it.</p>
<p>This last night, though, I&#8217;ve decided to try another game from the top list, named <a href="https://www.youtube.com/watch?v=1JpdW-D6c14">Cut the Rope</a>, and this time I was immediately hooked into it.   That was certainly one of the most enjoyable gaming experiences I had in quite a while, and when going to bed I started to ponder about what was different there.</p>
<p>The game is obviously well executed, with cute drawings and sounds, and also smooth, but I think there was something else as well. In retrospect, the other games felt a lot like ports of a desktop/laptop experience.  The side scrolling game, for instance, was quite well suited for a joystick, and at least one other game had an actual joystick emulated on the screen, which is an enabler, but far from nice to be honest.</p>
<p>This one game, though, felt very well suited for a hands-based interaction: quickly drawing lines for cutting ropes, tapping on balloons to push air out, moving levers around, etc.  In some more advanced levels, it was clear that my dexterity (or lack thereof) was playing a much more important role in accomplishing the tasks than the traditional button/joystick version of it. This felt like an <i>entirely</i> novel gaming experience that just hadn&#8217;t happened yet.</p>
<p>It&#8217;s funny and ironic that I had this experience within a week from Microsoft <a href="http://www.zdnet.com/blog/mobile-news/-8216we-view-a-tablet-as-a-pc-microsoft-chooses-to-repeat-history/3267">reportedly saying</a> (<a href="http://www.ipadguys.com.au/ballmer-cracks-ipad-just-a-pc/">again!</a>) that a tablet is just another PC.  It&#8217;s not, and if they tried it out with some minimum attention they&#8217;d see why it&#8217;s so clearly not.</p>
<p>In that experience, the joystick felt familiar but at the same quite awkward to use, but using my hands naturally in an environment where that was suitable felt very pleasing. We can generalize that a bit and note a common way to relate to innovation: we first try to reuse the knowledge we have when facing a new concept, but when we understand the concept better quite often we&#8217;re able to come up with more effective and interesting ways to relate to it.</p>
<p>In the tablet vs. laptop/desktop thread, you probably won&#8217;t want to be typing long documents in a tablet, but would most likely prefer to shuffle items in an agenda with your fingers.  Also, you likely wouldn&#8217;t want to do that detailed CAD work with a fat finger in a screen, but would certainly be happy to review code or a document sitting in your backyard with the birds (no whales).</p>
<p>So, let&#8217;s please put that hammer away for a second while creating a most enjoyable touch-based experience.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.labix.org/2011/07/19/cut-the-rope-a-tablet-is-not-a-laptop/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Efficient algorithm for expanding circular buffers</title>
		<link>http://blog.labix.org/2010/12/23/efficient-algorithm-for-expanding-circular-buffers</link>
		<comments>http://blog.labix.org/2010/12/23/efficient-algorithm-for-expanding-circular-buffers#comments</comments>
		<pubDate>Thu, 23 Dec 2010 12:57:40 +0000</pubDate>
		<dc:creator>Gustavo Niemeyer</dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Article]]></category>
		<category><![CDATA[C/C++]]></category>
		<category><![CDATA[Erlang]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Haskell]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Lua]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Snippet]]></category>

		<guid isPermaLink="false">http://blog.labix.org/?p=580</guid>
		<description><![CDATA[Circular buffers are based on an algorithm well known by any developer who&#8217;s got past the &#8220;Hello world!&#8221; days. They offer a number of key characteristics with wide applicability such as constant and efficient memory use, efficient FIFO semantics, etc. &#8230; <a href="http://blog.labix.org/2010/12/23/efficient-algorithm-for-expanding-circular-buffers">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Circular buffers are based on an algorithm well known by any developer who&#8217;s got past the <i>&#8220;Hello world!&#8221;</i> days.  They offer a number of key characteristics with wide applicability such as constant and efficient memory use, efficient FIFO semantics, etc.</p>
<p>One feature which is not always desired, though, it the fact that circular buffers traditionally will either overwrite the last element, or raise an overflow error, since they are generally implemented as a buffer of <i>constant</i> size.  This is an unwanted property when one is attempting to <i>consume</i> items from the buffer and it is not an option to blindly drop items, for instance.</p>
<p>This post presents an efficient (and potentially novel) algorithm for implementing circular buffers which preserves most of the key aspects of the traditional version, while also supporting dynamic expansion when the buffer would otherwise have its oldest entry overwritten. It&#8217;s not clear if the described approach is novel or not (most of my novel ideas seem to have been written down 40 years ago), so I&#8217;ll publish it below and let you decide.</p>
<p><span id="more-580"></span><b>Traditional circular buffers</b></p>
<p>Before introducing the variant which can actually expand during use, let&#8217;s go through a quick review on traditional circular buffers, so that we can then reuse the nomenclature when extending the concept.  All the snippets provided in this post are written in Python, as a better alternative to pseudo-code, but the concepts are naturally portable to any other language.</p>
<p>So, the most basic circular buffer needs the buffer itself, its total capacity, and a position where the next write should occur.  The following snippet demonstrates the concept in practice:</p>
<pre>
buf = [None, None, None, None, None]
bufcap = len(buf)
pushi = 0   

for elem in range(7):
    buf[pushi] = elem
    pushi = (pushi + 1) % bufcap

print buf # => [5, 6, 2, 3, 4]
</pre>
<p>In the example above, the first two elements of the series (0 and 1) were overwritten once the pointer wrapped around. That&#8217;s the specific feature of circular buffers which the proposal in this post will offer an alternative for.</p>
<p>The snippet below provides a full implementation of the traditional approach, this time including both the pushing and popping logic, and raising an error when an overflow or underflow would occur.  Please note that these snippets are not necessarily idiomatic Python.  The intention is to highlight the algorithm itself.</p>
<pre>
class CircBuf(object):

    def __init__(self):
        self.buf = [None, None, None, None, None]
        self.buflen = self.pushi = self.popi = 0
        self.bufcap = len(self.buf)

    def push(self, x):
        assert self.buflen == 0 or self.pushi != self.popi, \
               "Buffer overflow!"
        self.buf[self.pushi] = x
        self.pushi = (self.pushi + 1) % self.bufcap
        self.buflen += 1

    def pop(self):
        assert self.buflen != 0, "Buffer underflow!"
        x = self.buf[self.popi]
        self.buf[self.popi] = None
        self.buflen -= 1
        self.popi = (self.popi + 1) % self.bufcap
        return x
</pre>
<p>With the basics covered, let&#8217;s look at how to extend this algorithm to support dynamic expansion in case of overflows.</p>
<p><b>Dynamically expanding a circular buffer</b></p>
<p>The approach consists in imagining that the same buffer can contain both a circular buffer area (referred to as <i>the ring area</i> from here on), and an overflow area, and that it is possible to transform a mixed buffer back into a pure circular buffer again.  To clarify what this means, some examples are presented below.  The full algorithm will be presented afterwards.</p>
<p>First, imagine that we have an empty buffer with a capacity of 5 elements as per the snippet above, and then the following operations take place:</p>
<pre>
for i in range(5):
    circbuf.push(i)

circbuf.pop() # => 0
circbuf.pop() # => 1

circbuf.push(5)
circbuf.push(6)

print circbuf.buf # => [<font style="color: blue">5, 6, 2, 3, 4</font>]
</pre>
<p>At this point we have a full buffer, and with the original implementation an additional push would raise an assertion error. To implement expansion, the algorithm will be changed so that those items will be appended at the end of the buffer.  Following the example, pushing two additional elements would behave the following way:</p>
<pre>
circbuf.push(7)
circbuf.push(8)

print circbuf.buf # => [<font style="color: blue">5, 6, 2, 3, 4,</font> <font color="red">7, 8</font>]
</pre>
<p>In that example, elements 7 and 8 are part of the overflow area, and the ring area remains with the same capacity and length of the original buffer. Let&#8217;s perform a few additional operations to see how it would behave when items are popped and pushed while the buffer is split:</p>
<pre>
circbuf.pop() # => 2
circbuf.pop() # => 3
circbuf.push(9)

print circbuf.buf # => [<font style="color: blue">5, 6,</font> None, None, <font style="color: blue">4,</font> <font style="color: red">7, 8, 9</font>]
</pre>
<p>In this case, even though there are two free slots available in the ring area, the last item pushed was still appended at the overflow area.  That&#8217;s necessary to preserve the FIFO semantics of the circular buffer, and means that the buffer may expand more than strictly necessary given the space available. In most cases this should be a reasonable trade off, and should stop happening once the circular buffer size stabilizes to reflect the production vs. consumption pressure (if you have a producer which constantly operates faster than a consumer, though, please look at the literature for plenty of advice on the problem).</p>
<p>The remaining interesting step in that sequence of events is the moment when the ring area capacity is expanded to cover the full allocated buffer again, with the previous overflow area being integrated into the ring area.  This will happen when the content of the previous partial ring area is fully consumed, as shown below:</p>
<pre>
circbuf.pop() # => 4
circbuf.pop() # => 5
circbuf.pop() # => 6
circbuf.push(10)

print circbuf.buf # => [<font style="color: blue">10,</font> None, None, None, None, <font style="color: blue">7, 8, 9</font>]
</pre>
<p>At this point, the whole buffer contains just a ring area and the overflow area is again empty, which means it becomes a traditional circular buffer.</p>
<p><b>Sample algorithm</b></p>
<p>With some simple modifications in the traditional implementation presented previously, the above semantics may be easily supported. Note how the additional properties did not introduce significant overhead. Of course, this version will incur in additional memory allocation to support the buffer expansion, bu that&#8217;s inherent to the problem being solved.</p>
<pre>
class ExpandingCircBuf(object):

    def __init__(self):
        self.buf = [None, None, None, None, None]
        self.buflen = self.ringlen = self.pushi = self.popi = 0
        self.bufcap = self.ringcap = len(self.buf)

    def push(self, x):
        if self.ringlen == self.ringcap or \
           self.ringcap != self.bufcap:
            self.buf.append(x)
            self.buflen += 1
            self.bufcap += 1
            if self.pushi == 0: # Optimization.
                self.ringlen = self.buflen
                self.ringcap = self.bufcap
        else:
            self.buf[self.pushi] = x
            self.pushi = (self.pushi + 1) % self.ringcap
            self.buflen += 1
            self.ringlen += 1

    def pop(self):
        assert self.buflen != 0, "Buffer underflow!"
        x = self.buf[self.popi]
        self.buf[self.popi] = None
        self.buflen -= 1
        self.ringlen -= 1
        if self.ringlen == 0 and self.buflen != 0:
            self.popi = self.ringcap
            self.pushi = 0
            self.ringlen = self.buflen
            self.ringcap = self.bufcap
        else:
            self.popi = (self.popi + 1) % self.ringcap
        return x
</pre>
<p>Note that the above algorithm will allocate each element in the list individually, but in sensible situations it may be better to allocate additional space for the overflow area in advance, to avoid potentially frequent reallocation.  In a situation when the rate of consumption of elements is about the same as the rate of production, for instance, there are advantages in doubling the amount of allocated memory per expansion.  Given the way in which the algorithm works, the previous ring area will be exhausted before the mixed buffer becomes circular again, so with a constant rate of production and an equivalent consumption it will effectively have its size doubled on expansion.</p>
<p><b>UPDATE:</b> Below is shown a version of the same algorithm which not only allows allocating more than one additional slot at a time during expansion, but also incorporates it in the overflow area immediately so that the allocated space is used optimally.</p>
<pre>
class ExpandingCircBuf2(object):

    def __init__(self):
        self.buf = []
        self.buflen = self.ringlen = self.pushi = self.popi = 0
        self.bufcap = self.ringcap = len(self.buf)

    def push(self, x):
        if self.ringcap != self.bufcap:
            expandbuf = (self.pushi == 0)
            expandring = False
        elif self.ringcap == self.ringlen:
            expandbuf = True
            expandring = (self.pushi == 0)
        else:
            expandbuf = False
            expandring = False

        if expandbuf:
            self.pushi = self.bufcap
            expansion = [None, None, None]
            self.buf.extend(expansion)
            self.bufcap += len(expansion)
            if expandring:
                self.ringcap = self.bufcap

        self.buf[self.pushi] = x
        self.buflen += 1
        if self.pushi < self.ringcap:
            self.ringlen += 1
        self.pushi = (self.pushi + 1) % self.bufcap

    def pop(self):
        assert self.buflen != 0, "Buffer underflow!"
        x = self.buf[self.popi]
        self.buf[self.popi] = None
        self.buflen -= 1
        self.ringlen -= 1
        if self.ringlen == 0 and self.buflen != 0:
            self.popi = self.ringcap
            self.ringlen = self.buflen
            self.ringcap = self.bufcap
        else:
            self.popi = (self.popi + 1) % self.ringcap
        return x
</pre>
<p><b>Conclusion</b></p>
<p>This blog post presented an algorithm which supports the expansion of circular buffers while preserving most of their key characteristics.  When not faced with an overflowing buffer, the algorithm should offer very similar performance characteristics to a normal circular buffer, with a few additional instructions and constant space for registers only. When faced with an overflowing buffer, the algorithm maintains the FIFO property and enables using contiguous allocated memory to maintain both the original circular buffer and the additional elements, and follows up reusing the full area as part of a new circular buffer in an attempt to find the proper size for the given use case.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.labix.org/2010/12/23/efficient-algorithm-for-expanding-circular-buffers/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Integrating Go with C: the ZooKeeper binding experience</title>
		<link>http://blog.labix.org/2010/12/10/integrating-go-with-c-the-zookeeper-binding-experience</link>
		<comments>http://blog.labix.org/2010/12/10/integrating-go-with-c-the-zookeeper-binding-experience#comments</comments>
		<pubDate>Fri, 10 Dec 2010 16:17:16 +0000</pubDate>
		<dc:creator>Gustavo Niemeyer</dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Article]]></category>
		<category><![CDATA[C/C++]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Project]]></category>
		<category><![CDATA[Snippet]]></category>

		<guid isPermaLink="false">http://blog.labix.org/?p=534</guid>
		<description><![CDATA[ZooKeeper is a clever generic coordination server for distributed systems, and is one of the core softwares which facilitate the development of Ensemble (project for automagic IaaS deployments which we push at Canonical), so it was a natural choice to &#8230; <a href="http://blog.labix.org/2010/12/10/integrating-go-with-c-the-zookeeper-binding-experience">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><a href="http://zookeeper.apache.org">ZooKeeper</a> is a clever generic coordination server for distributed systems, and is one of the core softwares which facilitate the development of Ensemble (project for automagic <a href="http://en.wikipedia.org/wiki/Cloud_computing">IaaS</a> deployments which we push at <a href="http://www.canonical.com">Canonical</a>), so it was a natural choice to experiment with.</p>
<p><a href="https://wiki.ubuntu.com/gozk">Gozk</a> is a complete binding for ZooKeeper which explores the native features of Go to facilitate the interaction with a ZooKeeper server.  To avoid reimplementing the well tested bits of the protocol in an unstable way, Gozk is built on top of the standard C ZooKeeper library.</p>
<p>The experience of integrating ZooKeeper with Go was certainly valuable on itself, and worked as a nice way to learn the details of integrating the Go language with a C library. If you&#8217;re interested in learning a bit about Go, ZooKeeper, or other details related to the creation of bindings and asynchronous programming, please fasten the seatbelt now.</p>
<p><span id="more-534"></span><b>Basics of C wrapping in Go</b></p>
<p>Creating the binding on itself was a pretty interesting experiment already.  I have worked on the creation of quite a few bindings and language bridges before, and must say I was pleasantly surprised with the experience of creating the Go binding.  With <i>Cgo</i>, the name given to the &#8220;<i>foreign function interface</i>&#8221; mechanism for C integration, one basically declares a special import statement which causes a pre-processor to look at the comment preceding it.  Something similar to this:</p>
<pre>
// #include &lt;zookeeper.h&gt;
import "C"
</pre>
<p>The comment doesn&#8217;t have to be restricted to a single line, or to <i>#include</i> statements even.  The C code contained in the comment will be transparently inserted into a helper C file which is compiled and linked with the final object file, and the given snippet will also be parsed and inclusions processed.  In the Go side, that &#8220;C&#8221; import is simulated as if it were a normal Go package so that the C functions, types, and values are all directly accessible.</p>
<p>As an example, a C function with this prototype:</p>
<pre>
int zoo_wexists(zhandle_t *zh, const char *path, watcher_fn watcher,
                void *context, struct Stat *stat);
</pre>
<p>In Go may be used as:</p>
<pre>
cstat := C.struct_Stat{}
rc, cerr := C.zoo_wexists(zk.handle, cpath, nil, nil, &#038;cstat)
</pre>
<p>When the C function is used in a context where two result values are requested, as done above, Cgo will save the well known <i>errno</i> variable after the function has finished executing and will return it wrapped into an <i>os.Errno</i> value.</p>
<p>Also, note how the C struct is defined in a way that can be passed straight to the C function.  Interestingly, the allocation of the memory backing the structure is going to be performed and tracked by the Go runtime, and will be garbage collected appropriately once no more references exist <i>within the Go runtime</i>. This fact has to be kept in mind since the application will crash if a value allocated normally within Go is saved with a foreign C function and maintained after all the Go references are gone.  The alternative in these cases is to call the usual C functions to get hold of memory for the involved values.  That memory won&#8217;t be touched by the garbage collector, and, of course, must be explicitly freed when no longer necessary.  Here is a simple example showing explicit allocation:</p>
<pre>
cbuffer := (*C.char)(C.malloc(bufferSize))
defer C.free(unsafe.Pointer(cbuffer))
</pre>
<p>Note the use of the <i>defer</i> statement above. Even when dealing with foreign functionality, it comes in handy. The above call will ensure that the buffer is deallocated right before the current function returns, for instance, so it&#8217;s a nice way to ensure no leaks happen, even if in the future the function suddenly gets a new exit point which didn&#8217;t consider the allocation of resources.</p>
<p>In terms of typing, Go is more strict than C, and Cgo-based logic will also ensure that the types returned and passed into the foreign C functions are correctly typed, in the same way done for the native types.  Note above, for instance, how the call to the <i>free()</i> function has to explicitly convert the value into an <i>unsafe.Pointer</i>, even though in C no casting would be necessary to pass a pointer into a <i>void *</i> parameter.</p>
<p>The <i>unsafe.Pointer</i> is in fact a very special type within Go. Using it, one can convert any pointer type into any other pointer type in an unsafe way (thus the package name), and also back and forth into a <i>uintptr</i> value with the address of the memory referenced by the pointer.  For every other type conversion, Go will ensure at compilation time that doing the conversion at runtime is a safe operation.</p>
<p>With all of these resources, including the ability to use common Go syntax and functionality even when dealing with foreign types, values, and function calls, the integration task turns out to be quite a pleasant experience.  That said, some of the things may still require some good thinking to get right, as we&#8217;ll see shortly.</p>
<p><b>Watch callbacks and channels</b></p>
<p>One of the most interesting (and slightly tricky) aspects of mapping the ZooKeeper concepts into Go was the &#8220;watch&#8221; functionality.  ZooKeeper allows one to attach a &#8220;watch&#8221; to a node so that the server will report back when changes happen to the given node.  In the C library, this functionality is exposed via a callback function which is executed once the monitored node aspect is modified.</p>
<p>It would certainly be possible to offer this functionality in Go using a similar mechanism, but <a href="http://golang.org/doc/go_spec.html#Channel_types">Go channels</a> provide a number of advantages for that kind of asynchronous notification: waiting for multiple events via the <a href="http://golang.org/doc/go_spec.html#Select_statements">select statement</a>, synchronous blocking until the event happens, testing if the event is already available, etc.</p>
<p>The tricky bit, though, isn&#8217;t the use of channels.  That part is quite simple.  The tricky detail is that the C callback function execution happens in a C thread started by the ZooKeeper library, and happens asynchronously, while the Go application is doing its business elsewhere.  Right now, there&#8217;s no straightforward way to transfer the execution of this asynchronous C function back into the Go land.  The solution for this problem was found with some help from the folks at the <a href="">golang-nuts</a> mailing list, and luckily it&#8217;s not that hard to support or understand.  That said, this is a good opportunity to get some coffee or your preferred focus-enhancing drink.</p>
<p>The solution works like this: when the ZooKeeper C library gets a watch notification, it executes a C callback function which is inside a Gozk helper file. Rather than transferring control to Go right away, this C function simply appends data about the event onto a queue, and signals a pthread condition variable to notify that an event is available.  Then, on the Go side, once the first ZooKeeper connection is initialized, a new goroutine is fired and loops waiting for events to be available.  The interesting detail about this loop, is that it blocks <i>within a foreign C function</i> waiting for an event to be available, through the signaling of the shared pthread condition variable.  In the Go side, that&#8217;s how the call looks like, just to give a more practical feeling:</p>
<pre>
// This will block until there's a watch available.
data := C.wait_for_watch()
</pre>
<p>Then, on the C side, here is the function definition:</p>
<pre>
watch_data *wait_for_watch() {
    watch_data *data = NULL;
    pthread_mutex_lock(&#038;watch_mutex);
    if (first_watch == NULL)
        pthread_cond_wait(&#038;watch_available, &#038;watch_mutex);
    data = first_watch;
    first_watch = first_watch->next;
    pthread_mutex_unlock(&#038;watch_mutex);
    return data;
}
</pre>
<p>As you can see, not really a big deal.  When that kind of blocking occurs inside a foreign C function, the Go runtime will correctly continue the execution of other goroutines within other operating system threads.</p>
<p>The result of this mechanism is a nice to use interface based on channels, which may be explored in different ways depending on the application needs.  Here is a simple example blocking on the event synchronously, for instance:</p>
<pre>
stat, watch, err := zk.ExistsW("/some/path")
if stat == nil &#038;&#038; err == nil {
    event := <-watch
    // Use event ...
}
</pre>
<p><b>Concluding</b></p>
<p>Those were some of the interesting aspects of implementing the ZooKeeper binding.  I would like to speak about some additional details, but this post is rather long already, so I'll keep that for a future opportunity.  The code is available under the LGPL, so if you're curious about some other aspect, or would like to use ZooKeeper with Go, please move on and <a href="https://wiki.ubuntu.com/gozk">check it out</a>!</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.labix.org/2010/12/10/integrating-go-with-c-the-zookeeper-binding-experience/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Interfaces and the design of software</title>
		<link>http://blog.labix.org/2010/11/09/interfaces-and-the-design-of-software</link>
		<comments>http://blog.labix.org/2010/11/09/interfaces-and-the-design-of-software#comments</comments>
		<pubDate>Tue, 09 Nov 2010 18:30:29 +0000</pubDate>
		<dc:creator>Gustavo Niemeyer</dc:creator>
				<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Article]]></category>
		<category><![CDATA[Test]]></category>

		<guid isPermaLink="false">http://blog.labix.org/?p=450</guid>
		<description><![CDATA[A while ago Martin Pool made a very interesting post on the design of interfaces, inspired by a talk from Rusty Russel from 2003. Besides the interesting scale of interface quality explained there, this is a very insightful comment, often &#8230; <a href="http://blog.labix.org/2010/11/09/interfaces-and-the-design-of-software">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>A while ago Martin Pool made a <a href="http://sourcefrog.net/weblog/software/aesthetics/interface-levels.html">very interesting post</a> on the design of interfaces, inspired by a <a href="http://www.ozlabs.com/~rusty/ols-2003-keynote/ols-keynote-2003.html">talk from Rusty Russel</a> from 2003.</p>
<p>Besides the interesting scale of interface quality explained there, this is a very insightful comment, often overlooked:</p>
<p><span id="more-450"></span><br />
<blockquote>
Once the code gets too big for one person, it&#8217;s all about damage control. Interfaces make damage control possible&#8230; except when the interfaces themselves are the problem.
</p></blockquote>
<p>Designing a system as a group of people requires splitting tasks up among team members, and/or the community, and perhaps even separate teams. Interfaces are the touch points of that splitting, and is what represents the functionality offered within the module/library/file/command/service/whatever.  Too often, people spend a long time working on the implementation details, thinking really deep about how to obtain the desired behavior, and forget to define clearly what is the <i>interface</i> to that behavior.</p>
<p>Having good interfaces is a key aspect of software development, and getting it correctly offers a number of important benefits:</p>
<p><b>Good encapsulation</b></p>
<p>Having good encapsulation is pretty much a synonym of having good interfaces. Too often, though, people focus on the encapsulation of the small pieces (the functions, the classes, etc), and forget about the encapsulation of the larger blocks (the libraries, modules, packages, commands, etc).</p>
<p>Also, in my experience trying to encourage good architectures, I have found that stating <i>&#8220;We need good encapsulation!&#8221;</i> gives developers no tangible line of action.  It reminds me of a parent telling the child <i>&#8220;You should be responsible!&#8221;</i>.  Sure, encapsulation and responsibility both sound great, but.. what does that <i>really mean</i>?</p>
<p>When inviting developers to think about the <i>interfaces</i> of the system parts they are responsible for, encapsulation becomes a natural outcome. It&#8217;s clear that there must be a line drawn between that part of the system and the rest, and the shape of this line must be considered while (or even better, <i>before</i>) the behavior is implemented.</p>
<p>Given well designed interfaces, the additional requirement of only using other parts of the system through their public interfaces seals the achievement of good encapsulation. Ideally, this barrier would be a natural property of the language used to develop the system (see the interface quality scale in <a href="http://sourcefrog.net/weblog/software/aesthetics/interface-levels.html">Martin&#8217;s post</a>). In other cases, this must be achieved through conventions, agreements, and good documentation.</p>
<p><b>Improved scope and communication</b></p>
<p>By inviting developers to think about the <i>interfaces</i> of the parts they are responsible for, one is basically encouraging the consideration of the interaction between those pieces and the rest of the system.  This process gives an interesting perspective, both in terms of the external expectations (what do I need to offer other people?), as well as the internal goals (what do I need to implement for satisfying what other people need?).</p>
<p>Besides helping people to figure the scope and goal of the piece being developed, this will also give a nice structure to some of the communication which must inevitably happen to integrate correctly the separate parts of the system being developed.</p>
<p><b>Improved testing and experimentation</b></p>
<p>If an interface is well designed and defined, and encapsulates well part of the functionality of the system, it improves significantly the testing and experimentation related to that part of the system.  Again, this has an effect internally and externally to the interface.</p>
<p>Internally in the sense that there&#8217;s a clear boundary between the part in development and the rest of the system, and thus it should be easier to verify that the bits which compose it are working according to plan without dragging the whole system together, and also to verify that the interface itself is behaving as intended (and hopefully as documented).</p>
<p>Externally in the sense that, given that there&#8217;s agreement regarding what is the public interface to the part being considered, one may easily provide a test double (a fake, or dummy, or mock) to simulate that part of the system.  This is well known to be useful in a number of ways:</p>
<ul>
<li>Dependent work may be run in parallel by different people</li>
<li>Real implementation backing the given interface may be postponed, until the idea is proven useful, and the interface feels suitable</li>
<li>External systems which would be hard to run locally may be simulated so that tests run fast and cheap, even without network connections</li>
<li>Faults may be injected in the system via the test doubles to verify behavior in hostile conditions</li>
</ul>
<p>and so on.</p>
<p><b>Quality isolation</b></p>
<p>This point is also my understanding of what Rusty refers to as <i>damage control</i> in his talk.  This property is very useful when designing a system, but even then it&#8217;s often missed when discussing interfaces and encapsulation.</p>
<p>If there&#8217;s a well defined interface to a piece of functionality in the system, and that interface was carefully considered to cover the needs of the system, the implementation of that interface may not start as the most beautiful, or most scalable, or even most reliable piece of software. As any developer responsible for a successful startup will happily point out, a half-baked implementation is often good enough to get things going, prove the concept, and extend the project runway.</p>
<p>Good interfaces play an important role in this kind of situation.  They are, in this sense, a way to be better prepared for success (or, for failure, <a href="http://twitter.com">depending on the perspective</a>).  If the interface implementation suddenly becomes an issue for whatever reason, the implementation itself may be replaced by something which better suits the current reality, while preserving the interaction with the rest of the system.</p>
<p>Of course, it&#8217;s still very hard to predict future system behavior when facing a completely different reality.  Changing the scale requirements for the system a few orders of magnitude, for instance, may easily break existing assumptions, and interfaces designed around these assumptions. Still, even if good interfaces won&#8217;t be enough to avoid modifications in the architecture and integration points in many cases, they will certainly help framing the conversations which will take place when this happens and new interfaces must be developed.</p>
<p><b>Conclusion</b></p>
<p>When developing non-trivial software products, there&#8217;s no other way but to split out the problem solving in several layers and components.  Looking at the points where these layers and components touch each other is a very useful and natural way to organize conversations and structure work which must take place to push the product forward.</p>
<p>It&#8217;s quite revealing to look at the points above, and note that it&#8217;s not simply the existence of interfaces themselves which presents the advantages described, but the process which they encourage around them.  Software architecture is essentially about people.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.labix.org/2010/11/09/interfaces-and-the-design-of-software/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Recovering a bootable EBS image</title>
		<link>http://blog.labix.org/2010/03/09/recovering-a-bootable-ebs-image</link>
		<comments>http://blog.labix.org/2010/03/09/recovering-a-bootable-ebs-image#comments</comments>
		<pubDate>Tue, 09 Mar 2010 21:45:43 +0000</pubDate>
		<dc:creator>Gustavo Niemeyer</dc:creator>
				<category><![CDATA[Article]]></category>
		<category><![CDATA[Cloud]]></category>

		<guid isPermaLink="false">http://blog.labix.org/?p=250</guid>
		<description><![CDATA[Scott Moser has just announced this week that the new Ubuntu images which boot out of an EBS-based root filesystem in EC2, and thus will persist across reboots, are available for testing. As usual with something that just left the &#8230; <a href="http://blog.labix.org/2010/03/09/recovering-a-bootable-ebs-image">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Scott Moser has <a href="https://lists.ubuntu.com/archives/ubuntu-cloud/2010-March/000155.html">just announced</a> this week that the new Ubuntu images which boot out of an EBS-based root filesystem in EC2, and thus will persist across reboots, are available for testing.</p>
<p>As usual with something that just left the oven and is explicitly labeled <i>for testing purposes</i>, there was a minor bug in the first iteration of images which was even mentioned in the announcement itself.  The bug, if not worked around as specified in the announcement, will prevent the image from rebooting.</p>
<p>Having an bootable EBS image which can&#8217;t reboot is a quite interesting (and ironic) problem.  You have an image which persists, but suddenly you have no way to see what is inside the image anymore because you can&#8217;t boot it.  Naturally, even if the said bug didn&#8217;t exist in the first place, it&#8217;s fairly easy to get into such a situation accidentally if you&#8217;re fiddling with the image configuration.</p>
<p>So, in this post we&#8217;ll see how to recover from a situation where a bootable EBS image can&#8217;t boot.</p>
<p><span id="more-250"></span></p>
<p><b>Getting started</b></p>
<p>To start this up, we&#8217;ll boot one of the EBS images which Scott mentioned in his announcement: ami-8bec03e2.  As we see in the output of <i>ec2-describe-images</i>, this is an EBS-based image for <i>i386</i>:</p>
<blockquote><p>
% ec2-describe-images ami-8bec03e2<br />
IMAGE	ami-8bec03e2	099720109477/ebs/ubuntu-images-testing/ubuntu-lucid-daily-i386-server-20100305	099720109477	available	public		i386	machine	aki-3fdb3756			ebs<br />
BLOCKDEVICEMAPPING	/dev/sda1		snap-f1efd098	15
</p></blockquote>
<p>Let&#8217;s run this image.  Remember to replace the value passed in the <i>-k</i> command line option with your own key pair name.</p>
<blockquote><p>
% ec2-run-instances -k gsg-keypair ami-8bec03e2<br />
RESERVATION	r-9e4615f6	626886203892	default<br />
INSTANCE	i-e3e33a88	ami-8bec03e2			pending	gsg-keypair	0		m1.small	2010-03-09T20:04:12+0000	us-east-1c	aki-3fdb3756			monitoring-disabled			ebs
</p></blockquote>
<p>There we go.  We got an instance allocated in the availability zone us-east-1c.  It&#8217;s important to keep track of this information, since EBS volumes are zone-specific.</p>
<p>As part of the above command, we must have been allocated an EBS volume automatically, and it should be attached to the instance we just started.  We can investigate it with the <i>ec2-describe-volumes</i> command:</p>
<blockquote><p>
% ec2-describe-volumes<br />
VOLUME	vol-edca1684	15	snap-f1efd098	us-east-1c	in-use	2010-03-09T20:04:20+0000<br />
ATTACHMENT	vol-edca1684	i-e3e33a88	/dev/sda1	attached	2010-03-09T20:04:24+0000
</p></blockquote>
<p>Now, we&#8217;ll get into the running instance and do some arbitrary modifications, just as a way to demonstrate that the data we don&#8217;t want to lose actually survives the recovering operation. Note that the domain name is obtained with the <i>ec2-describe-instances</i> command.</p>
<blockquote><p>
% ssh -i ~/.ssh/id_dsa_gsg-keypair ubuntu@ec2-184-73-51-147.compute-1.amazonaws.com<br />
(&#8230;)</p>
<p>ubuntu@domU-12-31-39-0E-A0-03:~$ echo &#8220;Important data&#8221; > important-data<br />
ubuntu@domU-12-31-39-0E-A0-03:~$ ls -l important-data<br />
-rw-r&#8211;r&#8211; 1 ubuntu ubuntu 15 Mar  9 20:15 important-data</p>
<p>ubuntu@domU-12-31-39-0E-A0-03:~$ sudo reboot<br />
Broadcast message from ubuntu@domU-12-31-39-0E-A0-03<br />
	(/dev/pts/0) at 20:18 &#8230;<br />
The system is going down for reboot NOW!
</p></blockquote>
<p>Note that we didn&#8217;t actually fix the problem reported by Scott, so our machine won&#8217;t really reboot.  If we wait a while, we can even see that the problem is exactly what was reported in the announcement (note it really takes a bit for the output to be synced up):</p>
<blockquote><p>
% ec2-get-console-output i-e3e33a88 | tail -4<br />
mount: special device ephemeral0 does not exist<br />
mountall: mount /mnt [294] terminated with status 32<br />
mountall: Filesystem could not be mounted: /mnt
</p></blockquote>
<p>Alright, now what?  Machine is dead.. and can&#8217;t reboot.  How do we get to our important data?</p>
<p><b>Fixing the problem</b></p>
<p>The first thing we do is to <b>stop</b> the instance.  Do <i>not</i> terminate it, or you&#8217;ll lose the EBS volume!  After stopping it, we&#8217;ll detach the EBS volume that was being used as the root filesystem, so that we can attach somewhere else.</p>
<blockquote><p>
% ec2-stop-instances i-e3e33a88<br />
INSTANCE	i-e3e33a88	running	stopping</p>
<p>% ec2-detach-volume vol-edca1684<br />
ATTACHMENT	vol-edca1684	i-e3e33a88	/dev/sda1	detaching	2010-03-09T20:04:22+0000
</p></blockquote>
<p>Now, we need to attach this volume in an image which actually boots, so that we can fix it.  For this experiment, we&#8217;ll pick one of the daily Lucid images, but we could use any other working image really.  Just remind that the image must be running in the same availability zone as our previous instance, since the EBS volume won&#8217;t be accessible otherwise.</p>
<blockquote><p>
% ec2-run-instances -k gsg-keypair -z us-east-1c ami-b5f619dc<br />
RESERVATION	r-967427fe	626886203892	default<br />
INSTANCE	i-fd08d196	ami-b5f619dc			pending	gsg-keypair	0		m1.small	2010-03-09T21:10:11+0000	us-east-1c	aki-3fdb3756			monitoring-disabled			instance-store		</p>
<p>% ec2-attach-volume vol-edca1684 -i i-fd08d196 -d /dev/sdh1<br />
ATTACHMENT	vol-edca1684	i-fd08d196	/dev/sdh1	attaching	2010-03-09T21:10:51+0000
</p></blockquote>
<p>With the instance running and the EBS root device attached with an alternative device name, we can then login to fix the original problem which prevented the image from booting correctly.  In our case, we&#8217;ll simply do what Scott suggested in the announcement.</p>
<blockquote><p>
% ssh -i ~/.ssh/id_dsa_gsg-keypair ubuntu@ec2-204-236-194-196.compute-1.amazonaws.com<br />
(&#8230;)<br />
$ mkdir ebs-root<br />
$ sudo mount /dev/sdh1 ebs-root<br />
$ sudo sed -i &#8216;s/^ephemeral0/#ephemeral0/&#8217; ebs-root/etc/fstab<br />
$ sudo umount ebs-root<br />
$ logout<br />
Connection to ec2-204-236-194-196.compute-1.amazonaws.com closed.
</p></blockquote>
<p>Done!  Our EBS volume is now correct, and it should boot alright.  We&#8217;ll detach the volume from the temporary instance we created, and will reattach it back to the old bootable EBS instance which is stopped.  Note that we won&#8217;t yet terminate the temporary instance, because we may need it in case something else is still wrong, and we are already paying to use it for the hour anyway.  We just have to remind ourselves to terminate it once we&#8217;re fully done.</p>
<blockquote><p>
% ec2-detach-volume vol-edca1684<br />
ATTACHMENT	vol-edca1684	i-fd08d196	/dev/sdh1	detaching	2010-03-09T21:10:51+0000</p>
<p>% ec2-attach-volume vol-edca1684 -i i-e3e33a88 -d /dev/sda1<br />
ATTACHMENT	vol-edca1684	i-e3e33a88	/dev/sda1	attaching	2010-03-09T21:24:55+0000</p>
<p>% ec2-describe-volumes vol-edca1684<br />
VOLUME	vol-edca1684	15	snap-f1efd098	us-east-1c	in-use	2010-03-09T20:04:20+0000<br />
ATTACHMENT	vol-edca1684	i-e3e33a88	/dev/sda1	attached	2010-03-09T21:24:55+0000
</p></blockquote>
<p>Okay!  It should all be good now.  It&#8217;s time to restart our instance, and see if it is working.  Note that since you stopped and started the instance, the public domain name most probably has changed, and thus we need to find it out again with <i>ec2-describe-instances</i> once the instance is running.</p>
<blockquote><p>
% ec2-start-instances i-e3e33a88<br />
INSTANCE	i-e3e33a88	stopped	pending</p>
<p>% ec2-describe-instances i-e3e33a88<br />
RESERVATION	r-9e4615f6	626886203892	default<br />
INSTANCE	i-e3e33a88	ami-8bec03e2	ec2-184-73-72-214.compute-1.amazonaws.com	domU-12-31-39-03-B8-21.compute-1.internal	running	gsg-keypair	0		m1.small	2010-03-09T21:28:43+0000	us-east-1c	aki-3fdb3756			monitoring-disabled	184.73.72.214	10.249.187.207			ebs<br />
BLOCKDEVICE	/dev/sda1	vol-edca1684	2010-03-09T21:24:55.000Z	</p>
<p>% ssh -i ~/.ssh/id_dsa_gsg-keypair ubuntu@ec2-184-73-72-214.compute-1.amazonaws.com<br />
(&#8230;)<br />
$ cat important-data<br />
Important data</p>
<p>$ logout<br />
Connection to ec2-184-73-72-214.compute-1.amazonaws.com closed.
</p></blockquote>
<p>It worked, and our important data is still there!</p>
<p>Don&#8217;t forget to kill the temporary instance you&#8217;ve used to fix it after you&#8217;re comfortable with the result:</p>
<blockquote><p>
% ec2-terminate-instances i-fd08d196<br />
INSTANCE	i-fd08d196	running	shutting-down
</p></blockquote>
<p><b>Conclusion</b></p>
<p>Concluding, in this post we have seen how to fix a bootable EBS machine which can&#8217;t actually boot.  The technique consists of detaching the volume from the stopped instance, attaching it to a temporary instance, fixing the image, and then reattaching it back to the original image.  This back and forth of EBS volumes is quite useful in many circumstances, so keep it in your tool belt.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.labix.org/2010/03/09/recovering-a-bootable-ebs-image/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>geohash.org is public!</title>
		<link>http://blog.labix.org/2008/02/26/geohashorg-is-public</link>
		<comments>http://blog.labix.org/2008/02/26/geohashorg-is-public#comments</comments>
		<pubDate>Wed, 27 Feb 2008 00:11:38 +0000</pubDate>
		<dc:creator>Gustavo Niemeyer</dc:creator>
				<category><![CDATA[Article]]></category>
		<category><![CDATA[GPS]]></category>
		<category><![CDATA[Project]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blog.labix.org/2008/02/26/geohashorg-is-public/</guid>
		<description><![CDATA[After about one year writing this service in my spare time, it&#8217;s finally out. geohash.org offers short URLs which encode a latitude/longitude pair, so that referencing them in emails, forums, and websites is more convenient. Geohashes offer properties like arbitrary &#8230; <a href="http://blog.labix.org/2008/02/26/geohashorg-is-public">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>After about one year writing this service in my spare time, it&#8217;s finally out.</p>
<p><a href="http://geohash.org">geohash.org</a> offers short URLs which encode a latitude/longitude pair, so that referencing them in emails, forums, and websites is more convenient.</p>
<p>Geohashes offer properties like arbitrary precision, similar prefixes for nearby positions, and the possibility of gradually removing characters from the end of the code to reduce its size (and gradually lose precision).  I&#8217;ve put the algorithm created in the <b>public domain</b>.  Some details may be seen in the <a href="http://en.wikipedia.org/wiki/Geohash">Wikipedia article</a> about it (hopefully that&#8217;ll help establishing prior art, and prevent Microsoft from <a href="http://www.freepatentsonline.com/20050023524.html">patenting it</a>).</p>
<p>To obtain the Geohash, the user provides latitude and longitude coordinates in a single input box (most commonly used formats for latitude and longitude pairs are accepted), and performs the request.</p>
<p>Besides showing the latitude and longitude corresponding to the given Geohash, users who navigate to a Geohash at geohash.org are also presented with an embedded map, and may download a GPX file, or transfer the waypoint directly to certain GPS receivers. Links are also provided to external sites that may provide further details around the specified location.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.labix.org/2008/02/26/geohashorg-is-public/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>APT-RPM article now in portuguese</title>
		<link>http://blog.labix.org/2004/03/11/apt-rpm-article-now-in-portuguese</link>
		<comments>http://blog.labix.org/2004/03/11/apt-rpm-article-now-in-portuguese#comments</comments>
		<pubDate>Thu, 11 Mar 2004 06:45:00 +0000</pubDate>
		<dc:creator>Gustavo Niemeyer</dc:creator>
				<category><![CDATA[Article]]></category>

		<guid isPermaLink="false">http://blog.labix.org/2004/03/11/apt-rpm-article-now-in-portuguese/</guid>
		<description><![CDATA[I&#8217;ve translated to portuguese (pt_BR) the APT-RPM article I wrote for LWN. It&#8217;s available in printed media on Revista do Linux #50, and also on the LinuxIT site.]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve translated to portuguese (pt_BR) the <a href="http://lwn.net/Articles/60650/">APT-RPM article</a> I wrote for <a href="http://lwn.net">LWN</a>. It&#8217;s available in printed media on <a href="http://www.revistadolinux.com.br">Revista do Linux</a> #50, and also on the <a href="http://www.linuxit.com.br/modules.php?name=Sections&amp;op=viewarticle&amp;artid=422">LinuxIT</a> site.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.labix.org/2004/03/11/apt-rpm-article-now-in-portuguese/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>APT-RPM Article</title>
		<link>http://blog.labix.org/2003/12/14/apt-rpm-article</link>
		<comments>http://blog.labix.org/2003/12/14/apt-rpm-article#comments</comments>
		<pubDate>Sun, 14 Dec 2003 06:25:00 +0000</pubDate>
		<dc:creator>Gustavo Niemeyer</dc:creator>
				<category><![CDATA[Article]]></category>
		<category><![CDATA[Project]]></category>

		<guid isPermaLink="false">http://blog.labix.org/2003/12/14/apt-rpm-article/</guid>
		<description><![CDATA[I&#8217;ve written an article about APT-RPM to LWN, exposing features recently introduced in the software. Check it out!]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve written an <a href="http://lwn.net/Articles/60650/">article</a> about <a href="https://moin.conectiva.com.br/AptRpm">APT-RPM</a> to LWN, exposing features recently introduced in the software. Check it out!</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.labix.org/2003/12/14/apt-rpm-article/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

