<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://blog.telios.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.telios.dev/" rel="alternate" type="text/html" /><updated>2025-04-30T05:31:57+00:00</updated><id>https://blog.telios.dev/feed.xml</id><title type="html">Telios I Project</title><subtitle>Some scribbles and thoughts from my desk.  Don&apos;t think too much about it.</subtitle><entry><title type="html">Memory Safety, or Why mmap Is Unsafe</title><link href="https://blog.telios.dev/blog/memory-safety.html" rel="alternate" type="text/html" title="Memory Safety, or Why mmap Is Unsafe" /><published>2025-04-28T00:00:00+00:00</published><updated>2025-04-28T00:00:00+00:00</updated><id>https://blog.telios.dev/blog/memory-safety</id><content type="html" xml:base="https://blog.telios.dev/blog/memory-safety.html"><![CDATA[<p>I’ve recently been diving into unsafe rust, and I’ve been fascinated with how
things interact. One of the most interesting aspects, to me, are memory safety,
and ownership.</p>

<p>Now, I’m going to make a major disclaimer here: I am by no means an expert in
Rust, or memory safety, or even low-level programming. While I have been writing
Rust for a while, I haven’t delved into the unsafety aspects of the language.
This post will be an attempt to explain <em>why</em> I haven’t.</p>

<h2 id="the-star-of-the-show-mmap">The Star of the Show: mmap</h2>

<p>Before I go too far into Rust and memory unsafety, I first want to talk about
the use of the <a href="https://man7.org/linux/man-pages/man2/mmap.2.html"><code class="language-plaintext highlighter-rouge">mmap(2)</code></a> syscall. I’m going to copy the signature verbatum,
and describe some of its uses, before we move forward. In case you’re alaready
familiar with <code class="language-plaintext highlighter-rouge">mmap</code>, feel free to skip ahead.</p>

<p><code class="language-plaintext highlighter-rouge">mmap(2)</code> (and its corresponding <code class="language-plaintext highlighter-rouge">munmap(2)</code> syscall) are used to map a file or
device into memory.</p>

<div class="language-plaintext highlighter-rouge"><table class="rouge-line-table"><tbody><tr id="line-1" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>1</pre></td><td class="rouge-code"><pre>void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
</pre></td></tr><tr id="line-2" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>2</pre></td><td class="rouge-code"><pre>int munmap(void *addr, size_t length);
</pre></td></tr></tbody></table></div>

<p>At its core, <code class="language-plaintext highlighter-rouge">mmap</code> maps the file described by <code class="language-plaintext highlighter-rouge">fd</code> from the offset in <code class="language-plaintext highlighter-rouge">offset</code>
at the address <code class="language-plaintext highlighter-rouge">addr</code>, valid for <code class="language-plaintext highlighter-rouge">length</code> bytes. This allows us to read from
and write to the file described by <code class="language-plaintext highlighter-rouge">fd</code> as-if it were just in memory. (The
file descriptor can be freely closed after the mapping is created.) If <code class="language-plaintext highlighter-rouge">addr</code>
is null, the kernel will choose an address for us (and return it). If it errors,
it’ll return a special value (not <code class="language-plaintext highlighter-rouge">NULL</code>), and set <code class="language-plaintext highlighter-rouge">errno</code>.</p>

<p>The <code class="language-plaintext highlighter-rouge">mmap</code> call supports many flags in <code class="language-plaintext highlighter-rouge">flags</code>, but there are three that we’re
really interested in:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">MAP_SHARED</code>/<code class="language-plaintext highlighter-rouge">MAP_SHARED_VALIDATE</code>: These flags allow us to share the mapped
memory with other processes. <code class="language-plaintext highlighter-rouge">MAP_SHARED</code> allows us to share the memory with
other processes, while <code class="language-plaintext highlighter-rouge">MAP_SHARED_VALIDATE</code> validates all of the other flags
that we pass. (<code class="language-plaintext highlighter-rouge">MAP_SHARED</code> just ignores invalid flags.)</li>
  <li><code class="language-plaintext highlighter-rouge">MAP_PRIVATE</code>: This flag creates “a private copy-on-write mapping.” Updates
to the mapping do not carry over to the file, or to other processes; they are
private to the process. However, according to the manual, “it is unspecified
whether changes made to the file after the <code class="language-plaintext highlighter-rouge">mmap()</code> call are visible in the
mapped region.</li>
  <li><code class="language-plaintext highlighter-rouge">MAP_ANON</code>/<code class="language-plaintext highlighter-rouge">MAP_ANONYMOUS</code>: The mapping is not backed by any file. <code class="language-plaintext highlighter-rouge">fd</code> is
ignored, but for compatibility reasons, it’s suggested to pass <code class="language-plaintext highlighter-rouge">-1</code>. (The
<code class="language-plaintext highlighter-rouge">offset</code> argument should also be set to <code class="language-plaintext highlighter-rouge">0</code>.)</li>
</ul>

<h3 id="creating-an-anonymous-private-mapping">Creating an Anonymous Private Mapping</h3>

<p>We’ll start in C, so that we can translate it over to Rust later.  For this
example, we’ll create an anonymous private mapping. This is useful for
allocating memory - we create a request to the operating system for a chunk of
memory that we can use for our own purposes. Many allocators (e.g., <code class="language-plaintext highlighter-rouge">malloc</code>)
sometimes use <code class="language-plaintext highlighter-rouge">mmap</code> to allocate memory. I will gloss over the details of pages
for this post, since we don’t need to know what they are for now.</p>

<p>For this example, we’ll create a buffer of 32 bytes, and write the <code class="language-plaintext highlighter-rouge">"foo"</code>
string to it, before printing. This is a very simple example, but it’s just a
way to demonstrate how <code class="language-plaintext highlighter-rouge">mmap</code> works.  Behold!</p>

<div class="language-c highlighter-rouge"><table class="rouge-line-table"><tbody><tr id="line-1" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>1</pre></td><td class="rouge-code"><pre><span class="cp">#include</span> <span class="cpf">&lt;stddef.h&gt;</span>
</pre></td></tr><tr id="line-2" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>2</pre></td><td class="rouge-code"><pre><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span>
</pre></td></tr><tr id="line-3" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>3</pre></td><td class="rouge-code"><pre><span class="cp">#include</span> <span class="cpf">&lt;stdlib.h&gt;</span>
</pre></td></tr><tr id="line-4" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>4</pre></td><td class="rouge-code"><pre><span class="cp">#include</span> <span class="cpf">&lt;sys/mman.h&gt;</span>
</pre></td></tr><tr id="line-5" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>5</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-6" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>6</pre></td><td class="rouge-code"><pre><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</pre></td></tr><tr id="line-7" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>7</pre></td><td class="rouge-code"><pre>  <span class="kt">void</span> <span class="o">*</span><span class="n">addr</span> <span class="o">=</span> <span class="n">mmap</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="n">PROT_READ</span> <span class="o">|</span> <span class="n">PROT_WRITE</span><span class="p">,</span> <span class="n">MAP_PRIVATE</span> <span class="o">|</span> <span class="n">MAP_ANON</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> <span class="c1">// (1)</span>
</pre></td></tr><tr id="line-8" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>8</pre></td><td class="rouge-code"><pre>  <span class="k">if</span> <span class="p">(</span><span class="n">addr</span> <span class="o">==</span> <span class="n">MAP_FAILED</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// (2)</span>
</pre></td></tr><tr id="line-9" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>9</pre></td><td class="rouge-code"><pre>    <span class="n">perror</span><span class="p">(</span><span class="s">"mmap"</span><span class="p">);</span>
</pre></td></tr><tr id="line-10" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>10</pre></td><td class="rouge-code"><pre>    <span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
</pre></td></tr><tr id="line-11" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>11</pre></td><td class="rouge-code"><pre>  <span class="p">}</span>
</pre></td></tr><tr id="line-12" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>12</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-13" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>13</pre></td><td class="rouge-code"><pre>  <span class="kt">char</span> <span class="o">*</span><span class="n">bytes</span> <span class="o">=</span> <span class="n">addr</span><span class="p">;</span>
</pre></td></tr><tr id="line-14" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>14</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-15" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>15</pre></td><td class="rouge-code"><pre>  <span class="n">bytes</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'f'</span><span class="p">;</span>
</pre></td></tr><tr id="line-16" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>16</pre></td><td class="rouge-code"><pre>  <span class="n">bytes</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'o'</span><span class="p">;</span>
</pre></td></tr><tr id="line-17" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>17</pre></td><td class="rouge-code"><pre>  <span class="n">bytes</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'o'</span><span class="p">;</span>
</pre></td></tr><tr id="line-18" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>18</pre></td><td class="rouge-code"><pre>  <span class="n">bytes</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'\0'</span><span class="p">;</span>
</pre></td></tr><tr id="line-19" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>19</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-20" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>20</pre></td><td class="rouge-code"><pre>  <span class="n">printf</span><span class="p">(</span><span class="s">"%s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">bytes</span><span class="p">);</span> <span class="c1">// (3)</span>
</pre></td></tr><tr id="line-21" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>21</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-22" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>22</pre></td><td class="rouge-code"><pre>  <span class="k">if</span> <span class="p">(</span><span class="n">munmap</span><span class="p">(</span><span class="n">addr</span><span class="p">,</span> <span class="mi">32</span><span class="p">)</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// (4)</span>
</pre></td></tr><tr id="line-23" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>23</pre></td><td class="rouge-code"><pre>    <span class="n">perror</span><span class="p">(</span><span class="s">"munmap"</span><span class="p">);</span>
</pre></td></tr><tr id="line-24" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>24</pre></td><td class="rouge-code"><pre>    <span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
</pre></td></tr><tr id="line-25" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>25</pre></td><td class="rouge-code"><pre>  <span class="p">}</span>
</pre></td></tr><tr id="line-26" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>26</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-27" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>27</pre></td><td class="rouge-code"><pre>  <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</pre></td></tr><tr id="line-28" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>28</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr></tbody></table></div>

<p>First, for (1), we create the <code class="language-plaintext highlighter-rouge">mmap</code> mapping; since we do not care where the
allocation is in our address space is<sup id="fnref:address-space"><a href="#fn:address-space" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, we’ll let the system
choose the address for us by passing <code class="language-plaintext highlighter-rouge">NULL</code> as the first parameter - and it’ll
return where it is allocated to us. Since we want to be able to read and write
to the memory, we pass <code class="language-plaintext highlighter-rouge">PROT_READ | PROT_WRITE</code> as the second parameter. And
since this is a private allocation with no backing file, we’ll pass <code class="language-plaintext highlighter-rouge">MAP_ANON</code>
as the fourth parameter with <code class="language-plaintext highlighter-rouge">MAP_PRIVATE</code> to make it private. With no backing
file, we’ll pass <code class="language-plaintext highlighter-rouge">-1</code> as the fifth parameter and <code class="language-plaintext highlighter-rouge">0</code> as the sixth parameter.</p>

<p>If the allocation failed for some reason, in (2), <code class="language-plaintext highlighter-rouge">mmap</code> will have returned
<code class="language-plaintext highlighter-rouge">MAP_FAILED</code><sup id="fnref:mmap-failed"><a href="#fn:mmap-failed" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>. If this happens, we should handle the error by
printing an error message and exiting the program. (Note that <code class="language-plaintext highlighter-rouge">perror</code> checks
the value of <code class="language-plaintext highlighter-rouge">errno</code> and prints a message based on the value of <code class="language-plaintext highlighter-rouge">errno</code>.)</p>

<p>We’ll then put the string in the buffer - individually assigning the characters
to not overcomplicate things - and use <code class="language-plaintext highlighter-rouge">printf</code> to print the string.</p>

<p>Finally, we unmap the memory. After that point, any memory accesses to the
mapped region will be invalid and result in undefined behavior.  Fun!</p>

<div class="language-plaintext highlighter-rouge"><table class="rouge-line-table"><tbody><tr id="line-1" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>1</pre></td><td class="rouge-code"><pre>$ cc -Wall -Wextra private.c -o private
</pre></td></tr><tr id="line-2" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>2</pre></td><td class="rouge-code"><pre>$ ./private
</pre></td></tr><tr id="line-3" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>3</pre></td><td class="rouge-code"><pre>foo
</pre></td></tr></tbody></table></div>

<p>And it works as expected!</p>

<h3 id="creating-a-shared-mapping-for-a-file">Creating a Shared Mapping for a File</h3>

<p>Now, we’re going to use <code class="language-plaintext highlighter-rouge">mmap</code> to create a shared mapping for a file.  This is
a bit more involved, but it’s the same basic concept:</p>

<div class="language-c highlighter-rouge"><table class="rouge-line-table"><tbody><tr id="line-1" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>1</pre></td><td class="rouge-code"><pre><span class="cp">#define _GNU_SOURCE</span>
</pre></td></tr><tr id="line-2" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>2</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-3" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>3</pre></td><td class="rouge-code"><pre><span class="cp">#include</span> <span class="cpf">&lt;stddef.h&gt;</span>
</pre></td></tr><tr id="line-4" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>4</pre></td><td class="rouge-code"><pre><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span>
</pre></td></tr><tr id="line-5" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>5</pre></td><td class="rouge-code"><pre><span class="cp">#include</span> <span class="cpf">&lt;stdlib.h&gt;</span>
</pre></td></tr><tr id="line-6" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>6</pre></td><td class="rouge-code"><pre><span class="cp">#include</span> <span class="cpf">&lt;sys/mman.h&gt;</span>
</pre></td></tr><tr id="line-7" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>7</pre></td><td class="rouge-code"><pre><span class="cp">#include</span> <span class="cpf">&lt;fcntl.h&gt;</span>
</pre></td></tr><tr id="line-8" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>8</pre></td><td class="rouge-code"><pre><span class="cp">#include</span> <span class="cpf">&lt;sys/stat.h&gt;</span>
</pre></td></tr><tr id="line-9" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>9</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-10" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>10</pre></td><td class="rouge-code"><pre><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</pre></td></tr><tr id="line-11" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>11</pre></td><td class="rouge-code"><pre>  <span class="kt">int</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="s">"example.txt"</span><span class="p">,</span> <span class="n">O_RDWR</span> <span class="o">|</span> <span class="n">O_CREAT</span><span class="p">,</span> <span class="mo">0644</span><span class="p">);</span> <span class="c1">// (1)</span>
</pre></td></tr><tr id="line-12" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>12</pre></td><td class="rouge-code"><pre>  <span class="k">if</span><span class="p">(</span><span class="n">fd</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</pre></td></tr><tr id="line-13" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>13</pre></td><td class="rouge-code"><pre>    <span class="n">perror</span><span class="p">(</span><span class="s">"open"</span><span class="p">);</span>
</pre></td></tr><tr id="line-14" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>14</pre></td><td class="rouge-code"><pre>    <span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
</pre></td></tr><tr id="line-15" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>15</pre></td><td class="rouge-code"><pre>  <span class="p">}</span>
</pre></td></tr><tr id="line-16" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>16</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-17" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>17</pre></td><td class="rouge-code"><pre>  <span class="k">if</span><span class="p">(</span><span class="n">fallocate</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// (2)</span>
</pre></td></tr><tr id="line-18" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>18</pre></td><td class="rouge-code"><pre>    <span class="n">perror</span><span class="p">(</span><span class="s">"fallocate"</span><span class="p">);</span>
</pre></td></tr><tr id="line-19" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>19</pre></td><td class="rouge-code"><pre>    <span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
</pre></td></tr><tr id="line-20" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>20</pre></td><td class="rouge-code"><pre>  <span class="p">}</span>
</pre></td></tr><tr id="line-21" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>21</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-22" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>22</pre></td><td class="rouge-code"><pre>  <span class="k">struct</span> <span class="n">stat</span> <span class="n">st</span><span class="p">;</span>
</pre></td></tr><tr id="line-23" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>23</pre></td><td class="rouge-code"><pre>  <span class="k">if</span><span class="p">(</span><span class="n">fstat</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">st</span><span class="p">)</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</pre></td></tr><tr id="line-24" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>24</pre></td><td class="rouge-code"><pre>    <span class="n">perror</span><span class="p">(</span><span class="s">"fstat"</span><span class="p">);</span>
</pre></td></tr><tr id="line-25" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>25</pre></td><td class="rouge-code"><pre>    <span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
</pre></td></tr><tr id="line-26" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>26</pre></td><td class="rouge-code"><pre>  <span class="p">}</span>
</pre></td></tr><tr id="line-27" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>27</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-28" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>28</pre></td><td class="rouge-code"><pre>  <span class="kt">void</span> <span class="o">*</span><span class="n">addr</span> <span class="o">=</span> <span class="n">mmap</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">st</span><span class="p">.</span><span class="n">st_size</span><span class="p">,</span> <span class="n">PROT_READ</span> <span class="o">|</span> <span class="n">PROT_WRITE</span><span class="p">,</span> <span class="n">MAP_SHARED</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> <span class="c1">// (3)</span>
</pre></td></tr><tr id="line-29" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>29</pre></td><td class="rouge-code"><pre>  <span class="k">if</span> <span class="p">(</span><span class="n">addr</span> <span class="o">==</span> <span class="n">MAP_FAILED</span><span class="p">)</span> <span class="p">{</span>
</pre></td></tr><tr id="line-30" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>30</pre></td><td class="rouge-code"><pre>    <span class="n">perror</span><span class="p">(</span><span class="s">"mmap"</span><span class="p">);</span>
</pre></td></tr><tr id="line-31" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>31</pre></td><td class="rouge-code"><pre>    <span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
</pre></td></tr><tr id="line-32" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>32</pre></td><td class="rouge-code"><pre>  <span class="p">}</span>
</pre></td></tr><tr id="line-33" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>33</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-34" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>34</pre></td><td class="rouge-code"><pre>  <span class="kt">char</span> <span class="o">*</span><span class="n">bytes</span> <span class="o">=</span> <span class="n">addr</span><span class="p">;</span>
</pre></td></tr><tr id="line-35" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>35</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-36" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>36</pre></td><td class="rouge-code"><pre>  <span class="n">bytes</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'f'</span><span class="p">;</span>
</pre></td></tr><tr id="line-37" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>37</pre></td><td class="rouge-code"><pre>  <span class="n">bytes</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'o'</span><span class="p">;</span>
</pre></td></tr><tr id="line-38" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>38</pre></td><td class="rouge-code"><pre>  <span class="n">bytes</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'o'</span><span class="p">;</span>
</pre></td></tr><tr id="line-39" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>39</pre></td><td class="rouge-code"><pre>  <span class="n">bytes</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="sc">'\0'</span><span class="p">;</span>
</pre></td></tr><tr id="line-40" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>40</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-41" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>41</pre></td><td class="rouge-code"><pre>  <span class="n">printf</span><span class="p">(</span><span class="s">"%s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">bytes</span><span class="p">);</span>
</pre></td></tr><tr id="line-42" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>42</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-43" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>43</pre></td><td class="rouge-code"><pre>  <span class="k">if</span> <span class="p">(</span><span class="n">munmap</span><span class="p">(</span><span class="n">addr</span><span class="p">,</span> <span class="mi">32</span><span class="p">)</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</pre></td></tr><tr id="line-44" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>44</pre></td><td class="rouge-code"><pre>    <span class="n">perror</span><span class="p">(</span><span class="s">"munmap"</span><span class="p">);</span>
</pre></td></tr><tr id="line-45" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>45</pre></td><td class="rouge-code"><pre>    <span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
</pre></td></tr><tr id="line-46" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>46</pre></td><td class="rouge-code"><pre>  <span class="p">}</span>
</pre></td></tr><tr id="line-47" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>47</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-48" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>48</pre></td><td class="rouge-code"><pre>  <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</pre></td></tr><tr id="line-49" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>49</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr></tbody></table></div>

<p>It’s very similar to what we were previously doing, but there’s a few changes:</p>

<ol>
  <li>We’re now opening the file. We need to ensure that the file exists; so if it
  doesn’t, we’ll need to create it.</li>
  <li>However, if we just created the file, the file may be empty; in order to
  write 4 bytes to it, we need to ensure that the file is <em>at least</em> 4 bytes
  long; this call to <a href="https://man7.org/linux/man-pages/man2/fallocate.2.html"><code class="language-plaintext highlighter-rouge">fallocate</code></a> ensures that. If the file is longer, this
  call does nothing.  If we don’t do this, one of two things happen:
    <ul>
      <li>If the file has nothing in it, i.e., is size 0, the call to <code class="language-plaintext highlighter-rouge">mmap</code> will
 fail with <code class="language-plaintext highlighter-rouge">EINVAL</code>. (Since Linux 2.6.12, the manual says. Neat!)</li>
      <li>If the file has not enough space in it, we’ll get undefined behavior when
 we try to write past the end of the file<sup id="fnref:write-ub"><a href="#fn:write-ub" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>.</li>
    </ul>
  </li>
  <li>Then, we perform the <code class="language-plaintext highlighter-rouge">mmap</code>. We’ll map the entire file, from start to end,
  into memory; as a shared mapping. If we do not use <code class="language-plaintext highlighter-rouge">MAP_SHARED</code> here, any
  modifications we make to the mapped memory will not be reflected in the file.</li>
</ol>

<p>At the end, this is what we get:</p>

<div class="language-plaintext highlighter-rouge"><table class="rouge-line-table"><tbody><tr id="line-1" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>1</pre></td><td class="rouge-code"><pre>$ cc shared.c -o shared
</pre></td></tr><tr id="line-2" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>2</pre></td><td class="rouge-code"><pre>$ ./shared
</pre></td></tr><tr id="line-3" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>3</pre></td><td class="rouge-code"><pre>foo
</pre></td></tr><tr id="line-4" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>4</pre></td><td class="rouge-code"><pre>$ hexdump -C example.txt
</pre></td></tr><tr id="line-5" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>5</pre></td><td class="rouge-code"><pre>00000000  66 6f 6f 00                                       |foo.|
</pre></td></tr><tr id="line-6" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>6</pre></td><td class="rouge-code"><pre>00000004
</pre></td></tr></tbody></table></div>

<p>Cool. Now we’ve got a solid idea of what we’re working with.</p>

<h2 id="lets-try-this-in-rust">Let’s Try This in Rust</h2>

<p>Let’s try to bring this over to Rust. There already exists multiple crates to
handle interacting with <code class="language-plaintext highlighter-rouge">mmap</code>, but we won’t use it here yet because it’s
spoilers.</p>

<p>We’ll use <a href="https://docs.rs/libc/latest/libc/"><code class="language-plaintext highlighter-rouge">libc</code></a>. <code class="language-plaintext highlighter-rouge">libc</code> contains <a href="https://docs.rs/libc/latest/libc/fn.mmap.html"><code class="language-plaintext highlighter-rouge">libc::mmap</code></a> (and <a href="https://docs.rs/libc/latest/libc/fn.mmap64.html"><code class="language-plaintext highlighter-rouge">libc::mmap64</code></a>, for
64-bit file offsets, which we don’t need right now).  Let’s take a look at the
signature for <code class="language-plaintext highlighter-rouge">libc::mmap</code>:</p>

<div class="language-rust highlighter-rouge"><table class="rouge-line-table"><tbody><tr id="line-1" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>1</pre></td><td class="rouge-code"><pre><span class="k">pub</span> <span class="k">unsafe</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">mmap</span><span class="p">(</span>
</pre></td></tr><tr id="line-2" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>2</pre></td><td class="rouge-code"><pre>    <span class="n">addr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">c_void</span><span class="p">,</span>
</pre></td></tr><tr id="line-3" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>3</pre></td><td class="rouge-code"><pre>    <span class="n">len</span><span class="p">:</span> <span class="nb">size_t</span><span class="p">,</span>
</pre></td></tr><tr id="line-4" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>4</pre></td><td class="rouge-code"><pre>    <span class="n">prot</span><span class="p">:</span> <span class="nb">c_int</span><span class="p">,</span>
</pre></td></tr><tr id="line-5" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>5</pre></td><td class="rouge-code"><pre>    <span class="n">flags</span><span class="p">:</span> <span class="nb">c_int</span><span class="p">,</span>
</pre></td></tr><tr id="line-6" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>6</pre></td><td class="rouge-code"><pre>    <span class="n">fd</span><span class="p">:</span> <span class="nb">c_int</span><span class="p">,</span>
</pre></td></tr><tr id="line-7" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>7</pre></td><td class="rouge-code"><pre>    <span class="n">offset</span><span class="p">:</span> <span class="nb">off_t</span><span class="p">,</span>
</pre></td></tr><tr id="line-8" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>8</pre></td><td class="rouge-code"><pre><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">c_void</span><span class="p">;</span>
</pre></td></tr></tbody></table></div>

<p>This makes sense; all of these match the arguments we need to pass to <code class="language-plaintext highlighter-rouge">mmap</code>,
and they all have the same types as the corresponding arguments in the C
function.</p>

<p>We’ll create a very small wrapper around it to handle the error checking and
returning a <code class="language-plaintext highlighter-rouge">Result</code>:</p>

<div class="language-rust highlighter-rouge"><table class="rouge-line-table"><tbody><tr id="line-1" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>1</pre></td><td class="rouge-code"><pre><span class="nd">#![allow(unsafe_code)]</span>
</pre></td></tr><tr id="line-2" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>2</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-3" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>3</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">libc</span><span class="p">::{</span><span class="nb">c_int</span><span class="p">,</span> <span class="nb">c_void</span><span class="p">,</span> <span class="nb">off_t</span><span class="p">,</span> <span class="nb">size_t</span><span class="p">};</span>
</pre></td></tr><tr id="line-4" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>4</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">ptr</span><span class="p">::</span><span class="n">NonNull</span><span class="p">;</span>
</pre></td></tr><tr id="line-5" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>5</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-6" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>6</pre></td><td class="rouge-code"><pre><span class="k">pub</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">mmap</span><span class="p">(</span>
</pre></td></tr><tr id="line-7" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>7</pre></td><td class="rouge-code"><pre>    <span class="n">addr</span><span class="p">:</span> <span class="o">*</span><span class="k">mut</span> <span class="nb">c_void</span><span class="p">,</span>
</pre></td></tr><tr id="line-8" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>8</pre></td><td class="rouge-code"><pre>    <span class="n">len</span><span class="p">:</span> <span class="nb">size_t</span><span class="p">,</span>
</pre></td></tr><tr id="line-9" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>9</pre></td><td class="rouge-code"><pre>    <span class="n">prot</span><span class="p">:</span> <span class="nb">c_int</span><span class="p">,</span>
</pre></td></tr><tr id="line-10" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>10</pre></td><td class="rouge-code"><pre>    <span class="n">flags</span><span class="p">:</span> <span class="nb">c_int</span><span class="p">,</span>
</pre></td></tr><tr id="line-11" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>11</pre></td><td class="rouge-code"><pre>    <span class="n">fd</span><span class="p">:</span> <span class="nb">c_int</span><span class="p">,</span>
</pre></td></tr><tr id="line-12" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>12</pre></td><td class="rouge-code"><pre>    <span class="n">offset</span><span class="p">:</span> <span class="nb">off_t</span><span class="p">,</span>
</pre></td></tr><tr id="line-13" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>13</pre></td><td class="rouge-code"><pre><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="n">NonNull</span><span class="o">&lt;</span><span class="nb">c_void</span><span class="o">&gt;</span><span class="p">,</span> <span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="p">{</span>
</pre></td></tr><tr id="line-14" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>14</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">addr</span> <span class="o">=</span> <span class="nn">libc</span><span class="p">::</span><span class="nf">mmap</span><span class="p">(</span><span class="n">addr</span><span class="p">,</span> <span class="n">len</span><span class="p">,</span> <span class="n">prot</span><span class="p">,</span> <span class="n">flags</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="n">offset</span><span class="p">);</span>
</pre></td></tr><tr id="line-15" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>15</pre></td><td class="rouge-code"><pre>    <span class="k">if</span> <span class="n">addr</span> <span class="o">==</span> <span class="nn">libc</span><span class="p">::</span><span class="n">MAP_FAILED</span> <span class="p">{</span>
</pre></td></tr><tr id="line-16" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>16</pre></td><td class="rouge-code"><pre>        <span class="nf">Err</span><span class="p">(</span><span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="nn">Error</span><span class="p">::</span><span class="nf">last_os_error</span><span class="p">())</span>
</pre></td></tr><tr id="line-17" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>17</pre></td><td class="rouge-code"><pre>    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</pre></td></tr><tr id="line-18" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>18</pre></td><td class="rouge-code"><pre>        <span class="nf">Ok</span><span class="p">(</span><span class="nn">NonNull</span><span class="p">::</span><span class="nf">new_unchecked</span><span class="p">(</span><span class="n">addr</span><span class="p">))</span>
</pre></td></tr><tr id="line-19" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>19</pre></td><td class="rouge-code"><pre>    <span class="p">}</span>
</pre></td></tr><tr id="line-20" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>20</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr><tr id="line-21" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>21</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-22" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>22</pre></td><td class="rouge-code"><pre><span class="k">pub</span> <span class="k">unsafe</span> <span class="k">fn</span> <span class="nf">munmap</span><span class="p">(</span><span class="n">addr</span><span class="p">:</span> <span class="n">NonNull</span><span class="o">&lt;</span><span class="nb">c_void</span><span class="o">&gt;</span><span class="p">,</span> <span class="n">len</span><span class="p">:</span> <span class="nb">size_t</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="p">{</span>
</pre></td></tr><tr id="line-23" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>23</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">result</span> <span class="o">=</span> <span class="nn">libc</span><span class="p">::</span><span class="nf">munmap</span><span class="p">(</span><span class="n">addr</span><span class="nf">.as_ptr</span><span class="p">(),</span> <span class="n">len</span><span class="p">);</span>
</pre></td></tr><tr id="line-24" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>24</pre></td><td class="rouge-code"><pre>    <span class="k">if</span> <span class="n">result</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span> <span class="p">{</span>
</pre></td></tr><tr id="line-25" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>25</pre></td><td class="rouge-code"><pre>        <span class="nf">Err</span><span class="p">(</span><span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="nn">Error</span><span class="p">::</span><span class="nf">last_os_error</span><span class="p">())</span>
</pre></td></tr><tr id="line-26" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>26</pre></td><td class="rouge-code"><pre>    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</pre></td></tr><tr id="line-27" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>27</pre></td><td class="rouge-code"><pre>        <span class="nf">Ok</span><span class="p">(())</span>
</pre></td></tr><tr id="line-28" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>28</pre></td><td class="rouge-code"><pre>    <span class="p">}</span>
</pre></td></tr><tr id="line-29" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>29</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr></tbody></table></div>

<p><a href="https://doc.rust-lang.org/std/io/struct.Error.html#method.last_os_error"><code class="language-plaintext highlighter-rouge">io::Error::last_os_error()</code></a> reads the last value of <code class="language-plaintext highlighter-rouge">errno</code> and creates a new
<a href="https://doc.rust-lang.org/std/io/struct.Error.html"><code class="language-plaintext highlighter-rouge">io::Error</code></a> from it, which is perfect for our needs. However, we are always
going to be passing in <a href="https://doc.rust-lang.org/std/ptr/fn.null_mut.html"><code class="language-plaintext highlighter-rouge">std::ptr::null()</code></a> as the first argument to <code class="language-plaintext highlighter-rouge">mmap</code>;
doing anything else would complicate things.</p>

<h3 id="anonymous-private-mapping-in-rust">Anonymous Private Mapping in Rust</h3>

<p>Let’s keep things simple and just… copy over what we did for the first
anonymous private mapping example:</p>

<div class="language-rust highlighter-rouge"><table class="rouge-line-table"><tbody><tr id="line-1" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>1</pre></td><td class="rouge-code"><pre><span class="k">mod</span> <span class="n">sys</span><span class="p">;</span>
</pre></td></tr><tr id="line-2" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>2</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-3" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>3</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">ffi</span><span class="p">::</span><span class="n">CStr</span><span class="p">;</span>
</pre></td></tr><tr id="line-4" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>4</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-5" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>5</pre></td><td class="rouge-code"><pre><span class="nd">#[allow(unsafe_code)]</span>
</pre></td></tr><tr id="line-6" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>6</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="nn">std</span><span class="p">::</span><span class="nn">error</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
</pre></td></tr><tr id="line-7" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>7</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">map</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="c1">// (1)</span>
</pre></td></tr><tr id="line-8" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>8</pre></td><td class="rouge-code"><pre>        <span class="k">self</span><span class="p">::</span><span class="nn">sys</span><span class="p">::</span><span class="nf">mmap</span><span class="p">(</span>
</pre></td></tr><tr id="line-9" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>9</pre></td><td class="rouge-code"><pre>            <span class="nn">std</span><span class="p">::</span><span class="nn">ptr</span><span class="p">::</span><span class="nf">null_mut</span><span class="p">(),</span>
</pre></td></tr><tr id="line-10" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>10</pre></td><td class="rouge-code"><pre>            <span class="mi">32</span><span class="p">,</span>
</pre></td></tr><tr id="line-11" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>11</pre></td><td class="rouge-code"><pre>            <span class="nn">libc</span><span class="p">::</span><span class="n">PROT_READ</span> <span class="p">|</span> <span class="nn">libc</span><span class="p">::</span><span class="n">PROT_WRITE</span><span class="p">,</span>
</pre></td></tr><tr id="line-12" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>12</pre></td><td class="rouge-code"><pre>            <span class="nn">libc</span><span class="p">::</span><span class="n">MAP_ANON</span> <span class="p">|</span> <span class="nn">libc</span><span class="p">::</span><span class="n">MAP_PRIVATE</span><span class="p">,</span>
</pre></td></tr><tr id="line-13" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>13</pre></td><td class="rouge-code"><pre>            <span class="o">-</span><span class="mi">1</span><span class="p">,</span>
</pre></td></tr><tr id="line-14" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>14</pre></td><td class="rouge-code"><pre>            <span class="mi">0</span><span class="p">,</span>
</pre></td></tr><tr id="line-15" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>15</pre></td><td class="rouge-code"><pre>        <span class="p">)</span>
</pre></td></tr><tr id="line-16" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>16</pre></td><td class="rouge-code"><pre>    <span class="p">}</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-17" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>17</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-18" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>18</pre></td><td class="rouge-code"><pre>    <span class="p">{</span>
</pre></td></tr><tr id="line-19" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>19</pre></td><td class="rouge-code"><pre>        <span class="k">let</span> <span class="n">bytes_ptr</span> <span class="o">=</span> <span class="n">map</span><span class="nf">.as_ptr</span><span class="p">()</span><span class="py">.cast</span><span class="p">::</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;</span><span class="p">();</span> <span class="c1">// (2)</span>
</pre></td></tr><tr id="line-20" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>20</pre></td><td class="rouge-code"><pre>        <span class="k">let</span> <span class="n">slice_ptr</span> <span class="o">=</span> <span class="nn">std</span><span class="p">::</span><span class="nn">ptr</span><span class="p">::</span><span class="nf">slice_from_raw_parts_mut</span><span class="p">(</span><span class="n">bytes_ptr</span><span class="p">,</span> <span class="mi">32</span><span class="p">);</span> <span class="c1">// (3)</span>
</pre></td></tr><tr id="line-21" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>21</pre></td><td class="rouge-code"><pre>        <span class="k">let</span> <span class="n">slice</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="o">*</span><span class="n">slice_ptr</span> <span class="p">};</span>
</pre></td></tr><tr id="line-22" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>22</pre></td><td class="rouge-code"><pre>        <span class="n">slice</span><span class="p">[</span><span class="mi">0</span><span class="o">..</span><span class="mi">4</span><span class="p">]</span><span class="nf">.copy_from_slice</span><span class="p">(</span><span class="s">b"foo</span><span class="se">\0</span><span class="s">"</span><span class="p">);</span> <span class="c1">// (4)</span>
</pre></td></tr><tr id="line-23" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>23</pre></td><td class="rouge-code"><pre>        <span class="k">let</span> <span class="n">string</span> <span class="o">=</span> <span class="nn">CStr</span><span class="p">::</span><span class="nf">from_bytes_until_nul</span><span class="p">(</span><span class="o">&amp;</span><span class="n">slice</span><span class="p">[</span><span class="o">..</span><span class="p">])</span><span class="o">?</span><span class="nf">.to_str</span><span class="p">()</span><span class="o">?</span><span class="p">;</span> <span class="c1">// (5)</span>
</pre></td></tr><tr id="line-24" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>24</pre></td><td class="rouge-code"><pre>        <span class="nd">println!</span><span class="p">(</span><span class="s">"{string}"</span><span class="p">);</span>
</pre></td></tr><tr id="line-25" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>25</pre></td><td class="rouge-code"><pre>    <span class="p">}</span>
</pre></td></tr><tr id="line-26" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>26</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-27" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>27</pre></td><td class="rouge-code"><pre>    <span class="k">unsafe</span> <span class="p">{</span> <span class="k">self</span><span class="p">::</span><span class="nn">sys</span><span class="p">::</span><span class="nf">munmap</span><span class="p">(</span><span class="n">map</span><span class="p">,</span> <span class="mi">32</span><span class="p">)</span> <span class="p">}</span><span class="o">?</span><span class="p">;</span> <span class="c1">// (6)</span>
</pre></td></tr><tr id="line-28" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>28</pre></td><td class="rouge-code"><pre>    <span class="nf">Ok</span><span class="p">(())</span>
</pre></td></tr><tr id="line-29" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>29</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr></tbody></table></div>

<p>This… is already intimidating. But we’ll step through it.</p>

<p>One of the things that I like to do is document everything - for every use of
unsafe - what <em>makes</em> it unsafe, and why we can use it safely.  Let’s start from
the top.</p>

<p>At (1), we’re calling <code class="language-plaintext highlighter-rouge">mmap</code>. We haven’t yet introduced references, so we don’t
have to worry about any invariants with those. Holding pointers isn’t
unsafe; calling <code class="language-plaintext highlighter-rouge">std::ptr::null()</code> isn’t unsafe, as long as we don’t
dereference it. We know that when <code class="language-plaintext highlighter-rouge">mmap</code> returns successfully, it returns a
an allocation, starting at the address returned, and continuing for the
specified length in bytes. <code class="language-plaintext highlighter-rouge">mmap</code> cannot return a NULL pointer, and the API
we wrote captures that; the pointer uses <code class="language-plaintext highlighter-rouge">std::ptr::NonNull</code> (which is a
fancy <code class="language-plaintext highlighter-rouge">*mut T</code> with features). However, for reasons we’ll get into later, we
do need the allocation to be both readable and writable<sup id="fnref:read-write"><a href="#fn:read-write" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>.</p>

<p>Surprisingly, the cast at (2) can be dangerous! The return result of <code class="language-plaintext highlighter-rouge">mmap</code>
returns <code class="language-plaintext highlighter-rouge">*mut c_void</code>, so we need to cast that into something to be useable.
Here, we’re just casting it into <code class="language-plaintext highlighter-rouge">u8</code>. This isn’t marked as <code class="language-plaintext highlighter-rouge">unsafe</code> because
we’re not yet dereferencing it - it would be unsafe if this were to dereference
the pointer. Consider instead if we were to cast it into a <code class="language-plaintext highlighter-rouge">bool</code>, for example.
<code class="language-plaintext highlighter-rouge">bool</code> only has <a href="https://doc.rust-lang.org/reference/types/boolean.html#r-type.bool.repr">two valid bit patterns</a> - it is “undefined behavior for an
object with the boolean type to have any other bit pattern.” However, we don’t
have any guarantee what the underlying value would be<sup id="fnref:underlying-pattern"><a href="#fn:underlying-pattern" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>! If
we cast it into a <code class="language-plaintext highlighter-rouge">bool</code> and then try to read the first one, that could
potentially be undefined behavior - unsafe!</p>

<p>Another thing to be concerned about here is <em>alignment</em>. Certain types have
alignment requirements, where the address of the values of the types needs to be
a multiple of the alignment. For example, <code class="language-plaintext highlighter-rouge">u64</code> requires an alignment of 8 bytes,
meaning that the address of a <code class="language-plaintext highlighter-rouge">u64</code> must be a multiple of 8. In our case, <code class="language-plaintext highlighter-rouge">mmap</code>
returns a page-aligned address, which means that the address is a multiple of
the page size (typically 4096 bytes).</p>

<p>Finally, this is where the magic happens. At (3), we convert the raw pointer
into a pointer to a slice. Recall that in Rust, a a pointer to a slice is
actually a fat pointer,  which contains both the pointer to the data, and
the length of pointed-to data.  Casting our pointer into a slice pointer is
not unsafe - it’s not unsafe yet because we haven’t done anything with it yet!</p>

<p>It becomes unsafe the moment we convert our pointer to a reference, as we now
have to abide by the rules of references.  Let’s go through a few of them, and
keep in mind this is by no means an exhaustive list:</p>

<ol>
  <li>It must be aligned. <code class="language-plaintext highlighter-rouge">u8</code> has an alignment of 1 byte, so it is always aligned;
  even still, <code class="language-plaintext highlighter-rouge">mmap</code> returns a page-aligned address, which means that any value
  that has an alignment smaller than a page size is guaranteed to be aligned.</li>
  <li>It must be non-null. We know this to be true, because <code class="language-plaintext highlighter-rouge">mmap</code> does not return
  null pointers.</li>
  <li>It must be dereferencable for the type it points to. <a href="https://doc.rust-lang.org/std/primitive.reference.html#safety">The documentation</a>
  states that if <code class="language-plaintext highlighter-rouge">size_of_val(t) &gt; 0</code>, then <code class="language-plaintext highlighter-rouge">t</code> must be dereferencable for
  <code class="language-plaintext highlighter-rouge">size_of_val(t)</code> bytes. Here, <code class="language-plaintext highlighter-rouge">size_of_val(t)</code> would correspond to the number
  of elements in the slice, times the size of the element in the slice;
  <code class="language-plaintext highlighter-rouge">len * size_of::&lt;u8&gt;()</code>. Since the allocation was 32 bytes, and
  <code class="language-plaintext highlighter-rouge">size_of::&lt;u8&gt;() == 1</code>, and <code class="language-plaintext highlighter-rouge">len</code> is 32, the slice is dereferencable for 32
  bytes, satisfying this constraint.</li>
  <li>The pointer must point to a valid value of the type it points to. In our
  case, the pointer points to a <code class="language-plaintext highlighter-rouge">u8</code>, so it must point to a valid <code class="language-plaintext highlighter-rouge">u8</code>.
  Thankfully, for <code class="language-plaintext highlighter-rouge">u8</code>, there are no invalid bit patterns, so this constraint
  is satisfied.</li>
  <li>Aliasing. Rust has aliasing rules; while a <code class="language-plaintext highlighter-rouge">&amp;mut</code> reference exists, no
  other reference to that data can exist; and while a <code class="language-plaintext highlighter-rouge">&amp;</code> reference exists, no
  other mutable reference to that data can exist.  Here, we’re creating the
  first (mutable) reference to the data; we had not created any other references
  to that data.</li>
  <li>Lifetimes. Rust has rules around lifetimes, but since we’re freely casting
  into a reference, we have to constrain the lifetime. The reference cannot
  outlive the allocation. In this case, the reference to <code class="language-plaintext highlighter-rouge">map</code> cannot live
  past point (6), when the allocation is de-allocated. We’ve ensured that by
  enclosing the reference in a scope that ends before point (6).</li>
</ol>

<p>That covers our bases! It <em>should</em> be safe to cast this pointer to a reference.
We’ll then copy the data into that slice at (4), then at (5) we’ll convert our
“C-string” (nul-terminated) to a <a href="https://doc.rust-lang.org/std/primitive.str.html"><code class="language-plaintext highlighter-rouge">&amp;str</code></a>. While we could unsafely cast the
pointer to a <code class="language-plaintext highlighter-rouge">&amp;str</code>, we’ll instead use the <a href="https://doc.rust-lang.org/std/ffi/struct.CStr.html"><code class="language-plaintext highlighter-rouge">CStr</code></a> type from the standard library
to safely convert the pointer to a <code class="language-plaintext highlighter-rouge">&amp;str</code>.</p>

<p>Ok, so far, so good.</p>

<h3 id="shared-file-mapping-in-rust">Shared File Mapping in Rust</h3>

<p>Since the anonymous, private mapping went so well, this should be easy, right?
Unfortunately, Rust doesn’t natively have a way to call <code class="language-plaintext highlighter-rouge">fallocate</code>, nor to
determine the size of the file (apart from seeking to the end of the file and
asking for your current position), so we’ll add those to the <code class="language-plaintext highlighter-rouge">sys</code> module:</p>

<div class="language-rust highlighter-rouge"><table class="rouge-line-table"><tbody><tr id="line-1" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>1</pre></td><td class="rouge-code"><pre><span class="c1">// ...previous functions</span>
</pre></td></tr><tr id="line-2" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>2</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-3" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>3</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">os</span><span class="p">::</span><span class="nn">fd</span><span class="p">::</span><span class="n">AsRawFd</span><span class="p">;</span>
</pre></td></tr><tr id="line-4" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>4</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-5" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>5</pre></td><td class="rouge-code"><pre><span class="k">pub</span> <span class="k">fn</span> <span class="n">fstat</span><span class="o">&lt;</span><span class="n">F</span><span class="p">:</span> <span class="n">AsRawFd</span><span class="o">&gt;</span><span class="p">(</span><span class="n">file</span><span class="p">:</span> <span class="n">F</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nn">libc</span><span class="p">::</span><span class="n">stat</span><span class="p">,</span> <span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="p">{</span>
</pre></td></tr><tr id="line-6" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>6</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="k">mut</span> <span class="n">buf</span> <span class="o">=</span> <span class="nn">std</span><span class="p">::</span><span class="nn">mem</span><span class="p">::</span><span class="nn">MaybeUninit</span><span class="p">::</span><span class="nf">zeroed</span><span class="p">();</span>
</pre></td></tr><tr id="line-7" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>7</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">result</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
</pre></td></tr><tr id="line-8" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>8</pre></td><td class="rouge-code"><pre>        <span class="nn">libc</span><span class="p">::</span><span class="nf">fstat</span><span class="p">(</span><span class="n">file</span><span class="nf">.as_raw_fd</span><span class="p">(),</span> <span class="n">buf</span><span class="nf">.as_mut_ptr</span><span class="p">())</span>
</pre></td></tr><tr id="line-9" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>9</pre></td><td class="rouge-code"><pre>    <span class="p">};</span>
</pre></td></tr><tr id="line-10" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>10</pre></td><td class="rouge-code"><pre>    <span class="k">if</span> <span class="n">result</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span> <span class="p">{</span>
</pre></td></tr><tr id="line-11" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>11</pre></td><td class="rouge-code"><pre>        <span class="nf">Err</span><span class="p">(</span><span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="nn">Error</span><span class="p">::</span><span class="nf">last_os_error</span><span class="p">())</span>
</pre></td></tr><tr id="line-12" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>12</pre></td><td class="rouge-code"><pre>    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</pre></td></tr><tr id="line-13" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>13</pre></td><td class="rouge-code"><pre>        <span class="nf">Ok</span><span class="p">(</span><span class="k">unsafe</span> <span class="p">{</span> <span class="n">buf</span><span class="nf">.assume_init</span><span class="p">()</span> <span class="p">})</span>
</pre></td></tr><tr id="line-14" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>14</pre></td><td class="rouge-code"><pre>    <span class="p">}</span>
</pre></td></tr><tr id="line-15" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>15</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr><tr id="line-16" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>16</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-17" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>17</pre></td><td class="rouge-code"><pre><span class="k">pub</span> <span class="k">fn</span> <span class="n">fallocate</span><span class="o">&lt;</span><span class="n">F</span><span class="p">:</span> <span class="n">AsRawFd</span><span class="o">&gt;</span><span class="p">(</span>
</pre></td></tr><tr id="line-18" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>18</pre></td><td class="rouge-code"><pre>    <span class="n">file</span><span class="p">:</span> <span class="n">F</span><span class="p">,</span>
</pre></td></tr><tr id="line-19" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>19</pre></td><td class="rouge-code"><pre>    <span class="n">mode</span><span class="p">:</span> <span class="nb">c_int</span><span class="p">,</span>
</pre></td></tr><tr id="line-20" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>20</pre></td><td class="rouge-code"><pre>    <span class="n">offset</span><span class="p">:</span> <span class="nb">off_t</span><span class="p">,</span>
</pre></td></tr><tr id="line-21" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>21</pre></td><td class="rouge-code"><pre>    <span class="n">len</span><span class="p">:</span> <span class="nb">off_t</span><span class="p">,</span>
</pre></td></tr><tr id="line-22" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>22</pre></td><td class="rouge-code"><pre><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="p">{</span>
</pre></td></tr><tr id="line-23" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>23</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">result</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
</pre></td></tr><tr id="line-24" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>24</pre></td><td class="rouge-code"><pre>        <span class="nn">libc</span><span class="p">::</span><span class="nf">fallocate</span><span class="p">(</span><span class="n">file</span><span class="nf">.as_raw_fd</span><span class="p">(),</span> <span class="n">mode</span><span class="p">,</span> <span class="n">offset</span><span class="p">,</span> <span class="n">len</span><span class="p">)</span>
</pre></td></tr><tr id="line-25" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>25</pre></td><td class="rouge-code"><pre>    <span class="p">};</span>
</pre></td></tr><tr id="line-26" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>26</pre></td><td class="rouge-code"><pre>    <span class="k">if</span> <span class="n">result</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span> <span class="p">{</span>
</pre></td></tr><tr id="line-27" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>27</pre></td><td class="rouge-code"><pre>        <span class="nf">Err</span><span class="p">(</span><span class="nn">std</span><span class="p">::</span><span class="nn">io</span><span class="p">::</span><span class="nn">Error</span><span class="p">::</span><span class="nf">last_os_error</span><span class="p">())</span>
</pre></td></tr><tr id="line-28" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>28</pre></td><td class="rouge-code"><pre>    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</pre></td></tr><tr id="line-29" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>29</pre></td><td class="rouge-code"><pre>        <span class="nf">Ok</span><span class="p">(())</span>
</pre></td></tr><tr id="line-30" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>30</pre></td><td class="rouge-code"><pre>    <span class="p">}</span>
</pre></td></tr><tr id="line-31" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>31</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr></tbody></table></div>

<p>Annoying, but there are crates out there that do this sort of thing for us;
we’re avoiding them for the time being.</p>

<p>Now onto the actual display:</p>

<div class="language-rust highlighter-rouge"><table class="rouge-line-table"><tbody><tr id="line-1" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>1</pre></td><td class="rouge-code"><pre><span class="k">mod</span> <span class="n">sys</span><span class="p">;</span>
</pre></td></tr><tr id="line-2" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>2</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-3" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>3</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">ffi</span><span class="p">::</span><span class="n">CStr</span><span class="p">;</span>
</pre></td></tr><tr id="line-4" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>4</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">os</span><span class="p">::</span><span class="nn">fd</span><span class="p">::</span><span class="n">AsRawFd</span> <span class="k">as</span> <span class="n">_</span><span class="p">;</span>
</pre></td></tr><tr id="line-5" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>5</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-6" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>6</pre></td><td class="rouge-code"><pre><span class="nd">#[allow(unsafe_code)]</span>
</pre></td></tr><tr id="line-7" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>7</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="nn">std</span><span class="p">::</span><span class="nn">error</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;&gt;</span> <span class="p">{</span>
</pre></td></tr><tr id="line-8" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>8</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">file</span> <span class="o">=</span> <span class="nn">std</span><span class="p">::</span><span class="nn">fs</span><span class="p">::</span><span class="nn">OpenOptions</span><span class="p">::</span><span class="nf">new</span><span class="p">()</span>
</pre></td></tr><tr id="line-9" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>9</pre></td><td class="rouge-code"><pre>        <span class="nf">.read</span><span class="p">(</span><span class="k">true</span><span class="p">)</span>
</pre></td></tr><tr id="line-10" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>10</pre></td><td class="rouge-code"><pre>        <span class="nf">.write</span><span class="p">(</span><span class="k">true</span><span class="p">)</span>
</pre></td></tr><tr id="line-11" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>11</pre></td><td class="rouge-code"><pre>        <span class="nf">.create</span><span class="p">(</span><span class="k">true</span><span class="p">)</span>
</pre></td></tr><tr id="line-12" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>12</pre></td><td class="rouge-code"><pre>        <span class="nf">.truncate</span><span class="p">(</span><span class="k">true</span><span class="p">)</span>
</pre></td></tr><tr id="line-13" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>13</pre></td><td class="rouge-code"><pre>        <span class="nf">.open</span><span class="p">(</span><span class="s">"example.txt"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span> <span class="c1">// (1)</span>
</pre></td></tr><tr id="line-14" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>14</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-15" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>15</pre></td><td class="rouge-code"><pre>    <span class="k">self</span><span class="p">::</span><span class="nn">sys</span><span class="p">::</span><span class="nf">fallocate</span><span class="p">(</span><span class="n">file</span><span class="nf">.as_raw_fd</span><span class="p">(),</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">32</span><span class="p">)</span><span class="o">?</span><span class="p">;</span> <span class="c1">// (2)</span>
</pre></td></tr><tr id="line-16" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>16</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">stat</span> <span class="o">=</span> <span class="k">self</span><span class="p">::</span><span class="nn">sys</span><span class="p">::</span><span class="nf">fstat</span><span class="p">(</span><span class="n">file</span><span class="nf">.as_raw_fd</span><span class="p">())</span><span class="o">?</span><span class="p">;</span> <span class="c1">// (3)</span>
</pre></td></tr><tr id="line-17" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>17</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-18" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>18</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">map</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
</pre></td></tr><tr id="line-19" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>19</pre></td><td class="rouge-code"><pre>        <span class="c1">// (4)</span>
</pre></td></tr><tr id="line-20" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>20</pre></td><td class="rouge-code"><pre>        <span class="k">self</span><span class="p">::</span><span class="nn">sys</span><span class="p">::</span><span class="nf">mmap</span><span class="p">(</span>
</pre></td></tr><tr id="line-21" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>21</pre></td><td class="rouge-code"><pre>            <span class="nn">std</span><span class="p">::</span><span class="nn">ptr</span><span class="p">::</span><span class="nf">null_mut</span><span class="p">(),</span>
</pre></td></tr><tr id="line-22" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>22</pre></td><td class="rouge-code"><pre>            <span class="nn">usize</span><span class="p">::</span><span class="nf">try_from</span><span class="p">(</span><span class="n">stat</span><span class="py">.st_size</span><span class="p">)</span><span class="o">?</span><span class="p">,</span>
</pre></td></tr><tr id="line-23" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>23</pre></td><td class="rouge-code"><pre>            <span class="nn">libc</span><span class="p">::</span><span class="n">PROT_READ</span> <span class="p">|</span> <span class="nn">libc</span><span class="p">::</span><span class="n">PROT_WRITE</span><span class="p">,</span>
</pre></td></tr><tr id="line-24" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>24</pre></td><td class="rouge-code"><pre>            <span class="nn">libc</span><span class="p">::</span><span class="n">MAP_SHARED</span><span class="p">,</span>
</pre></td></tr><tr id="line-25" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>25</pre></td><td class="rouge-code"><pre>            <span class="n">file</span><span class="nf">.as_raw_fd</span><span class="p">(),</span>
</pre></td></tr><tr id="line-26" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>26</pre></td><td class="rouge-code"><pre>            <span class="mi">0</span><span class="p">,</span>
</pre></td></tr><tr id="line-27" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>27</pre></td><td class="rouge-code"><pre>        <span class="p">)</span>
</pre></td></tr><tr id="line-28" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>28</pre></td><td class="rouge-code"><pre>    <span class="p">}</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-29" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>29</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-30" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>30</pre></td><td class="rouge-code"><pre>    <span class="p">{</span>
</pre></td></tr><tr id="line-31" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>31</pre></td><td class="rouge-code"><pre>        <span class="k">let</span> <span class="n">bytes_ptr</span> <span class="o">=</span> <span class="n">map</span><span class="nf">.as_ptr</span><span class="p">()</span><span class="py">.cast</span><span class="p">::</span><span class="o">&lt;</span><span class="nb">u8</span><span class="o">&gt;</span><span class="p">();</span>
</pre></td></tr><tr id="line-32" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>32</pre></td><td class="rouge-code"><pre>        <span class="k">let</span> <span class="n">slice_ptr</span> <span class="o">=</span> <span class="nn">std</span><span class="p">::</span><span class="nn">ptr</span><span class="p">::</span><span class="nf">slice_from_raw_parts_mut</span><span class="p">(</span><span class="n">bytes_ptr</span><span class="p">,</span> <span class="mi">32</span><span class="p">);</span>
</pre></td></tr><tr id="line-33" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>33</pre></td><td class="rouge-code"><pre>        <span class="k">let</span> <span class="n">slice</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="o">*</span><span class="n">slice_ptr</span> <span class="p">};</span> <span class="c1">// (5)</span>
</pre></td></tr><tr id="line-34" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>34</pre></td><td class="rouge-code"><pre>        <span class="n">slice</span><span class="p">[</span><span class="mi">0</span><span class="o">..</span><span class="mi">4</span><span class="p">]</span><span class="nf">.copy_from_slice</span><span class="p">(</span><span class="s">b"foo</span><span class="se">\0</span><span class="s">"</span><span class="p">);</span>
</pre></td></tr><tr id="line-35" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>35</pre></td><td class="rouge-code"><pre>        <span class="k">let</span> <span class="n">string</span> <span class="o">=</span> <span class="nn">CStr</span><span class="p">::</span><span class="nf">from_bytes_until_nul</span><span class="p">(</span><span class="o">&amp;</span><span class="n">slice</span><span class="p">[</span><span class="o">..</span><span class="p">])</span><span class="o">?</span><span class="nf">.to_str</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-36" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>36</pre></td><td class="rouge-code"><pre>        <span class="nd">println!</span><span class="p">(</span><span class="s">"{string}"</span><span class="p">);</span>
</pre></td></tr><tr id="line-37" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>37</pre></td><td class="rouge-code"><pre>    <span class="p">}</span>
</pre></td></tr><tr id="line-38" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>38</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-39" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>39</pre></td><td class="rouge-code"><pre>    <span class="k">unsafe</span> <span class="p">{</span> <span class="k">self</span><span class="p">::</span><span class="nn">sys</span><span class="p">::</span><span class="nf">munmap</span><span class="p">(</span><span class="n">map</span><span class="p">,</span> <span class="mi">32</span><span class="p">)</span> <span class="p">}</span><span class="o">?</span><span class="p">;</span> <span class="c1">// (6)</span>
</pre></td></tr><tr id="line-40" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>40</pre></td><td class="rouge-code"><pre>    <span class="nf">Ok</span><span class="p">(())</span>
</pre></td></tr><tr id="line-41" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>41</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr></tbody></table></div>

<p>This is very similar to the previous instance.  We open the file at (1),
fallocate on (2), load the file size at (3), mmap at (4), mutate at (5),
and unmap at (6).</p>

<p>However, this version holds a critical safety issue that the previous variant
didn’t. Can you spot it?</p>

<hr />

<p>The issue in this version is <code class="language-plaintext highlighter-rouge">libc::MAP_SHARED</code>. If we read the documentation
for <code class="language-plaintext highlighter-rouge">MAP_SHARED</code> again:</p>

<blockquote>
  <p>Share this mapping.  Updates to the mapping are visible to other processes
mapping the same region, and (in the case of file-backed mappings) are
carried through to the underlying file.</p>
</blockquote>

<p>In other words, if process A updates their mapping of the file, it should be
visible to process B. Or in other words, process A is able to mutate an object
in process B’s memory.</p>

<p>Conceptually, we can think of this as the kernel holding a mutable reference to
the memory returned by <code class="language-plaintext highlighter-rouge">mmap</code><sup id="fnref:kernel-mutable"><a href="#fn:kernel-mutable" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>. While the kernel holds a mutable
reference, we cannot create any sort of reference to any part of the memory
region[^atomic-memory] - which leaves us with no way to read/write to that
memory region in safe Rust!</p>

<p>It’s not just <code class="language-plaintext highlighter-rouge">MAP_SHARED</code>, either; if you don’t use <code class="language-plaintext highlighter-rouge">MAP_ANONYMOUS</code>, and
another process uses the same memory region as your process, it leads to the
same problem.</p>

<p>It’s a non-starter.</p>

<h2 id="what-now">What Now</h2>

<p>Unfortunately, there’s not much of a way around it. If you add invariants around
how the file is accessed - use OS abstractions to prevent concurrent fil
access, and file permissions, etc. - you can try to convince yourself that it
will be fine. But Linux doesn’t have mandatory file locking, and many different
concurrency issues could make it easy to trip yourself up. It’s possible to
use it as a interprocess communication mechanism, but there are plenty of other
forms of interprocess communication that don’t involve so many footguns (UNIX
sockets, named/unnamed pipes, TCP/IP sockets)…</p>

<p>Plus, using <code class="language-plaintext highlighter-rouge">mmap</code> to map files into memory hides errors that occur when reading
or writing to the file - and with very little guarantee that what you’re writing
will make it to disk<sup id="fnref:msync"><a href="#fn:msync" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>!</p>

<p>Maybe next time I’ll write about <code class="language-plaintext highlighter-rouge">fsync</code>…</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:address-space">
      <p>You probably shouldn’t be messing with this unless you know
what you’re doing. While the first parameter does allow you to specify where
you want the mapping to be, it’s generally better to let the system choose
the address for you - lest you accidentally clobber something else in your
address space. And there’s no real good way to determine where everything is
in your address space in a way that prevents race conditions. <a href="#fnref:address-space" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:mmap-failed">
      <p>Note that <code class="language-plaintext highlighter-rouge">mmap</code> doesn’t have to fail only because the OS ran
out of memory - it’s entirely possible that even when <code class="language-plaintext highlighter-rouge">mmap</code> returns, the OS
hasn’t even actually allocated the memory yet. (And when you first access
the map, it may trigger an OOM error.) It can fail because too much memory
has been <code class="language-plaintext highlighter-rouge">mmap</code>ed, or you passed invalid data (I love the man docs on this -
it says it can return <code class="language-plaintext highlighter-rouge">EINVAL</code> if  “we don’t like <code class="language-plaintext highlighter-rouge">addr</code>, <code class="language-plaintext highlighter-rouge">length</code>, or
<code class="language-plaintext highlighter-rouge">offset</code>”). <a href="#fnref:mmap-failed" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:write-ub">
      <p>While this is undefined behavior, it may not actually cause a
SEGFAULT or SEGBUS. The manual states that it would SEGBUS if we attempt to
access a page of the buffer that lies outside of the end of the file, but
since our file is so small, it fits in one page. In the Notes section, it
specifically states “the remaining bytes in the partial page at the end of
the mapping are zeroed when mapped, and modifications to that region are not
written out to the file.” <a href="#fnref:write-ub" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:read-write">
      <p>Something that I haven’t been able to determine is whether or
not it <em>does</em> actually need to be writable to construct a <code class="language-plaintext highlighter-rouge">&amp;mut</code> reference
to it; i.e., whether or not <code class="language-plaintext highlighter-rouge">PROT_WRITE</code> is required to hold a <code class="language-plaintext highlighter-rouge">&amp;mut</code>
reference to it. I suspect, given the declared safety constraints, that it
is not actually required - <code class="language-plaintext highlighter-rouge">&amp;mut</code> requires that (a) it is
dereferencable, and (b) no other mutable reference to the allocation exists.
Since, with <code class="language-plaintext highlighter-rouge">PROT_READ</code>, we can read from the <code class="language-plaintext highlighter-rouge">&amp;mut</code>, I could see the
argument that (a) is satisfied, even if you can’t <em>write</em> to it. This ties
back into the idea that <code class="language-plaintext highlighter-rouge">&amp;mut</code> is really just a way to express an exclusive
hold on the memory region, rather than actually mutating the memory region.
But since nothing I’ve read clearly indicated it, I’ll refrain from making
the assumption. <a href="#fnref:read-write" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:underlying-pattern">
      <p>Ok, <em>technically</em>, <code class="language-plaintext highlighter-rouge">mmap</code> by default clears uninitialized
pages - according to the man page, Linux 2.6.33 added the
<code class="language-plaintext highlighter-rouge">MAP_UNINITIALIZED</code> flag, which causes <code class="language-plaintext highlighter-rouge">mmap</code> to <em>not</em> clear the page.
This is only respected if the Linux kernel is configured to allow it. Let’s
say someone <code class="language-plaintext highlighter-rouge">mmap</code>s a random anonymous page, and the kernel sets up a
region of RAM to back that page. If the kernel did not clear the page, then
the process could read whatever was left in that region - even if that
region had previously held secrets that shouldn’t be shared! Thus, the
kernel is often configured <em>not</em> to allow it. The manual doesn’t specify
what “clearing” means, so it could just mean setting the bytes to an
arbitrary value - but most likely it just means setting all of the bytes
to null bytes. …which would be a valid bit pattern for a Rust <code class="language-plaintext highlighter-rouge">bool</code>.
Lucky! <a href="#fnref:underlying-pattern" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:kernel-mutable">
      <p>This… is a little more unclear. The <code class="language-plaintext highlighter-rouge">mmap</code> crate, archived
in 2022, bypasses this issue by washing its hands of it; it returns a raw
pointer for you to deal with. <code class="language-plaintext highlighter-rouge">memmap</code>/<code class="language-plaintext highlighter-rouge">memmap2</code> makes creating a
file-backed memory map unsafe, but doesn’t say what about it is unsafe.
<code class="language-plaintext highlighter-rouge">rustix</code> gives you a raw pointer. However, it is a fact that the contents
of the memory region can change due to out-of-process (or even in-process)
memory accesses, as enabled by the kernel.  Assumptions that the memory
can’t change if we don’t change it aren’t valid.
<code class="language-plaintext highlighter-rouge">read_volatile</code>/<code class="language-plaintext highlighter-rouge">write_volatile</code> may help (volatile prevents the compiler
from optimizing access to memory, especially since the “memory” can change
from under us), but it won’t help prevent read/write tears when another
process (or our process) changes the region in the file while we’re
reading/writing to it. (Unless, of course, you’re doing an atomic
read/write). Regardless, it’s unsafe.
[^atomic-memory]: Again, this may be fine for certain types - for example, if
your target platform guarantees atomic read/writes, then you could cast the
pointer to e.g. <code class="language-plaintext highlighter-rouge">&amp;[</code><a href="https://doc.rust-lang.org/std/sync/atomic/struct.AtomicU8.html"><code class="language-plaintext highlighter-rouge">AtomicU8</code></a><code class="language-plaintext highlighter-rouge">]</code>, and load each byte atomically… this
can be used to communicate across multiple threads using atomic load/store
instructions, but those already have an expectation of interior mutability -
which is the issue at hand. <a href="#fnref:kernel-mutable" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:msync">
      <p>Unless you use <a href="https://man7.org/linux/man-pages/man2/msync.2.html"><code class="language-plaintext highlighter-rouge">msync(2)</code></a>, but again, this hides any number of I/O
errors while trying to persist the changes to disk, and you’ll be none
the wiser. <a href="#fnref:msync" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="blog" /><summary type="html"><![CDATA[A dive into the underlying concepts of memory safety, and why many uses of mmap are inherently unsafe.]]></summary></entry><entry><title type="html">Things I Wish I Knew Before I Tried Implementing a Task Queue</title><link href="https://blog.telios.dev/blog/task-queue.html" rel="alternate" type="text/html" title="Things I Wish I Knew Before I Tried Implementing a Task Queue" /><published>2023-05-19T09:11:00+00:00</published><updated>2023-05-19T09:11:00+00:00</updated><id>https://blog.telios.dev/blog/task-queue</id><content type="html" xml:base="https://blog.telios.dev/blog/task-queue.html"><![CDATA[<p>Let’s say you’re writing a web application.  You have a long-running task that
you want to run in the background.  Maybe it’s a task that takes a long time to
complete, or maybe it’s a task that you want to run at a later time; or maybe
you just want to process it in aggregate somewhere else.  Either way, you don’t
want to block the user’s request while the task is running.</p>

<p>This is a very common problem.  In fact, it’s so common, there are a number of
services to help you solve it.  <a href="https://guides.rubyonrails.org/active_job_basics.html">ActiveJob</a> is a built-in solution for Ruby on
Rails.  <a href="https://docs.celeryproject.org/en/stable/">Celery</a> is a popular solution for Python.  <a href="https://sidekiq.org/">Sidekiq</a> is a popular
solution for Ruby.  If you don’t mind vendor lock-in, AWS’s <a href="https://aws.amazon.com/sns/">SNS</a> paired with
<a href="https://aws.amazon.com/sqs/">SQS</a> is a great solution; or Google Cloud’s <a href="https://cloud.google.com/pubsub">PubSub</a>.</p>

<p>But my application was small.  I don’t need anything too fancy for what I’m
doing; I just need something reliable that can handle a few hundred tasks a
day.  I don’t want to pay for a service, and I don’t want to have to manage
making everything Just Right™.  Surely, creating a task queue from scratch
wouldn’t be hard, right?  I could just use a Redis server to store the tasks,
and a worker process to pull them off the queue and process them.  How hard
could it be?</p>

<h2 id="the-basic-framework">The Basic Framework</h2>

<p>Let’s start with the basics.  We’ll need a way to add tasks to the queue, and a
way to process them.  We’ll also need a way to store the tasks.  We’ll choose
Redis here, as Redis is easy to set up, and has a number of data structures
that are useful for this task.</p>

<figure>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.5 -0.5 801 101" width="801px" height="101px"><use href="/assets/task-queue/basic-queue.svg#graphBasicQueue" /></svg>
    <figcaption>
        A diagram depicting a basic task queue.  On the left, there is a box labeled 'producers', in which producer 1 pushes into a set of tasks, t3, t2, t1, in a box labeled queue in the center; then, that gets pulled into consumer 1, in a box labeled consumers, on the right.
    </figcaption>
</figure>

<p>We have some number of producers that generate tasks and push them into this
queue.  These producers would be the front-end of our application; ideally, once
we’ve pushed into the queue, and we receive confirmation from the queue that it
has indeed received it, we should be confident that we won’t lose that
task<sup id="fnref:task-loss"><a href="#fn:task-loss" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>, so we can then return a response to the user.</p>

<p>Tasks should be processed in the order they are received, as often times later
tasks depend on earlier tasks to have been completed.  For example, if you
allow users to upload avatars to your application, you might want to process
those avatars before they are displayed to the user - but what if the user
changes the avatar <em>again</em> before the first one is processed?  You don’t want
to display the second avatar (as it was processed first), then the first avatar
(as it was processed second), so for some applications, ordering can be very
important.</p>

<p>Finally, you have the consumers, which pull tasks off the queue and process
them.  These would be servers that are running in the background, and are
responsible for actually doing the work.  They take a task off of the queue,
process it (for the avatar example, they might want to check the dimensions,
check that it is actually an image, resize it, virus check it, all sorts of
things), and save the results.</p>

<p>This is a good start.  In redis, all you’d need is a list - <a href="https://redis.io/commands/lpush"><code class="language-plaintext highlighter-rouge">LPUSH</code></a> items into
the list, and <a href="https://redis.io/commands/brpop"><code class="language-plaintext highlighter-rouge">BRPOP</code></a> them off on the consumer end.  You can even use any
version of Redis 2.0 and on, since only <code class="language-plaintext highlighter-rouge">BRPOP</code> is the most restrictive, being
available in 2.0.0 and on.  Simple.  Nothing can go wrong.</p>

<p>Well…</p>

<h3 id="what-can-go-wrong">What Can Go Wrong?</h3>

<p>It is often prudent to take a look at a system to see what aspects of it can
fail.  This very simplistic model assumes two major things:</p>

<ol>
  <li>The consumer will never fail to process a task.</li>
  <li>The consumer will never fail to acknowledge that it has processed - or
failed to process - a task.</li>
</ol>

<p>These seem very similar, but the second assumption is even more restrictive than
the first.  Let’s take a look at what happens if these assumptions are broken.</p>

<h4 id="the-consumer-fails-to-process-a-task">The Consumer Fails to Process a Task</h4>

<p>Let’s say that while processing an avatar, a transient fault occurs - maybe
there is a hiccup in the networking, and the consumer is unable to connect to
the database<sup id="fnref:task-state"><a href="#fn:task-state" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>.  As this connection is essential to completing the
task, the consumer cannot finish the task - the task has errored.  But it’s a
transient fault - the database is fine, and the consumer will be able to
connect to it again in a few seconds.</p>

<p>So we should <em>retry</em> the task.  But we shouldn’t retry it immediately - we’re
still unable to connect to the database.  Assuming we don’t know the nature of
the failure - when the error is happening, we can’t know it’s transient - we’ll
push the task back onto the queue using <a href="https://redis.io/commands/rpush"><code class="language-plaintext highlighter-rouge">RPUSH</code></a> to let another consumer that
is likely able to connect to the database.</p>

<p>But what if the issue is caused by the task itself?  What if the task is
malformed, and the consumer is unable to process it?  We can’t just keep
retrying the task, as it will never succeed.  We need to be able to handle
errors in the task itself.  So we’ll include a retry counter in the task, and
if the consumer fails to process the task, it will increment the retry counter
and push it back onto the queue.  If the retry counter exceeds some threshold,
we’ll just discard it.</p>

<p>But what if the database is down for a long time?  We don’t want to keep
discarding all tasks that are unable to connect to the database - that’s not
an issue with the task, that’s an issue with the database; but the retry counter
will keep incrementing, and eventually we’ll discard the task<sup id="fnref:retry-delay"><a href="#fn:retry-delay" class="footnote" rel="footnote" role="doc-noteref">3</a></sup>.  Or,
maybe we want to know what keeps causing issues with the task queue, so we can
fix it.  We can’t do that if we keep discarding the task.  So we’ll send tasks
that fail to connect to the database to a different queue, a “dead letter
queue” - a queue that is used to store tasks that have failed, and need to be
looked at by a human.  This way, we can keep track of what tasks are failing,
and why, and we can fix the issue.</p>

<h4 id="the-consumer-fails-to-acknowledge-a-task">The Consumer Fails to Acknowledge a Task</h4>

<p>Let’s take another look at the above scenario.  The consumer is processing an
avatar, and a fault occurs - only, instead of being a recoverable error, let’s
say that the consumer process is killed randomly<sup id="fnref:random-death"><a href="#fn:random-death" class="footnote" rel="footnote" role="doc-noteref">4</a></sup>.  The task is
lost to the abyss.  As the process itself was the one keeping track of the task,
including notifying the queue when it succeeded (or failed), if the process is
unable to do so, the task is lost, never to be completed.  A failure to
acknowledge a task, to the queue, is success, rather than failure.</p>

<p>So we need to be able to handle this.  We need to be able to detect when a
consumer has died, and requeue the task.  But how do we know when a consumer
has died?  This is a hard problem<sup id="fnref:hard-problem"><a href="#fn:hard-problem" class="footnote" rel="footnote" role="doc-noteref">5</a></sup>.  We can’t just check if the
process is running, as it might be just spinning its wheels, doing nothing.
Or it could just be working away at the task, invisible to the outside world.
For the sake of reducing the complexity, we’ll put a timeout on how long it
takes to process a task.  If a consumer takes longer than <em>n</em> seconds to
process a task, we’ll assume it has died, and requeue the task.</p>

<p>Of course, we can’t rely on the consumer to tell us how long it’s taking to
process a task - if it hard crashed, it won’t be able to tell us anything.
So the queue system now needs to keep track of the consumer, and how long it’s
taking to process a task.  This is a bit of a problem, as the queue system
doesn’t know anything about the consumer, the way we’ve set it up - it just
has a list of items, maybe two, and that’s it.  It doesn’t know who the
consumers are, let alone which consumers have which task, let alone how long
it’s taking them to process the task.  So we need to add some sort of
registration system to the queue, so that consumers can register themselves
with the queue, and the queue can keep track of them.</p>

<p>Thus, for the sake of reliability, <code class="language-plaintext highlighter-rouge">LPUSH</code> and <code class="language-plaintext highlighter-rouge">BRPOP</code> are not enough.</p>

<h2 id="take-two">Take Two</h2>

<p>This time, we’ll try to resolve the issues we ran into above.  We’ll start
with the same basic setup - a producer pushes into a queue, and a consumer
pops from the queue.  But this time, we’ll add a few more things.</p>

<figure>
    <svg xmlns="http://www.w3.org/2000/svg" width="817px" height="397px" viewBox="-0.5 -0.5 817 397"><use href="/assets/task-queue/take-two.svg#graphTakeTwo" /></svg>
    <figcaption>
        Similar to the previous image.  The producers on the left are pushing into the queue; specifically, the 'general queue' under the queue label.  The general queue is pushing into 3 consumer-specific queues underneath it, with varying numbers of items in each.  Below that is an empty queue labled 'dead letter queue'.  The consumer-specific queues are pushing into 3 separate consumers under the 'consumer' box on the right; one for each.
    </figcaption>
</figure>

<p>The same general outline is here.  We’ve got the producers pushing into the
queue, and consumers pulling items from the queue.  But this time, we’ve got
a few more things going on.  We’ll focus on the queue system, as that’s where
most of the changes are.</p>

<p>Instead of having one fixed queue for all consumers to pull from, we have one
“general queue,” where all of the items that are pushed into the queue are
pushed into.  Then, we have a number of consumer-specific queues, one for each
consumer.  When a consumer is ready to consume a task, it pops from its
consumer-specific queue.  If the consumer-specific queue is empty, it pops from
the general queue.  This way, we can have multiple consumers pulling from the
same queue.  The consumer-specific queue should only contain items that the
consumer <em>is currently working on</em>; not items that the consumer
<em>will work on</em><sup id="fnref:csq-tense"><a href="#fn:csq-tense" class="footnote" rel="footnote" role="doc-noteref">6</a></sup>.  This is important.  At the same time, the consumer
registers its presence with the queue, so that the queue knows which consumers
are currently active.  This way, the queue can keep track of which consumers
are currently active, and which consumers are currently working on which tasks.</p>

<p>If a consumer dies, the items in its queue can be released back into the
general queue, and another consumer can pick them up.  This way, we can
detect when a consumer has died, and requeue the items that it was working on,
preventing tasks from being lost.  Note, however, that this <em>necessarily</em>
requires that tasks can be processed more than once - if a consumer fails just
before it would have acknowledged the task, and the task is requeued, then
the task will be processed twice.  This is a tradeoff that we have to make,
in order to ensure that tasks are not processed zero times.</p>

<p>If a task fails on a consumer, the task can be pushed back into the general
queue and have its retry counter incremented.  This way, the task can be
retried on a potentially different consumer, and the consumer that failed
can continue processing other tasks.  If it fails too many times, it can be
pushed into a “dead letter queue,” where it can be inspected by a human, and
the task can be retried manually.</p>

<p>Interestingly, this can still be implemented with Redis tasks.  The producer
can <code class="language-plaintext highlighter-rouge">LPUSH</code> into the general queue.  When a consumer starts, it must set a key
specific to that consumer and queue (e.g. <code class="language-plaintext highlighter-rouge">queue:name:consumer:1:active</code>), and
set the expiration to the timeout for liveness (e.g. 30 minutes; the consumer
must then take care to “touch” the key half of that interval, to ensure that
the key doesn’t expire while it’s alive), and add itself to the list of
consumers for that queue.  Then, when a consumer is ready to consume a task, it
can do the following, in order:</p>

<ol>
  <li>Check its own queue for a task.  If a consumer crashed while processing a
task, and is able to come back up to operation within liveness time, the task
will still be in the queue, and the consumer can pick it up again.  It might
make sense to increment the retry counter here on the task, as it’s possible
that the consuemr crashed because of a bug in the task; this is an
implementation detail, though.</li>
  <li>Check all other consumers’ queues for task.  As all consumers add their
information to the queue system, we know which consumers have been active;
we can then filter down based on which aren’t currently, and for those that
aren’t currently, we can <em>steal</em> their tasks, assigning them to ourselves.
See (3) on how to move a task from one queue to another.</li>
  <li>If we’re unable to get a task any other way, then we can try pulling from the
general queue.  In version 6.2.0, Redis added <a href="https://redis.io/commands/blmove"><code class="language-plaintext highlighter-rouge">BLMOVE</code></a> as a command - this
takes an item from one list, and moves it to another list, blocking if the
former does not have any items for the given timeout.  (It also allows you
to specify which direction to remove and append the item, which is nice.)
But before that, there was <a href="https://redis.io/commands/brpoplpush"><code class="language-plaintext highlighter-rouge">BRPOPLPUSH</code></a> (available since 2.2.0); both
commands describe how to create a reliable queue system in Redis, the same
way we’re doing it!  These commands are atomic - popping one item off from
the general queue and pushing them onto the consumer-specific queue is
done in one operation, ensuring that we’re the only one that gets the
task<sup id="fnref:task-exclusivity"><a href="#fn:task-exclusivity" class="footnote" rel="footnote" role="doc-noteref">7</a></sup>.</li>
</ol>

<p>To be fair, this isn’t incredibly efficient - there are multiple round trips
here, and the number of round trips (if not careful) can potentially scale with
the number of consumers<sup id="fnref:round-trips"><a href="#fn:round-trips" class="footnote" rel="footnote" role="doc-noteref">8</a></sup>.  But it’s a basic system that can be
implemented pretty easily.</p>

<p>So, a producer’s code might look like this:</p>

<div class="language-rust highlighter-rouge"><table class="rouge-line-table"><tbody><tr id="line-1" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>1</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">collections</span><span class="p">::</span><span class="n">HashSet</span><span class="p">;</span>
</pre></td></tr><tr id="line-2" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>2</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-3" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>3</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">anyhow</span><span class="p">::</span><span class="n">Context</span><span class="p">;</span>
</pre></td></tr><tr id="line-4" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>4</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">redis</span><span class="p">::</span><span class="nn">aio</span><span class="p">::</span><span class="n">Connection</span><span class="p">;</span>
</pre></td></tr><tr id="line-5" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>5</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">redis</span><span class="p">::{</span><span class="n">AsyncCommands</span><span class="p">,</span> <span class="n">Client</span><span class="p">,</span> <span class="n">Direction</span><span class="p">};</span>
</pre></td></tr><tr id="line-6" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>6</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-7" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>7</pre></td><td class="rouge-code"><pre><span class="k">static</span> <span class="n">TASK_QUEUE_KEY</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span> <span class="s">"queue:main:tasks"</span><span class="p">;</span>
</pre></td></tr><tr id="line-8" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>8</pre></td><td class="rouge-code"><pre><span class="k">static</span> <span class="n">CONSUMER_SET_KEY</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span> <span class="s">"queue:main:consumers"</span><span class="p">;</span>
</pre></td></tr><tr id="line-9" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>9</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-10" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>10</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">consumer_liveness_key</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">String</span> <span class="p">{</span>
</pre></td></tr><tr id="line-11" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>11</pre></td><td class="rouge-code"><pre>    <span class="nd">format!</span><span class="p">(</span><span class="s">"queue:main:consumers:active:{}"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>
</pre></td></tr><tr id="line-12" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>12</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr><tr id="line-13" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>13</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-14" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>14</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">consumer_processing_list_key</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">String</span> <span class="p">{</span>
</pre></td></tr><tr id="line-15" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>15</pre></td><td class="rouge-code"><pre>    <span class="nd">format!</span><span class="p">(</span><span class="s">"queue:main:consumers:list:{}"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>
</pre></td></tr><tr id="line-16" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>16</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr><tr id="line-17" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>17</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-18" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>18</pre></td><td class="rouge-code"><pre><span class="nd">#[tokio::main]</span>
</pre></td></tr><tr id="line-19" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>19</pre></td><td class="rouge-code"><pre><span class="k">async</span> <span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nn">anyhow</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="p">{</span>
</pre></td></tr><tr id="line-20" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>20</pre></td><td class="rouge-code"><pre>    <span class="c1">// This will only fail if the url is badly formatted.</span>
</pre></td></tr><tr id="line-21" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>21</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">client</span> <span class="o">=</span> <span class="nn">Client</span><span class="p">::</span><span class="nf">open</span><span class="p">(</span><span class="s">"redis://localhost:6379/10"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-22" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>22</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="k">mut</span> <span class="n">connection</span> <span class="o">=</span> <span class="n">client</span>
</pre></td></tr><tr id="line-23" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>23</pre></td><td class="rouge-code"><pre>        <span class="nf">.get_async_connection</span><span class="p">()</span>
</pre></td></tr><tr id="line-24" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>24</pre></td><td class="rouge-code"><pre>        <span class="k">.await</span>
</pre></td></tr><tr id="line-25" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>25</pre></td><td class="rouge-code"><pre>        <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to open connection to redis server!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-26" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>26</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">message</span> <span class="o">=</span> <span class="nn">serde_json</span><span class="p">::</span><span class="nf">to_string</span><span class="p">(</span><span class="o">&amp;</span><span class="nn">serde_json</span><span class="p">::</span><span class="nd">json!</span><span class="p">({</span>
</pre></td></tr><tr id="line-27" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>27</pre></td><td class="rouge-code"><pre>        <span class="s">"id"</span><span class="p">:</span> <span class="nn">uuid</span><span class="p">::</span><span class="nn">Uuid</span><span class="p">::</span><span class="nf">new_v4</span><span class="p">(),</span>
</pre></td></tr><tr id="line-28" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>28</pre></td><td class="rouge-code"><pre>        <span class="s">"payload"</span><span class="p">:</span> <span class="p">{}</span>
</pre></td></tr><tr id="line-29" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>29</pre></td><td class="rouge-code"><pre>    <span class="p">}))</span>
</pre></td></tr><tr id="line-30" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>30</pre></td><td class="rouge-code"><pre>    <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to serialize message!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-31" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>31</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-32" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>32</pre></td><td class="rouge-code"><pre>    <span class="n">connection</span>
</pre></td></tr><tr id="line-33" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>33</pre></td><td class="rouge-code"><pre>        <span class="nf">.rpush</span><span class="p">(</span><span class="n">TASK_QUEUE_KEY</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
</pre></td></tr><tr id="line-34" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>34</pre></td><td class="rouge-code"><pre>        <span class="k">.await</span>
</pre></td></tr><tr id="line-35" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>35</pre></td><td class="rouge-code"><pre>        <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to push message to queue!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-36" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>36</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-37" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>37</pre></td><td class="rouge-code"><pre>    <span class="nf">Ok</span><span class="p">(())</span>
</pre></td></tr><tr id="line-38" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>38</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr></tbody></table></div>

<p>And a consumer’s code might look like this:</p>

<div class="language-rust highlighter-rouge"><table class="rouge-line-table"><tbody><tr id="line-1" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>1</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">collections</span><span class="p">::</span><span class="n">HashSet</span><span class="p">;</span>
</pre></td></tr><tr id="line-2" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>2</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-3" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>3</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">anyhow</span><span class="p">::</span><span class="n">Context</span><span class="p">;</span>
</pre></td></tr><tr id="line-4" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>4</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">redis</span><span class="p">::</span><span class="nn">aio</span><span class="p">::</span><span class="n">Connection</span><span class="p">;</span>
</pre></td></tr><tr id="line-5" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>5</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">redis</span><span class="p">::{</span><span class="n">AsyncCommands</span><span class="p">,</span> <span class="n">Client</span><span class="p">,</span> <span class="n">Direction</span><span class="p">};</span>
</pre></td></tr><tr id="line-6" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>6</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-7" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>7</pre></td><td class="rouge-code"><pre><span class="k">static</span> <span class="n">TASK_QUEUE_KEY</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span> <span class="s">"queue:main:tasks"</span><span class="p">;</span>
</pre></td></tr><tr id="line-8" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>8</pre></td><td class="rouge-code"><pre><span class="k">static</span> <span class="n">CONSUMER_SET_KEY</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span> <span class="s">"queue:main:consumers"</span><span class="p">;</span>
</pre></td></tr><tr id="line-9" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>9</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-10" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>10</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">consumer_liveness_key</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">String</span> <span class="p">{</span>
</pre></td></tr><tr id="line-11" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>11</pre></td><td class="rouge-code"><pre>    <span class="nd">format!</span><span class="p">(</span><span class="s">"queue:main:consumers:active:{}"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>
</pre></td></tr><tr id="line-12" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>12</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr><tr id="line-13" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>13</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-14" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>14</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">consumer_processing_list_key</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">String</span> <span class="p">{</span>
</pre></td></tr><tr id="line-15" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>15</pre></td><td class="rouge-code"><pre>    <span class="nd">format!</span><span class="p">(</span><span class="s">"queue:main:consumers:list:{}"</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>
</pre></td></tr><tr id="line-16" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>16</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr><tr id="line-17" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>17</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-18" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>18</pre></td><td class="rouge-code"><pre><span class="nd">#[tokio::main]</span>
</pre></td></tr><tr id="line-19" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>19</pre></td><td class="rouge-code"><pre><span class="k">async</span> <span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nn">anyhow</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="p">{</span>
</pre></td></tr><tr id="line-20" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>20</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">client</span> <span class="o">=</span> <span class="nn">Client</span><span class="p">::</span><span class="nf">open</span><span class="p">(</span><span class="s">"redis://localhost:6379/10"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-21" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>21</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="k">mut</span> <span class="n">connection</span> <span class="o">=</span> <span class="n">client</span>
</pre></td></tr><tr id="line-22" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>22</pre></td><td class="rouge-code"><pre>        <span class="nf">.get_async_connection</span><span class="p">()</span>
</pre></td></tr><tr id="line-23" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>23</pre></td><td class="rouge-code"><pre>        <span class="k">.await</span>
</pre></td></tr><tr id="line-24" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>24</pre></td><td class="rouge-code"><pre>        <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to open connection to redis server!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-25" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>25</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-26" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>26</pre></td><td class="rouge-code"><pre>    <span class="c1">// define the consumer's name.</span>
</pre></td></tr><tr id="line-27" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>27</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">consumer_name</span> <span class="o">=</span> <span class="s">"consumer-1"</span><span class="p">;</span>
</pre></td></tr><tr id="line-28" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>28</pre></td><td class="rouge-code"><pre>    <span class="c1">// define the blocking timeout for blmove in milliseconds.</span>
</pre></td></tr><tr id="line-29" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>29</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">blocking_timeout_ms</span> <span class="o">=</span> <span class="mi">5000</span><span class="p">;</span>
</pre></td></tr><tr id="line-30" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>30</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-31" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>31</pre></td><td class="rouge-code"><pre>    <span class="c1">// Set our liveness key to 1, with an expiration of 30 minutes.</span>
</pre></td></tr><tr id="line-32" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>32</pre></td><td class="rouge-code"><pre>    <span class="c1">// Once this expires we're considered dead.</span>
</pre></td></tr><tr id="line-33" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>33</pre></td><td class="rouge-code"><pre>    <span class="c1">// It is an excercise for the reader to implement a heartbeat.</span>
</pre></td></tr><tr id="line-34" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>34</pre></td><td class="rouge-code"><pre>    <span class="n">connection</span>
</pre></td></tr><tr id="line-35" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>35</pre></td><td class="rouge-code"><pre>        <span class="nf">.set_ex</span><span class="p">(</span>
</pre></td></tr><tr id="line-36" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>36</pre></td><td class="rouge-code"><pre>            <span class="nf">consumer_liveness_key</span><span class="p">(</span><span class="n">consumer_name</span><span class="p">),</span>
</pre></td></tr><tr id="line-37" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>37</pre></td><td class="rouge-code"><pre>            <span class="s">"1"</span><span class="p">,</span>
</pre></td></tr><tr id="line-38" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>38</pre></td><td class="rouge-code"><pre>            <span class="mi">1800</span><span class="p">,</span> <span class="c1">// 30 minutes</span>
</pre></td></tr><tr id="line-39" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>39</pre></td><td class="rouge-code"><pre>        <span class="p">)</span>
</pre></td></tr><tr id="line-40" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>40</pre></td><td class="rouge-code"><pre>        <span class="k">.await</span>
</pre></td></tr><tr id="line-41" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>41</pre></td><td class="rouge-code"><pre>        <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to set the liveness key!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-42" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>42</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-43" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>43</pre></td><td class="rouge-code"><pre>    <span class="c1">// Now, we can add ourselves to the consumer set.</span>
</pre></td></tr><tr id="line-44" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>44</pre></td><td class="rouge-code"><pre>    <span class="n">connection</span>
</pre></td></tr><tr id="line-45" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>45</pre></td><td class="rouge-code"><pre>        <span class="nf">.sadd</span><span class="p">(</span><span class="n">CONSUMER_SET_KEY</span><span class="p">,</span> <span class="n">consumer_name</span><span class="p">)</span>
</pre></td></tr><tr id="line-46" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>46</pre></td><td class="rouge-code"><pre>        <span class="k">.await</span>
</pre></td></tr><tr id="line-47" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>47</pre></td><td class="rouge-code"><pre>        <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to add consumer to set!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-48" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>48</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-49" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>49</pre></td><td class="rouge-code"><pre>    <span class="k">loop</span> <span class="p">{</span>
</pre></td></tr><tr id="line-50" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>50</pre></td><td class="rouge-code"><pre>        <span class="k">let</span> <span class="n">incoming</span> <span class="o">=</span> <span class="nf">next</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">connection</span><span class="p">,</span> <span class="n">consumer_name</span><span class="p">,</span>
</pre></td></tr><tr id="line-51" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>51</pre></td><td class="rouge-code"><pre>                <span class="n">blocking_timeout_ms</span><span class="p">)</span>
</pre></td></tr><tr id="line-52" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>52</pre></td><td class="rouge-code"><pre>            <span class="k">.await</span>
</pre></td></tr><tr id="line-53" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>53</pre></td><td class="rouge-code"><pre>            <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to load next task!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-54" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>54</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-55" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>55</pre></td><td class="rouge-code"><pre>        <span class="c1">// Pushing messages into a DLQ upon failure is an excercise</span>
</pre></td></tr><tr id="line-56" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>56</pre></td><td class="rouge-code"><pre>        <span class="c1">// for the reader.</span>
</pre></td></tr><tr id="line-57" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>57</pre></td><td class="rouge-code"><pre>        <span class="k">for</span> <span class="n">message</span> <span class="k">in</span> <span class="n">incoming</span> <span class="p">{</span>
</pre></td></tr><tr id="line-58" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>58</pre></td><td class="rouge-code"><pre>            <span class="k">let</span> <span class="n">task</span> <span class="o">=</span> <span class="nn">serde_json</span><span class="p">::</span><span class="nf">from_str</span><span class="p">(</span><span class="o">&amp;</span><span class="n">message</span><span class="p">)</span>
</pre></td></tr><tr id="line-59" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>59</pre></td><td class="rouge-code"><pre>                <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to deserialize message!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-60" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>60</pre></td><td class="rouge-code"><pre>            <span class="nf">process</span><span class="p">(</span><span class="n">task</span><span class="p">)</span><span class="nf">.context</span><span class="p">(</span><span class="s">"failed to process message!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-61" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>61</pre></td><td class="rouge-code"><pre>        <span class="p">}</span>
</pre></td></tr><tr id="line-62" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>62</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-63" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>63</pre></td><td class="rouge-code"><pre>        <span class="c1">// We've finished processing, so we can remove the items in the</span>
</pre></td></tr><tr id="line-64" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>64</pre></td><td class="rouge-code"><pre>        <span class="c1">// processing list.</span>
</pre></td></tr><tr id="line-65" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>65</pre></td><td class="rouge-code"><pre>        <span class="n">connection</span>
</pre></td></tr><tr id="line-66" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>66</pre></td><td class="rouge-code"><pre>            <span class="nf">.del</span><span class="p">(</span><span class="nf">consumer_processing_list_key</span><span class="p">(</span><span class="n">consumer_name</span><span class="p">))</span>
</pre></td></tr><tr id="line-67" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>67</pre></td><td class="rouge-code"><pre>            <span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-68" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>68</pre></td><td class="rouge-code"><pre>    <span class="p">}</span>
</pre></td></tr><tr id="line-69" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>69</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr><tr id="line-70" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>70</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-71" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>71</pre></td><td class="rouge-code"><pre><span class="k">async</span> <span class="k">fn</span> <span class="nf">next</span><span class="p">(</span>
</pre></td></tr><tr id="line-72" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>72</pre></td><td class="rouge-code"><pre>    <span class="n">connection</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Connection</span><span class="p">,</span>
</pre></td></tr><tr id="line-73" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>73</pre></td><td class="rouge-code"><pre>    <span class="n">consumer_name</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span>
</pre></td></tr><tr id="line-74" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>74</pre></td><td class="rouge-code"><pre>    <span class="n">timeout</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
</pre></td></tr><tr id="line-75" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>75</pre></td><td class="rouge-code"><pre><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="p">,</span> <span class="nn">anyhow</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="p">{</span>
</pre></td></tr><tr id="line-76" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>76</pre></td><td class="rouge-code"><pre>    <span class="nv">'retry</span><span class="p">:</span> <span class="k">loop</span> <span class="p">{</span>
</pre></td></tr><tr id="line-77" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>77</pre></td><td class="rouge-code"><pre>        <span class="c1">// Load our processing list.</span>
</pre></td></tr><tr id="line-78" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>78</pre></td><td class="rouge-code"><pre>        <span class="k">let</span> <span class="n">processing_list</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span> <span class="o">=</span> <span class="n">connection</span>
</pre></td></tr><tr id="line-79" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>79</pre></td><td class="rouge-code"><pre>            <span class="nf">.lrange</span><span class="p">(</span><span class="nf">consumer_processing_list_key</span><span class="p">(</span><span class="n">consumer_name</span><span class="p">),</span> <span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
</pre></td></tr><tr id="line-80" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>80</pre></td><td class="rouge-code"><pre>            <span class="k">.await</span>
</pre></td></tr><tr id="line-81" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>81</pre></td><td class="rouge-code"><pre>            <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to load processing list!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-82" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>82</pre></td><td class="rouge-code"><pre>        <span class="c1">// We've received an item from our processing list, so we can</span>
</pre></td></tr><tr id="line-83" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>83</pre></td><td class="rouge-code"><pre>        <span class="c1">// return it.  This is where we'd normally do some sort of</span>
</pre></td></tr><tr id="line-84" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>84</pre></td><td class="rouge-code"><pre>        <span class="c1">// retry counter for the message.</span>
</pre></td></tr><tr id="line-85" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>85</pre></td><td class="rouge-code"><pre>        <span class="k">if</span> <span class="o">!</span><span class="n">processing_list</span><span class="nf">.is_empty</span><span class="p">()</span> <span class="p">{</span>
</pre></td></tr><tr id="line-86" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>86</pre></td><td class="rouge-code"><pre>            <span class="c1">// This is the only return in this function to make sure</span>
</pre></td></tr><tr id="line-87" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>87</pre></td><td class="rouge-code"><pre>            <span class="c1">// that whatever we return is in the processing list.</span>
</pre></td></tr><tr id="line-88" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>88</pre></td><td class="rouge-code"><pre>            <span class="k">return</span> <span class="nf">Ok</span><span class="p">(</span><span class="n">processing_list</span><span class="p">);</span>
</pre></td></tr><tr id="line-89" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>89</pre></td><td class="rouge-code"><pre>        <span class="p">}</span>
</pre></td></tr><tr id="line-90" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>90</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-91" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>91</pre></td><td class="rouge-code"><pre>        <span class="c1">// Now, we perform "work stealing." Get the list of consumers,</span>
</pre></td></tr><tr id="line-92" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>92</pre></td><td class="rouge-code"><pre>        <span class="c1">// excluding ourselves, and check if any of them are dead.</span>
</pre></td></tr><tr id="line-93" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>93</pre></td><td class="rouge-code"><pre>        <span class="c1">// If so, move something to ourselves.</span>
</pre></td></tr><tr id="line-94" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>94</pre></td><td class="rouge-code"><pre>        <span class="k">let</span> <span class="k">mut</span> <span class="n">consumers</span><span class="p">:</span> <span class="n">HashSet</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span> <span class="o">=</span> <span class="n">connection</span>
</pre></td></tr><tr id="line-95" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>95</pre></td><td class="rouge-code"><pre>            <span class="nf">.smembers</span><span class="p">(</span><span class="n">CONSUMER_SET_KEY</span><span class="p">)</span>
</pre></td></tr><tr id="line-96" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>96</pre></td><td class="rouge-code"><pre>            <span class="k">.await</span>
</pre></td></tr><tr id="line-97" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>97</pre></td><td class="rouge-code"><pre>            <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to load consumer set!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-98" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>98</pre></td><td class="rouge-code"><pre>        <span class="n">consumers</span><span class="nf">.remove</span><span class="p">(</span><span class="n">consumer_name</span><span class="p">);</span>
</pre></td></tr><tr id="line-99" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>99</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-100" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>100</pre></td><td class="rouge-code"><pre>        <span class="k">for</span> <span class="n">consumer</span> <span class="k">in</span> <span class="n">consumers</span> <span class="p">{</span>
</pre></td></tr><tr id="line-101" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>101</pre></td><td class="rouge-code"><pre>            <span class="k">const</span> <span class="n">LIVENESS_MESSAGE</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span>
</pre></td></tr><tr id="line-102" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>102</pre></td><td class="rouge-code"><pre>                <span class="s">"failed to load external consumer's liveness key"</span><span class="p">;</span>
</pre></td></tr><tr id="line-103" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>103</pre></td><td class="rouge-code"><pre>            <span class="c1">// redis's exists returns a 1 if the key exists, 0 otherwise.</span>
</pre></td></tr><tr id="line-104" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>104</pre></td><td class="rouge-code"><pre>            <span class="k">let</span> <span class="n">exists</span><span class="p">:</span> <span class="nb">u32</span> <span class="o">=</span> <span class="n">connection</span>
</pre></td></tr><tr id="line-105" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>105</pre></td><td class="rouge-code"><pre>                <span class="nf">.exists</span><span class="p">(</span><span class="nf">consumer_liveness_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">consumer</span><span class="p">))</span>
</pre></td></tr><tr id="line-106" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>106</pre></td><td class="rouge-code"><pre>                <span class="k">.await</span>
</pre></td></tr><tr id="line-107" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>107</pre></td><td class="rouge-code"><pre>                <span class="nf">.context</span><span class="p">(</span><span class="n">LIVENESS_MESSAGE</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-108" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>108</pre></td><td class="rouge-code"><pre>            <span class="k">if</span> <span class="n">exists</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
</pre></td></tr><tr id="line-109" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>109</pre></td><td class="rouge-code"><pre>                <span class="k">const</span> <span class="n">DEAD_MESSAGE</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span>
</pre></td></tr><tr id="line-110" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>110</pre></td><td class="rouge-code"><pre>                    <span class="s">"failed to move message from dead consumer!"</span><span class="p">;</span>
</pre></td></tr><tr id="line-111" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>111</pre></td><td class="rouge-code"><pre>                <span class="c1">// The consumer is dead, so we can move something from</span>
</pre></td></tr><tr id="line-112" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>112</pre></td><td class="rouge-code"><pre>                <span class="c1">// their processing list to ours.</span>
</pre></td></tr><tr id="line-113" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>113</pre></td><td class="rouge-code"><pre>                <span class="k">let</span> <span class="n">message</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span> <span class="o">=</span> <span class="n">connection</span>
</pre></td></tr><tr id="line-114" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>114</pre></td><td class="rouge-code"><pre>                    <span class="nf">.lmove</span><span class="p">(</span>
</pre></td></tr><tr id="line-115" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>115</pre></td><td class="rouge-code"><pre>                        <span class="nf">consumer_processing_list_key</span><span class="p">(</span><span class="o">&amp;</span><span class="n">consumer</span><span class="p">),</span>
</pre></td></tr><tr id="line-116" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>116</pre></td><td class="rouge-code"><pre>                        <span class="nf">consumer_processing_list_key</span><span class="p">(</span><span class="n">consumer_name</span><span class="p">),</span>
</pre></td></tr><tr id="line-117" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>117</pre></td><td class="rouge-code"><pre>                        <span class="nn">redis</span><span class="p">::</span><span class="nn">Direction</span><span class="p">::</span><span class="n">Right</span><span class="p">,</span>
</pre></td></tr><tr id="line-118" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>118</pre></td><td class="rouge-code"><pre>                        <span class="nn">redis</span><span class="p">::</span><span class="nn">Direction</span><span class="p">::</span><span class="n">Left</span><span class="p">,</span>
</pre></td></tr><tr id="line-119" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>119</pre></td><td class="rouge-code"><pre>                    <span class="p">)</span>
</pre></td></tr><tr id="line-120" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>120</pre></td><td class="rouge-code"><pre>                    <span class="k">.await</span>
</pre></td></tr><tr id="line-121" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>121</pre></td><td class="rouge-code"><pre>                    <span class="nf">.context</span><span class="p">(</span><span class="n">DEAD_MESSAGE</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-122" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>122</pre></td><td class="rouge-code"><pre>                <span class="k">if</span> <span class="n">message</span><span class="nf">.is_some</span><span class="p">()</span> <span class="p">{</span>
</pre></td></tr><tr id="line-123" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>123</pre></td><td class="rouge-code"><pre>                    <span class="c1">// We've successfully stolen a message, so we can</span>
</pre></td></tr><tr id="line-124" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>124</pre></td><td class="rouge-code"><pre>                    <span class="c1">// return it.</span>
</pre></td></tr><tr id="line-125" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>125</pre></td><td class="rouge-code"><pre>                    <span class="k">continue</span> <span class="nv">'retry</span><span class="p">;</span>
</pre></td></tr><tr id="line-126" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>126</pre></td><td class="rouge-code"><pre>                <span class="p">}</span>
</pre></td></tr><tr id="line-127" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>127</pre></td><td class="rouge-code"><pre>            <span class="p">}</span>
</pre></td></tr><tr id="line-128" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>128</pre></td><td class="rouge-code"><pre>        <span class="p">}</span>
</pre></td></tr><tr id="line-129" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>129</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-130" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>130</pre></td><td class="rouge-code"><pre>        <span class="c1">// No consumers to steal from, and no items in our queue, so</span>
</pre></td></tr><tr id="line-131" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>131</pre></td><td class="rouge-code"><pre>        <span class="c1">// remove from the main queue and add to our processing list.</span>
</pre></td></tr><tr id="line-132" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>132</pre></td><td class="rouge-code"><pre>        <span class="n">connection</span>
</pre></td></tr><tr id="line-133" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>133</pre></td><td class="rouge-code"><pre>            <span class="nf">.blmove</span><span class="p">(</span>
</pre></td></tr><tr id="line-134" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>134</pre></td><td class="rouge-code"><pre>                <span class="n">TASK_QUEUE_KEY</span><span class="p">,</span>
</pre></td></tr><tr id="line-135" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>135</pre></td><td class="rouge-code"><pre>                <span class="nf">consumer_processing_list_key</span><span class="p">(</span><span class="n">consumer_name</span><span class="p">),</span>
</pre></td></tr><tr id="line-136" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>136</pre></td><td class="rouge-code"><pre>                <span class="nn">Direction</span><span class="p">::</span><span class="n">Right</span><span class="p">,</span>
</pre></td></tr><tr id="line-137" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>137</pre></td><td class="rouge-code"><pre>                <span class="nn">Direction</span><span class="p">::</span><span class="n">Left</span><span class="p">,</span>
</pre></td></tr><tr id="line-138" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>138</pre></td><td class="rouge-code"><pre>                <span class="n">timeout</span><span class="p">,</span>
</pre></td></tr><tr id="line-139" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>139</pre></td><td class="rouge-code"><pre>            <span class="p">)</span>
</pre></td></tr><tr id="line-140" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>140</pre></td><td class="rouge-code"><pre>            <span class="k">.await</span>
</pre></td></tr><tr id="line-141" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>141</pre></td><td class="rouge-code"><pre>            <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to pop message from queue!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-142" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>142</pre></td><td class="rouge-code"><pre>    <span class="p">}</span>
</pre></td></tr><tr id="line-143" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>143</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr><tr id="line-144" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>144</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-145" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>145</pre></td><td class="rouge-code"><pre><span class="k">fn</span> <span class="nf">process</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="nn">serde_json</span><span class="p">::</span><span class="n">Value</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nn">anyhow</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="p">{</span>
</pre></td></tr><tr id="line-146" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>146</pre></td><td class="rouge-code"><pre>    <span class="c1">// Do something with the message.</span>
</pre></td></tr><tr id="line-147" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>147</pre></td><td class="rouge-code"><pre>    <span class="nd">println!</span><span class="p">(</span><span class="s">"processing message: {:?}"</span><span class="p">,</span> <span class="n">message</span><span class="p">);</span>
</pre></td></tr><tr id="line-148" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>148</pre></td><td class="rouge-code"><pre>    <span class="nf">Ok</span><span class="p">(())</span>
</pre></td></tr><tr id="line-149" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>149</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr></tbody></table></div>

<p>Packaging this up into a library, adding quality of life features, retries, and
the liveness check are all left as exercises for the reader.  I also make no
guarantees that the above code is bug-free, but it should be a good starting
point - it should <em>not</em> be used in production, especially not as-is.</p>

<p>It’s a shame that Redis doesn’t have a built-in for this, though.  Really seems
like one of those things they’d’ve added at some point, instead of having to
abuse the list commands like this.</p>

<h2 id="rediss-built-in-for-this">Redis’s Built-In For This</h2>

<p>In Redis 5.0.0, the <a href="https://redis.io/commands/xadd"><code class="language-plaintext highlighter-rouge">XADD</code></a> command was added.  This command is to be used with
a new data type added in 5.0 called a <a href="https://redis.io/docs/data-types/streams/">stream</a>.  Streams are
an append-only data structure, where each entry is a key-value pair; while also
including stateful and optimization information around that.</p>

<p>Up until now, we’ve been using lists as a queue.  But in reality, all we need
is a data structure in which we can append items to the end, and mark items as
being worked on (and by whom), or as being completed.  An append-only log of
tasks is a perfect fit - it’ll keep track of the order of tasks, as well as
who’s working on what, what’s been completed, and what’s still pending<sup id="fnref:stream"><a href="#fn:stream" class="footnote" rel="footnote" role="doc-noteref">9</a></sup>.</p>

<p>But before we can use streams, I need to introduce another concept we haven’t
yet included into our model: consumer groups.  We’ve been treating each consumer
as all performing the same job, and doing the same thing for every task - so
there’s no need to differentiate between them.  But what if we have multiple
consumers, and we want the same task to go to all of them?  For example, we
might want to gather image metadata quickly (e.g. dimensions, file size, etc.),
and do more complex processing on other servers separately.</p>

<p>This is where consumer groups come in.  A consumer group is a named group of
consumers that all receive different tasks.  Each consumer group essentially
gets a “copy” of the queue, and each consumer in the group is assigned a task
from that queue.  This means that each task is only processed once <em>within</em> a
consumer group, but can be processed multiple times <em>across</em> consumer groups.</p>

<p>A version of our system from above with consumer gorups might look something
like this:</p>

<figure>
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="817px" height="667px" viewBox="-0.5 -0.5 817 667"><use href="/assets/task-queue/with-consumer-groups.svg#graphConsumerGroups" /></svg>
    <figcaption>
        A version of the above diagram.  This time, the queue box is separated into 'consumer group 1' and 'consumer group 2', which contain the same things the queue used to; producer pushes into both general queues now.  The consumers box now similarly contains 'consumer group 1' and 'consumer group 2', but they draw from the respective consumer-specific queues now.
    </figcaption>
</figure>

<p>Note that in this variant, the producer has to push into both general queues
for both consumer groups.  We could offload that responsibility onto consumers -
before polling, consumers could check a queue, and move all items in that queue
into both consumer group queues - but that’s a lot of duplication and extra
work in general<sup id="fnref:extra-work"><a href="#fn:extra-work" class="footnote" rel="footnote" role="doc-noteref">10</a></sup>.  However, Redis Streams has this concept built in
as its core, as it’s completely free, no extra work required.</p>

<p>So, let’s take a look at how this would be implemented in Redis Streams.  Again,
Streams are an append-only log - and the reason we need one queue per consumer
group previously is because we were popping items off of lists to claim them.
Instead, with a stream, we can keep track of our position in the stream, and
move that position around - when we claim a new item, we move that position
forward.  Thus, each consumer group is just a pointer into the stream, and
consumers are just moving that pointer around.</p>

<p>However, <em>within</em> a consumer group, we still need to keep track of which tasks
are pending.  We can’t just use the stream itself, because we would need to be
able to <em>update</em> the tasks with consumer information - and that would conflict
across consumer groups.  So each consumer group has a “Pending Entries List,”
which is a structure that contains just that information - the ID of the task,
the consumer that’s working on it, and the number of times it was delivered.</p>

<p>To remove a task from the pending entries list, we can use the <a href="https://redis.io/commands/xack"><code class="language-plaintext highlighter-rouge">XACK</code></a> command,
which effectively marks it as complete.  Redis doesn’t offer a version of
<code class="language-plaintext highlighter-rouge">NACK</code>, or negative acknowledgement, which would mark a task as incomplete and
put it back onto the effective queue; but it does allow recovery in case of a
failure.  There is a way to emulate <code class="language-plaintext highlighter-rouge">NACK</code> using <a href="https://redis.io/commands/xclaim"><code class="language-plaintext highlighter-rouge">XCLAIM</code></a>, but I’ll explain
that later.</p>

<p>To pull a task from the stream, for our use-case, we should use <a href="https://redis.io/commands/xreadgroup"><code class="language-plaintext highlighter-rouge">XREADGROUP</code></a>.
We’ll ignore <code class="language-plaintext highlighter-rouge">XREAD</code>, which is the non-grouped version, because we want to
ensure that each task is only processed once within a consumer group.  The
command takes the group we’re in, and the consumer name, and returns the next
<em>n</em> tasks from the streams specified.  But you’ll notice that it takes an ID
argument.  For <code class="language-plaintext highlighter-rouge">XREADGROUP</code>, the ID we’ll care about here is <code class="language-plaintext highlighter-rouge">&gt;</code> - this is a
special ID to redis, which means “messages that were never delivered to any
other consumer in the group.”  This is how we can ensure that each task is only
processed once within a consumer group<sup id="fnref:other-ids"><a href="#fn:other-ids" class="footnote" rel="footnote" role="doc-noteref">11</a></sup>.</p>

<p>But again, we want to reclaim tasks that have been abandoned by the consumer.
Redis 5.0 includes the <a href="https://redis.io/commands/xpending"><code class="language-plaintext highlighter-rouge">XPENDING</code></a> and <a href="https://redis.io/commands/xclaim"><code class="language-plaintext highlighter-rouge">XCLAIM</code></a> commands; before using
<a href="https://redis.io/commands/xreadgroup"><code class="language-plaintext highlighter-rouge">XREADGROUP</code></a>, we can use <code class="language-plaintext highlighter-rouge">XPENDING</code> to get a list of tasks that have been
idle for a certain amount of time, and then use <code class="language-plaintext highlighter-rouge">XCLAIM</code> to claim them; <code class="language-plaintext highlighter-rouge">XCLAIM</code>
allows us to specify the same idle timeout, so if we would claim a task that
another consumer has already claimed (and reset the timeout for), we can ignore
it.  In addition, <code class="language-plaintext highlighter-rouge">XCLAIM</code> allows us to update the idle time (and retry
count)<sup id="fnref:update-idle-time"><a href="#fn:update-idle-time" class="footnote" rel="footnote" role="doc-noteref">12</a></sup>, so we can use this to emulate <code class="language-plaintext highlighter-rouge">NACK</code> - we can just
set the idle time of the message to be past the idle timeout, and it’ll be
picked up the next time someone calls <code class="language-plaintext highlighter-rouge">XPENDING</code>.</p>

<p>Redis 6.2 also includes the <a href="https://redis.io/commands/xautoclaim"><code class="language-plaintext highlighter-rouge">XAUTOCLAIM</code></a> command, which is a combination of
<code class="language-plaintext highlighter-rouge">XPENDING</code> and <code class="language-plaintext highlighter-rouge">XCLAIM</code> - you specify the group, the consumer, the minimum idle
time, the starting ID (<code class="language-plaintext highlighter-rouge">0-0</code> is a good choice here), and the maximum number of
tasks to claim, and it’ll do the rest.  The return value is similar to <code class="language-plaintext highlighter-rouge">XCLAIM</code>.
There is one key difference, though - <code class="language-plaintext highlighter-rouge">XAUTOCLAIM</code> returns only the next ID
to pass to <code class="language-plaintext highlighter-rouge">XAUTOCLAIM</code> on the next call, and the items claimed (as well as
message IDs that no longer exist, but we don’t need this).  <code class="language-plaintext highlighter-rouge">XCLAIM</code> returns
the same information, about.  <em>However…</em> <code class="language-plaintext highlighter-rouge">XPENDING</code> returns more information.
It returns the idle time of the message, but more importantly, it returns the
number of times the message has been delivered.  This is important, because
we want to track that, to push messages into a dead letter queue when it exceeds
a threshold.  So, we’ll need to use <code class="language-plaintext highlighter-rouge">XPENDING</code> either way to get the number of
deliveries.</p>

<p>So, this is what it would look like with redis streams:</p>

<figure>
    <svg xmlns="http://www.w3.org/2000/svg" width="801px" height="471px" viewBox="-0.5 -0.5 801 471"><use href="/assets/task-queue/stream.svg#graphStream" /></svg>
    <figcaption>
        A heavily modified version of the above diagram.  the queue block now has a boundless stream of items at the top; two consumer group blocks within the queue point to different points in the stream.  Each consumer group have a 'pending entries list' within them.  The consumers point to these consumer groups.
    </figcaption>
</figure>

<p>That’s pretty neat!  Now, let’s take a look at how we’d implement this.  The
code for the producer wouldn’t change much, as we’d just need to <code class="language-plaintext highlighter-rouge">XADD</code> to the
stream instead of <code class="language-plaintext highlighter-rouge">LPUSH</code> to the list:</p>

<div class="language-rust highlighter-rouge"><table class="rouge-line-table"><tbody><tr id="line-1" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>1</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">anyhow</span><span class="p">::</span><span class="n">Context</span><span class="p">;</span>
</pre></td></tr><tr id="line-2" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>2</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">redis</span><span class="p">::{</span><span class="n">AsyncCommands</span><span class="p">,</span> <span class="n">Client</span><span class="p">};</span>
</pre></td></tr><tr id="line-3" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>3</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-4" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>4</pre></td><td class="rouge-code"><pre><span class="nd">#[tokio::main]</span>
</pre></td></tr><tr id="line-5" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>5</pre></td><td class="rouge-code"><pre><span class="k">async</span> <span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nn">anyhow</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="p">{</span>
</pre></td></tr><tr id="line-6" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>6</pre></td><td class="rouge-code"><pre>    <span class="nf">producer</span><span class="p">()</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-7" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>7</pre></td><td class="rouge-code"><pre>    <span class="nf">consumer</span><span class="p">()</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-8" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>8</pre></td><td class="rouge-code"><pre>    <span class="nf">Ok</span><span class="p">(())</span>
</pre></td></tr><tr id="line-9" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>9</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr><tr id="line-10" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>10</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-11" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>11</pre></td><td class="rouge-code"><pre><span class="k">async</span> <span class="k">fn</span> <span class="nf">producer</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nn">anyhow</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="p">{</span>
</pre></td></tr><tr id="line-12" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>12</pre></td><td class="rouge-code"><pre>    <span class="c1">// This will only fail if the url is badly formatted.</span>
</pre></td></tr><tr id="line-13" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>13</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">client</span> <span class="o">=</span> <span class="nn">Client</span><span class="p">::</span><span class="nf">open</span><span class="p">(</span><span class="s">"redis://localhost:6379/11"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-14" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>14</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="k">mut</span> <span class="n">connection</span> <span class="o">=</span> <span class="n">client</span>
</pre></td></tr><tr id="line-15" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>15</pre></td><td class="rouge-code"><pre>        <span class="nf">.get_async_connection</span><span class="p">()</span>
</pre></td></tr><tr id="line-16" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>16</pre></td><td class="rouge-code"><pre>        <span class="k">.await</span>
</pre></td></tr><tr id="line-17" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>17</pre></td><td class="rouge-code"><pre>        <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to open connection to redis server!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-18" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>18</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-19" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>19</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">id</span> <span class="o">=</span> <span class="nn">uuid</span><span class="p">::</span><span class="nn">Uuid</span><span class="p">::</span><span class="nf">new_v4</span><span class="p">()</span><span class="nf">.to_string</span><span class="p">();</span>
</pre></td></tr><tr id="line-20" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>20</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">message</span> <span class="o">=</span> <span class="p">[(</span><span class="s">"id"</span><span class="p">,</span> <span class="n">id</span><span class="nf">.as_str</span><span class="p">()),</span> <span class="p">(</span><span class="s">"payload"</span><span class="p">,</span> <span class="s">"{}"</span><span class="p">)];</span>
</pre></td></tr><tr id="line-21" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>21</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-22" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>22</pre></td><td class="rouge-code"><pre>    <span class="n">connection</span>
</pre></td></tr><tr id="line-23" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>23</pre></td><td class="rouge-code"><pre>        <span class="nf">.xadd</span><span class="p">(</span><span class="s">"stream:main"</span><span class="p">,</span> <span class="s">"*"</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">message</span><span class="p">)</span>
</pre></td></tr><tr id="line-24" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>24</pre></td><td class="rouge-code"><pre>        <span class="k">.await</span>
</pre></td></tr><tr id="line-25" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>25</pre></td><td class="rouge-code"><pre>        <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to push message to queue!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-26" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>26</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-27" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>27</pre></td><td class="rouge-code"><pre>    <span class="nf">Ok</span><span class="p">(())</span>
</pre></td></tr><tr id="line-28" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>28</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr></tbody></table></div>

<p>The consumer, however, would change quite a bit.</p>

<ol>
  <li>We need to create the consumer group the consumer is in, if it doesn’t exist.
This is done with the <a href="https://redis.io/commands/xgroup-create/"><code class="language-plaintext highlighter-rouge">XGROUP CREATE</code></a> command.  Note that we must specify
the ID of where the consumer group’s position is in the stream - we’ll use
<code class="language-plaintext highlighter-rouge">0-0</code> here, which means “the first message in the stream.”  This is because
we want to process all messages in the stream, and not just new ones.  (If
the group already exists, it’ll return an error, so we’ll need to make sure
to handle that.)</li>
  <li>We need to create the consumer within the consumer group.  This can be
implicit with <code class="language-plaintext highlighter-rouge">XREADGROUP</code> - if the consumer’s name doesn’t exist, it’ll
create it for us.  We could use <a href="https://redis.io/commands/xgroup-createconsumer/"><code class="language-plaintext highlighter-rouge">XGROUP CREATECONSUMER</code></a> as well.</li>
  <li>Then, we’ll start by pulling all of the consumer’s pending messages, in case
we had crashed and need to recover our tasks.  We’ll use <code class="language-plaintext highlighter-rouge">XREADGROUP</code> for
this - if we specify <code class="language-plaintext highlighter-rouge">&gt;</code> for the ID, it’ll pull all of our pending messages
for us.  This has the side effect of incrementing the number of deliveries
for each message, and resetting the idle time.</li>
  <li>If we don’t have any pending messages, we’ll use <code class="language-plaintext highlighter-rouge">XAUTOCLAIM</code> to claim
messages that have been idle for a certain amount of time.</li>
  <li>If we get any messages for either of these, we’ll check their delivery count
with <code class="language-plaintext highlighter-rouge">XPENDING</code> - if it’s over a certain threshold, we’ll push it to a dead
letter queue, and <code class="language-plaintext highlighter-rouge">XACK</code> it.  Otherwise, we’ll process it, and <code class="language-plaintext highlighter-rouge">XACK</code> it.</li>
  <li>Finally, if there are no pending messages, and no idle messages, we will use
<code class="language-plaintext highlighter-rouge">XREADGROUP</code> with <code class="language-plaintext highlighter-rouge">&gt;</code> to pull the next message from the stream, blocking for
a few seconds.  If we get a message, we’ll process it, and <code class="language-plaintext highlighter-rouge">XACK</code> it.</li>
  <li>Go to (3).</li>
</ol>

<p>This might look something like this:</p>

<div class="language-rust highlighter-rouge"><table class="rouge-line-table"><tbody><tr id="line-1" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>1</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">collections</span><span class="p">::</span><span class="n">HashMap</span><span class="p">;</span>
</pre></td></tr><tr id="line-2" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>2</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-3" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>3</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">anyhow</span><span class="p">::</span><span class="n">Context</span><span class="p">;</span>
</pre></td></tr><tr id="line-4" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>4</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">redis</span><span class="p">::</span><span class="nn">aio</span><span class="p">::</span><span class="n">Connection</span><span class="p">;</span>
</pre></td></tr><tr id="line-5" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>5</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">redis</span><span class="p">::</span><span class="nn">streams</span><span class="p">::{</span>
</pre></td></tr><tr id="line-6" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>6</pre></td><td class="rouge-code"><pre>    <span class="n">StreamId</span><span class="p">,</span>
</pre></td></tr><tr id="line-7" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>7</pre></td><td class="rouge-code"><pre>    <span class="n">StreamPendingCountReply</span><span class="p">,</span>
</pre></td></tr><tr id="line-8" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>8</pre></td><td class="rouge-code"><pre>    <span class="n">StreamReadOptions</span><span class="p">,</span>
</pre></td></tr><tr id="line-9" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>9</pre></td><td class="rouge-code"><pre>    <span class="n">StreamReadReply</span>
</pre></td></tr><tr id="line-10" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>10</pre></td><td class="rouge-code"><pre><span class="p">};</span>
</pre></td></tr><tr id="line-11" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>11</pre></td><td class="rouge-code"><pre><span class="k">use</span> <span class="nn">redis</span><span class="p">::{</span><span class="n">cmd</span><span class="p">,</span> <span class="n">AsyncCommands</span><span class="p">,</span> <span class="n">Client</span><span class="p">,</span> <span class="n">FromRedisValue</span><span class="p">};</span>
</pre></td></tr><tr id="line-12" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>12</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-13" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>13</pre></td><td class="rouge-code"><pre><span class="k">static</span> <span class="n">STREAM_NAME</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span> <span class="s">"stream:main"</span><span class="p">;</span>
</pre></td></tr><tr id="line-14" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>14</pre></td><td class="rouge-code"><pre><span class="c1">// Left as an exercise to make this configurable.</span>
</pre></td></tr><tr id="line-15" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>15</pre></td><td class="rouge-code"><pre><span class="k">static</span> <span class="n">GROUP_NAME</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span> <span class="s">"group-a"</span><span class="p">;</span>
</pre></td></tr><tr id="line-16" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>16</pre></td><td class="rouge-code"><pre><span class="c1">// Ditto.</span>
</pre></td></tr><tr id="line-17" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>17</pre></td><td class="rouge-code"><pre><span class="k">static</span> <span class="n">CONSUMER_NAME</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span> <span class="s">"consumer-1"</span><span class="p">;</span>
</pre></td></tr><tr id="line-18" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>18</pre></td><td class="rouge-code"><pre><span class="c1">// Idle time in milliseconds before a message can be reclaimed.</span>
</pre></td></tr><tr id="line-19" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>19</pre></td><td class="rouge-code"><pre><span class="k">static</span> <span class="n">CONSUMER_IDLE_TIME_MS</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">10000</span><span class="p">;</span>
</pre></td></tr><tr id="line-20" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>20</pre></td><td class="rouge-code"><pre><span class="c1">// Maximum number of times a message can be retried before being sent</span>
</pre></td></tr><tr id="line-21" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>21</pre></td><td class="rouge-code"><pre><span class="c1">// to the dead letter queue.</span>
</pre></td></tr><tr id="line-22" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>22</pre></td><td class="rouge-code"><pre><span class="k">static</span> <span class="n">MAX_RETRIES</span><span class="p">:</span> <span class="nb">usize</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
</pre></td></tr><tr id="line-23" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>23</pre></td><td class="rouge-code"><pre><span class="c1">// Name of the dead letter queue.</span>
</pre></td></tr><tr id="line-24" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>24</pre></td><td class="rouge-code"><pre><span class="k">static</span> <span class="n">DEAD_LETTER_QUEUE</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span> <span class="s">"queue:main:dead-letter"</span><span class="p">;</span>
</pre></td></tr><tr id="line-25" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>25</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-26" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>26</pre></td><td class="rouge-code"><pre><span class="k">async</span> <span class="k">fn</span> <span class="nf">consumer</span><span class="p">()</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nn">anyhow</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="p">{</span>
</pre></td></tr><tr id="line-27" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>27</pre></td><td class="rouge-code"><pre>    <span class="c1">// This will only fail if the url is badly formatted.</span>
</pre></td></tr><tr id="line-28" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>28</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">client</span> <span class="o">=</span> <span class="nn">Client</span><span class="p">::</span><span class="nf">open</span><span class="p">(</span><span class="s">"redis://localhost:6379/11"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-29" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>29</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="k">mut</span> <span class="n">connection</span> <span class="o">=</span> <span class="n">client</span>
</pre></td></tr><tr id="line-30" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>30</pre></td><td class="rouge-code"><pre>        <span class="nf">.get_async_connection</span><span class="p">()</span>
</pre></td></tr><tr id="line-31" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>31</pre></td><td class="rouge-code"><pre>        <span class="k">.await</span>
</pre></td></tr><tr id="line-32" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>32</pre></td><td class="rouge-code"><pre>        <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to open connection to redis server!"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-33" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>33</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-34" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>34</pre></td><td class="rouge-code"><pre>    <span class="c1">// Create the consumer group if it doesn't exist.  We'll ignore the</span>
</pre></td></tr><tr id="line-35" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>35</pre></td><td class="rouge-code"><pre>    <span class="c1">// error if it already exists.</span>
</pre></td></tr><tr id="line-36" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>36</pre></td><td class="rouge-code"><pre>    <span class="n">connection</span>
</pre></td></tr><tr id="line-37" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>37</pre></td><td class="rouge-code"><pre>        <span class="nf">.xgroup_create_mkstream</span><span class="p">(</span><span class="n">STREAM_NAME</span><span class="p">,</span> <span class="n">GROUP_NAME</span><span class="p">,</span> <span class="s">"0-0"</span><span class="p">)</span>
</pre></td></tr><tr id="line-38" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>38</pre></td><td class="rouge-code"><pre>        <span class="k">.await</span>
</pre></td></tr><tr id="line-39" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>39</pre></td><td class="rouge-code"><pre>        <span class="nf">.or_else</span><span class="p">(|</span><span class="n">e</span><span class="p">|</span> <span class="p">{</span>
</pre></td></tr><tr id="line-40" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>40</pre></td><td class="rouge-code"><pre>            <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="s">"BUSYGROUP"</span><span class="p">)</span> <span class="o">=</span> <span class="n">e</span><span class="nf">.code</span><span class="p">()</span> <span class="p">{</span>
</pre></td></tr><tr id="line-41" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>41</pre></td><td class="rouge-code"><pre>                <span class="nf">Ok</span><span class="p">(())</span>
</pre></td></tr><tr id="line-42" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>42</pre></td><td class="rouge-code"><pre>            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</pre></td></tr><tr id="line-43" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>43</pre></td><td class="rouge-code"><pre>                <span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</pre></td></tr><tr id="line-44" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>44</pre></td><td class="rouge-code"><pre>            <span class="p">}</span>
</pre></td></tr><tr id="line-45" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>45</pre></td><td class="rouge-code"><pre>        <span class="p">})</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-46" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>46</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-47" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>47</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="k">mut</span> <span class="n">pending_messages</span><span class="p">:</span> <span class="n">StreamReadReply</span> <span class="o">=</span> <span class="n">connection</span>
</pre></td></tr><tr id="line-48" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>48</pre></td><td class="rouge-code"><pre>        <span class="nf">.xread_options</span><span class="p">(</span>
</pre></td></tr><tr id="line-49" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>49</pre></td><td class="rouge-code"><pre>            <span class="o">&amp;</span><span class="p">[</span><span class="n">STREAM_NAME</span><span class="p">],</span>
</pre></td></tr><tr id="line-50" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>50</pre></td><td class="rouge-code"><pre>            <span class="o">&amp;</span><span class="p">[</span><span class="s">"&gt;"</span><span class="p">],</span>
</pre></td></tr><tr id="line-51" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>51</pre></td><td class="rouge-code"><pre>            <span class="o">&amp;</span><span class="nn">StreamReadOptions</span><span class="p">::</span><span class="nf">default</span><span class="p">()</span>
</pre></td></tr><tr id="line-52" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>52</pre></td><td class="rouge-code"><pre>                <span class="nf">.group</span><span class="p">(</span><span class="n">GROUP_NAME</span><span class="p">,</span> <span class="n">CONSUMER_NAME</span><span class="p">)</span>
</pre></td></tr><tr id="line-53" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>53</pre></td><td class="rouge-code"><pre>                <span class="nf">.count</span><span class="p">(</span><span class="mi">10</span><span class="p">),</span>
</pre></td></tr><tr id="line-54" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>54</pre></td><td class="rouge-code"><pre>        <span class="p">)</span>
</pre></td></tr><tr id="line-55" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>55</pre></td><td class="rouge-code"><pre>        <span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-56" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>56</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-57" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>57</pre></td><td class="rouge-code"><pre>    <span class="k">for</span> <span class="n">message</span> <span class="k">in</span> <span class="n">pending_messages</span><span class="py">.keys</span><span class="nf">.pop</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">()</span><span class="py">.ids</span> <span class="p">{</span>
</pre></td></tr><tr id="line-58" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>58</pre></td><td class="rouge-code"><pre>        <span class="k">let</span> <span class="n">StreamId</span> <span class="p">{</span> <span class="n">id</span><span class="p">,</span> <span class="n">map</span> <span class="p">}</span> <span class="o">=</span> <span class="n">message</span><span class="p">;</span>
</pre></td></tr><tr id="line-59" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>59</pre></td><td class="rouge-code"><pre>        <span class="nf">process</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">connection</span><span class="p">,</span> <span class="n">id</span><span class="p">,</span> <span class="n">map</span><span class="p">)</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-60" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>60</pre></td><td class="rouge-code"><pre>    <span class="p">}</span>
</pre></td></tr><tr id="line-61" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>61</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-62" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>62</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="k">mut</span> <span class="n">start_id</span> <span class="o">=</span> <span class="s">"0-0"</span><span class="nf">.to_string</span><span class="p">();</span>
</pre></td></tr><tr id="line-63" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>63</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-64" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>64</pre></td><td class="rouge-code"><pre>    <span class="k">loop</span> <span class="p">{</span>
</pre></td></tr><tr id="line-65" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>65</pre></td><td class="rouge-code"><pre>        <span class="k">type</span> <span class="n">RangeReply</span> <span class="o">=</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="p">(</span><span class="nb">String</span><span class="p">,</span> <span class="n">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span> <span class="nn">redis</span><span class="p">::</span><span class="n">Value</span><span class="o">&gt;</span><span class="p">)</span><span class="o">&gt;</span><span class="p">;</span>
</pre></td></tr><tr id="line-66" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>66</pre></td><td class="rouge-code"><pre>        <span class="k">type</span> <span class="n">AutoClaimReply</span> <span class="o">=</span> <span class="p">(</span><span class="nb">String</span><span class="p">,</span> <span class="n">RangeReply</span><span class="p">,</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="p">);</span>
</pre></td></tr><tr id="line-67" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>67</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-68" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>68</pre></td><td class="rouge-code"><pre>        <span class="c1">// The redis library I am using does not support XAUTOCLAIM; it</span>
</pre></td></tr><tr id="line-69" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>69</pre></td><td class="rouge-code"><pre>        <span class="c1">// makes sense as XAUTOCLAIM is only available after 6.2, and the</span>
</pre></td></tr><tr id="line-70" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>70</pre></td><td class="rouge-code"><pre>        <span class="c1">// library may not support newer commands.  So, we'll use the</span>
</pre></td></tr><tr id="line-71" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>71</pre></td><td class="rouge-code"><pre>        <span class="c1">// generic `cmd` function to send the command.</span>
</pre></td></tr><tr id="line-72" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>72</pre></td><td class="rouge-code"><pre>        <span class="k">let</span> <span class="n">idle</span><span class="p">:</span> <span class="n">AutoClaimReply</span> <span class="o">=</span> <span class="nf">cmd</span><span class="p">(</span><span class="s">"XAUTOCLAIM"</span><span class="p">)</span>
</pre></td></tr><tr id="line-73" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>73</pre></td><td class="rouge-code"><pre>            <span class="nf">.arg</span><span class="p">(</span><span class="n">STREAM_NAME</span><span class="p">)</span>
</pre></td></tr><tr id="line-74" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>74</pre></td><td class="rouge-code"><pre>            <span class="nf">.arg</span><span class="p">(</span><span class="n">GROUP_NAME</span><span class="p">)</span>
</pre></td></tr><tr id="line-75" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>75</pre></td><td class="rouge-code"><pre>            <span class="nf">.arg</span><span class="p">(</span><span class="n">CONSUMER_NAME</span><span class="p">)</span>
</pre></td></tr><tr id="line-76" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>76</pre></td><td class="rouge-code"><pre>            <span class="nf">.arg</span><span class="p">(</span><span class="n">CONSUMER_IDLE_TIME_MS</span><span class="p">)</span>
</pre></td></tr><tr id="line-77" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>77</pre></td><td class="rouge-code"><pre>            <span class="nf">.arg</span><span class="p">(</span><span class="o">&amp;</span><span class="n">start_id</span><span class="p">)</span>
</pre></td></tr><tr id="line-78" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>78</pre></td><td class="rouge-code"><pre>            <span class="nf">.arg</span><span class="p">(</span><span class="s">"COUNT"</span><span class="p">)</span>
</pre></td></tr><tr id="line-79" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>79</pre></td><td class="rouge-code"><pre>            <span class="nf">.arg</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
</pre></td></tr><tr id="line-80" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>80</pre></td><td class="rouge-code"><pre>            <span class="nf">.arg</span><span class="p">(</span><span class="s">"JUSTID"</span><span class="p">)</span>
</pre></td></tr><tr id="line-81" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>81</pre></td><td class="rouge-code"><pre>            <span class="nf">.query_async</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">connection</span><span class="p">)</span>
</pre></td></tr><tr id="line-82" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>82</pre></td><td class="rouge-code"><pre>            <span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-83" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>83</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-84" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>84</pre></td><td class="rouge-code"><pre>        <span class="n">start_id</span> <span class="o">=</span> <span class="n">idle</span><span class="na">.0</span><span class="p">;</span>
</pre></td></tr><tr id="line-85" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>85</pre></td><td class="rouge-code"><pre>        <span class="k">for</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">map</span><span class="p">)</span> <span class="k">in</span> <span class="n">idle</span><span class="na">.1</span> <span class="p">{</span>
</pre></td></tr><tr id="line-86" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>86</pre></td><td class="rouge-code"><pre>            <span class="nf">process</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">connection</span><span class="p">,</span> <span class="n">id</span><span class="p">,</span> <span class="n">map</span><span class="p">)</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-87" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>87</pre></td><td class="rouge-code"><pre>        <span class="p">}</span>
</pre></td></tr><tr id="line-88" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>88</pre></td><td class="rouge-code"><pre>        <span class="k">if</span> <span class="n">start_id</span> <span class="o">==</span> <span class="s">"0-0"</span> <span class="p">{</span>
</pre></td></tr><tr id="line-89" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>89</pre></td><td class="rouge-code"><pre>            <span class="k">break</span><span class="p">;</span>
</pre></td></tr><tr id="line-90" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>90</pre></td><td class="rouge-code"><pre>        <span class="p">}</span>
</pre></td></tr><tr id="line-91" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>91</pre></td><td class="rouge-code"><pre>    <span class="p">}</span>
</pre></td></tr><tr id="line-92" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>92</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-93" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>93</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="k">mut</span> <span class="n">messages</span><span class="p">:</span> <span class="n">StreamReadReply</span> <span class="o">=</span> <span class="n">connection</span>
</pre></td></tr><tr id="line-94" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>94</pre></td><td class="rouge-code"><pre>        <span class="nf">.xread_options</span><span class="p">(</span>
</pre></td></tr><tr id="line-95" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>95</pre></td><td class="rouge-code"><pre>            <span class="o">&amp;</span><span class="p">[</span><span class="n">STREAM_NAME</span><span class="p">],</span>
</pre></td></tr><tr id="line-96" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>96</pre></td><td class="rouge-code"><pre>            <span class="o">&amp;</span><span class="p">[</span><span class="s">"&gt;"</span><span class="p">],</span>
</pre></td></tr><tr id="line-97" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>97</pre></td><td class="rouge-code"><pre>            <span class="o">&amp;</span><span class="nn">StreamReadOptions</span><span class="p">::</span><span class="nf">default</span><span class="p">()</span>
</pre></td></tr><tr id="line-98" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>98</pre></td><td class="rouge-code"><pre>                <span class="nf">.group</span><span class="p">(</span><span class="n">GROUP_NAME</span><span class="p">,</span> <span class="n">CONSUMER_NAME</span><span class="p">)</span>
</pre></td></tr><tr id="line-99" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>99</pre></td><td class="rouge-code"><pre>                <span class="nf">.block</span><span class="p">(</span><span class="n">CONSUMER_IDLE_TIME_MS</span><span class="p">)</span>
</pre></td></tr><tr id="line-100" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>100</pre></td><td class="rouge-code"><pre>                <span class="nf">.count</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</pre></td></tr><tr id="line-101" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>101</pre></td><td class="rouge-code"><pre>        <span class="p">)</span>
</pre></td></tr><tr id="line-102" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>102</pre></td><td class="rouge-code"><pre>        <span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-103" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>103</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-104" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>104</pre></td><td class="rouge-code"><pre>    <span class="k">for</span> <span class="n">message</span> <span class="k">in</span> <span class="n">messages</span><span class="py">.keys</span><span class="nf">.pop</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">()</span><span class="py">.ids</span> <span class="p">{</span>
</pre></td></tr><tr id="line-105" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>105</pre></td><td class="rouge-code"><pre>        <span class="k">let</span> <span class="n">StreamId</span> <span class="p">{</span> <span class="n">id</span><span class="p">,</span> <span class="n">map</span> <span class="p">}</span> <span class="o">=</span> <span class="n">message</span><span class="p">;</span>
</pre></td></tr><tr id="line-106" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>106</pre></td><td class="rouge-code"><pre>        <span class="nf">process</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span> <span class="n">connection</span><span class="p">,</span> <span class="n">id</span><span class="p">,</span> <span class="n">map</span><span class="p">)</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-107" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>107</pre></td><td class="rouge-code"><pre>    <span class="p">}</span>
</pre></td></tr><tr id="line-108" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>108</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-109" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>109</pre></td><td class="rouge-code"><pre>    <span class="nf">Ok</span><span class="p">(())</span>
</pre></td></tr><tr id="line-110" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>110</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr><tr id="line-111" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>111</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-112" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>112</pre></td><td class="rouge-code"><pre><span class="k">async</span> <span class="k">fn</span> <span class="nf">process</span><span class="p">(</span>
</pre></td></tr><tr id="line-113" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>113</pre></td><td class="rouge-code"><pre>    <span class="n">connection</span><span class="p">:</span> <span class="o">&amp;</span><span class="k">mut</span> <span class="n">Connection</span><span class="p">,</span>
</pre></td></tr><tr id="line-114" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>114</pre></td><td class="rouge-code"><pre>    <span class="n">id</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
</pre></td></tr><tr id="line-115" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>115</pre></td><td class="rouge-code"><pre>    <span class="n">message</span><span class="p">:</span> <span class="n">HashMap</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span> <span class="nn">redis</span><span class="p">::</span><span class="n">Value</span><span class="o">&gt;</span><span class="p">,</span>
</pre></td></tr><tr id="line-116" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>116</pre></td><td class="rouge-code"><pre><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="p">(),</span> <span class="nn">anyhow</span><span class="p">::</span><span class="n">Error</span><span class="o">&gt;</span> <span class="p">{</span>
</pre></td></tr><tr id="line-117" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>117</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">pending_info</span><span class="p">:</span> <span class="n">StreamPendingCountReply</span> <span class="o">=</span> <span class="n">connection</span>
</pre></td></tr><tr id="line-118" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>118</pre></td><td class="rouge-code"><pre>        <span class="nf">.xpending_count</span><span class="p">(</span><span class="n">STREAM_NAME</span><span class="p">,</span> <span class="n">GROUP_NAME</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">id</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">id</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</pre></td></tr><tr id="line-119" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>119</pre></td><td class="rouge-code"><pre>        <span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-120" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>120</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">pending</span> <span class="o">=</span> <span class="n">pending_info</span>
</pre></td></tr><tr id="line-121" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>121</pre></td><td class="rouge-code"><pre>        <span class="py">.ids</span>
</pre></td></tr><tr id="line-122" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>122</pre></td><td class="rouge-code"><pre>        <span class="nf">.into_iter</span><span class="p">()</span>
</pre></td></tr><tr id="line-123" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>123</pre></td><td class="rouge-code"><pre>        <span class="nf">.find</span><span class="p">(|</span><span class="n">i</span><span class="p">|</span> <span class="n">i</span><span class="py">.id</span> <span class="o">==</span> <span class="n">id</span><span class="p">)</span>
</pre></td></tr><tr id="line-124" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>124</pre></td><td class="rouge-code"><pre>        <span class="nf">.context</span><span class="p">(</span><span class="s">"failed to find pending id!?"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-125" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>125</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-126" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>126</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">inner_id</span> <span class="o">=</span> <span class="n">message</span>
</pre></td></tr><tr id="line-127" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>127</pre></td><td class="rouge-code"><pre>        <span class="nf">.get</span><span class="p">(</span><span class="s">"id"</span><span class="p">)</span>
</pre></td></tr><tr id="line-128" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>128</pre></td><td class="rouge-code"><pre>        <span class="nf">.and_then</span><span class="p">(|</span><span class="n">v</span><span class="p">|</span> <span class="nn">String</span><span class="p">::</span><span class="nf">from_redis_value</span><span class="p">(</span><span class="n">v</span><span class="p">)</span><span class="nf">.ok</span><span class="p">())</span>
</pre></td></tr><tr id="line-129" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>129</pre></td><td class="rouge-code"><pre>        <span class="nf">.context</span><span class="p">(</span><span class="s">"missing inner message id"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-130" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>130</pre></td><td class="rouge-code"><pre>    <span class="k">let</span> <span class="n">payload</span> <span class="o">=</span> <span class="n">message</span>
</pre></td></tr><tr id="line-131" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>131</pre></td><td class="rouge-code"><pre>        <span class="nf">.get</span><span class="p">(</span><span class="s">"payload"</span><span class="p">)</span>
</pre></td></tr><tr id="line-132" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>132</pre></td><td class="rouge-code"><pre>        <span class="nf">.and_then</span><span class="p">(|</span><span class="n">v</span><span class="p">|</span> <span class="nn">String</span><span class="p">::</span><span class="nf">from_redis_value</span><span class="p">(</span><span class="n">v</span><span class="p">)</span><span class="nf">.ok</span><span class="p">())</span>
</pre></td></tr><tr id="line-133" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>133</pre></td><td class="rouge-code"><pre>        <span class="nf">.context</span><span class="p">(</span><span class="s">"missing inner message payload"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-134" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>134</pre></td><td class="rouge-code"><pre>
</pre></td></tr><tr id="line-135" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>135</pre></td><td class="rouge-code"><pre>    <span class="k">if</span> <span class="n">pending</span><span class="py">.times_delivered</span> <span class="o">&gt;=</span> <span class="n">MAX_RETRIES</span> <span class="p">{</span>
</pre></td></tr><tr id="line-136" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>136</pre></td><td class="rouge-code"><pre>        <span class="n">connection</span>
</pre></td></tr><tr id="line-137" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>137</pre></td><td class="rouge-code"><pre>            <span class="nf">.xadd</span><span class="p">(</span>
</pre></td></tr><tr id="line-138" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>138</pre></td><td class="rouge-code"><pre>                <span class="n">DEAD_LETTER_QUEUE</span><span class="p">,</span>
</pre></td></tr><tr id="line-139" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>139</pre></td><td class="rouge-code"><pre>                <span class="s">"*"</span><span class="p">,</span>
</pre></td></tr><tr id="line-140" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>140</pre></td><td class="rouge-code"><pre>                <span class="o">&amp;</span><span class="p">[(</span><span class="s">"id"</span><span class="p">,</span> <span class="n">inner_id</span><span class="p">),</span> <span class="p">(</span><span class="s">"payload"</span><span class="p">,</span> <span class="n">payload</span><span class="p">)],</span>
</pre></td></tr><tr id="line-141" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>141</pre></td><td class="rouge-code"><pre>            <span class="p">)</span>
</pre></td></tr><tr id="line-142" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>142</pre></td><td class="rouge-code"><pre>            <span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-143" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>143</pre></td><td class="rouge-code"><pre>        <span class="n">connection</span><span class="nf">.xack</span><span class="p">(</span><span class="n">STREAM_NAME</span><span class="p">,</span> <span class="n">GROUP_NAME</span><span class="p">,</span> <span class="o">&amp;</span><span class="p">[</span><span class="o">&amp;</span><span class="n">id</span><span class="p">])</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-144" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>144</pre></td><td class="rouge-code"><pre>    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</pre></td></tr><tr id="line-145" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>145</pre></td><td class="rouge-code"><pre>        <span class="c1">// Do something with the message.</span>
</pre></td></tr><tr id="line-146" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>146</pre></td><td class="rouge-code"><pre>        <span class="nd">println!</span><span class="p">(</span><span class="s">"processing message: {:?}"</span><span class="p">,</span> <span class="n">message</span><span class="p">);</span>
</pre></td></tr><tr id="line-147" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>147</pre></td><td class="rouge-code"><pre>        <span class="n">connection</span><span class="nf">.xack</span><span class="p">(</span><span class="n">STREAM_NAME</span><span class="p">,</span> <span class="n">GROUP_NAME</span><span class="p">,</span> <span class="o">&amp;</span><span class="p">[</span><span class="o">&amp;</span><span class="n">id</span><span class="p">])</span><span class="k">.await</span><span class="o">?</span><span class="p">;</span>
</pre></td></tr><tr id="line-148" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>148</pre></td><td class="rouge-code"><pre>    <span class="p">}</span>
</pre></td></tr><tr id="line-149" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>149</pre></td><td class="rouge-code"><pre>    <span class="nf">Ok</span><span class="p">(())</span>
</pre></td></tr><tr id="line-150" class="lineno"><td class="rouge-gutter gl" style="-moz-user-select: none;-ms-user-select: none;-webkit-user-select: none;user-select: none;"><pre>150</pre></td><td class="rouge-code"><pre><span class="p">}</span>
</pre></td></tr></tbody></table></div>

<p><code class="language-plaintext highlighter-rouge">producer</code> should be called in a loop, so that it will continue to
process messages.  This is, of course, far more complicated than the previous
example.  However, with more work, it can be simplified a lot more - we don’t
need to constantly pull our own pending messages except on start-up, for one.
We can also use <code class="language-plaintext highlighter-rouge">XPENDING</code> more intelligently than we do here, as we can grab
all of our own pending message information at once, instead of one at a time.
Thus, the number of round-trips to Redis, on average, for every message that
can be pulled, is far less than the previous example - <em>and</em> it is optimized
so that we can pull in multiple messages at once, and process them in bulk,
without losing too much, as long as all messages can be processed within the
idle time<sup id="fnref:idle-timeout"><a href="#fn:idle-timeout" class="footnote" rel="footnote" role="doc-noteref">13</a></sup>.</p>

<p>This will also reduce the storage requirements on the server for this process.
When adding a message to the stream, you can specify the maximum number of
entries to keep in the stream - if you don’t specify this, it will keep all
messages forever.  If you specify that the max length of the queue is 1000,
then the server will only keep the last 1000 messages in the stream<sup id="fnref:approx"><a href="#fn:approx" class="footnote" rel="footnote" role="doc-noteref">14</a></sup>.</p>

<p>Trimming is optional.  But considering that you only need to keep the active
messages in the stream, i.e. messages that are being processed or will be
processed, you can automatically clear out any messages that have been
processed<sup id="fnref:periodic-cleaning"><a href="#fn:periodic-cleaning" class="footnote" rel="footnote" role="doc-noteref">15</a></sup>.</p>

<h2 id="beyond-redis">Beyond Redis</h2>

<p>Redis isn’t always the best tool for scalability.  Redis works best on a single
server instance, where it has easy linear serializability; unfortunately,
when trying to scale it horizontally, it does not maintain any level of
consistency that we would want for such a task system:</p>

<figure>
  <blockquote cite="https://aphyr.com/posts/283-jepsen-redis"><p>
    Out of 2000 writes, Redis claimed that 1998 of them completed successfully.
    However, only 872 of those integers were present in the final set. <b>Redis
    threw away 56% of the writes</b> it told us succeeded.
  </p></blockquote>
  <figcaption>—<a href="https://aphyr.com/posts/283-jepsen-redis">Jepsen: Redis</a>, on Redis Sentinel</figcaption>
</figure>

<figure>
  <blockquote cite="https://jepsen.io/analyses/redis-raft-1b3fbf6">
    <p>A second replication strategy, Redis Cluster, provides transparent sharding
    and majority availability. Like Redis Sentinel, it uses asynchronous
    replication, and can lose acknowledged writes during some types of network
    failures[.]</p>
    <p>[...]</p>
    <p>Redis Sentinel, Redis Cluster, and Active-Active Geo-Distribution all allow
    lost updates—at least for some workloads.</p>
    <p>[...]</p>
    <p>In discussions with Jepsen, Redis Labs clarified that Redis Enterprise can
    offer ACID characteristics, but only with significant limitations.
    Replication must be disabled, the write-ahead log must be set to fsync on
    every write, and there is no way to roll back when transactions fail. These
    factors are not clearly documented, but Redis Labs plans to document them
    in the future.</p>
    <p>In short, users who want fault-tolerance and not lost updates need
    something stronger than existing Redis replication systems. Whereupon: the
    creation of Redis-Raft.</p>
    <p>[...]</p>
    <p>Tangentially, we were surprised to discover that Redis Enterprise’s claim
    of “full ACID compliance” only applied to specially-configured non-redundant
    deployments, rather than replicated clusters. While Jepsen has not
    experimentally confirmed data loss in Redis Enterprise, our discussions with
    Redis Labs suggest that users should not, in general, assume that multi-node
    Redis deployments offer ACID guarantees. We agree with Redis Labs that the
    documentation should make this clear.</p>
  </blockquote>
  <figcaption>—<a href="https://jepsen.io/analyses/redis-raft-1b3fbf6">Jepsen: Redis-Raft 1b3fbf6</a></figcaption>
</figure>

<p>So if you want to use a distributed Redis system for your task queue, you will
need to do your own research in this regard.  I’m not an expert in distributed
database systems (yet), but for a task queue where you don’t want to lose data,
you will want <em>at least</em> sequential serializable consistency.</p>

<p>So, when you hit a certain scale, you are going to need to go beyond what Redis
can offer you.  And thankfully, there are systems that can offer you this
consistency, and still be fast enough to handle your task
queue<sup id="fnref:fast-task-queue"><a href="#fn:fast-task-queue" class="footnote" rel="footnote" role="doc-noteref">16</a></sup>.</p>

<h3 id="kafka">Kafka</h3>

<p><a href="https://kafka.apache.org/">Kafka</a> is a distributed streaming platform.  It is a distributed commit log,
where you can write messages to a topic, and then read messages from that topic
in the order that they were written.  It is astonishingly similar to Redis’s
Streams - though there are definitely big differences, so make sure you adjust
your mental model accordingly - but it is also a lot more powerful, more
scalable, and offers more throughput.  Accordingly, it is also more complex.</p>

<p>See your <s>doctor</s> engineer to see if Kafka is right for you.</p>

<h3 id="rabbitmq">RabbitMQ</h3>

<p><a href="https://www.rabbitmq.com/">RabbitMQ</a> is a message broker.  It is fundamentally different from Kafka and
Redis Streams, and is more similar to a combination of Redis’s Pub/Sub and
lists.  You define the structure of your queues, exchanges, and consumers, and
RabbitMQ will route them accordingly.  RabbitMQ is a lot more focused on
<em>message passing</em> rather than <em>event streaming</em>, which might make sense for our
workload here.</p>

<p>RabbitMQ can be configured to be in a cluster, but queues are not replicated
across the cluster by default - instead, you must use a custom queue type
to do so.  Currently, RabbitMQ’s documentation states the following:</p>

<figure>
  <blockquote cite="https://www.rabbitmq.com/quorum-queues.html">
    <p>The quorum queue is a modern queue type for RabbitMQ implementing a
    durable, replicated FIFO queue based on the Raft consensus algorithm. It is
    available as of RabbitMQ 3.8.0.</p>
    <p>Quorum queues and streams replace durable mirrored queues, the original
    replicated queue type which is now deprecated and scheduled for removal.</p>
    <p>Quorum queues are optimized for set of use cases where data safety is a
    top priority. This is covered in Motivation. Quorum queues should be
    considered the default option for a replicated queue type.</p>
  </blockquote>
  <figcaption>—<a href="https://www.rabbitmq.com/quorum-queues.html">Quorum Queues</a>, RabbitMQ</figcaption>
</figure>

<h3 id="postgresql">PostgreSQL</h3>

<p><a href="https://www.postgresql.org/">PostgreSQL</a> is a relational database.</p>

<p>You <em>could</em>, theoretically, implement a lot of these things within PostgreSQL.
There’s even extensions for this.</p>

<p>If you do want to go down this path, <a href="https://www.postgresql.org/docs/15/explicit-locking.html#LOCKING-ROWS"><code class="language-plaintext highlighter-rouge">FOR UPDATE</code></a><a href="https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE"><code class="language-plaintext highlighter-rouge"> SKIP LOCKED</code></a>
will likely be your friend; but you will need to be careful about how you
structure your queries, how you will handle failures, and how you will handle
deadlocks.</p>

<p>I’ve tried implementing this, but I’ve found that it’s a lot of work to get
<em>right</em>, let alone to get something that is nearly as performant as just using
Redis<sup id="fnref:skill-issue"><a href="#fn:skill-issue" class="footnote" rel="footnote" role="doc-noteref">17</a></sup>.</p>

<h2 id="discussion">Discussion</h2>

<p>This was a deep dive I had to take to understand the complexities of “just
getting a task queue to work.”  I hope you’re able to learn from my mistakes,
and that you’re able to make a more informed decision about what you want to
use for your task queue.</p>

<p>There is no shame in not reinventing the wheel.  These systems are complex, for
good reasons, and people have spent a lot of time and effort to make them
reliable and performant.  If you can use them, you should!  But every system
is geared for a specific purpose - and the context of what you’re trying to
solve will determine what you should use.  Even if you shouldn’t reinvent the
wheel, fitting a square peg into a round hole is still a bad idea.  I hope this
article has helped you understand more about the problem in depth!</p>

<p>Good luck, and feel free to let me know if I’ve left something out!</p>

<hr />
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:task-loss">

      <p>This is a difficult invariant to prove, but it is essential.  If,
for example, you wanted to do post-processing of an upload (e.g. for a
user’s avatar), if the task was never put on the queue, the avatar would
never be processed, and the user would be left with a broken avatar.  This
is not only a bad user experience, but it leaves inconsistent state in the
application - the file is uploaded somewhere, and has to be there for the
task to eventually process it, but the task was never put on the queue, so
the file is just sitting there, taking up space, when it can’t even be seen
by the user.  If you can’t guarantee that the task will be put on the queue,
then you should have some periodic job (does that put a task on the queue?)
that checks the state of the database to ensure e.g. avatar entries aren’t
left in this inconsistent state for too long. <a href="#fnref:task-loss" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:task-state">

      <p>There is a problem involving the state of the task here.  Going back to the
avatar example, when the user uploads a new avatar before the first one is
done processing, both uploads are now in the queue.  If we transfer all
state about the avatar <em>and</em> user in the task, the consumer will never know
that the first avatar was replaced, and will process both avatars.  This
is wasted work at best, but at worst, it can cause inconsistency issues in
the database.  So even if you transfer the entire snapshot of the state of
the resource with the task, you still need to be able to pull the current
state of the resource from the database, in the consumer, and compare it to
the expected state of the resource in the task.  If they differ, you can
do something different - in this avatar example, you can clean up the files
from the first avatar, as it’s no longer ever going to be used. <a href="#fnref:task-state" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:retry-delay">

      <p>It is similarly prudent to add some sort of delay to the retry.  If there
is a transient fault, and the consumer is unable to connect to the database,
it makes sense to slow the retry so that the database has time to recover.
If it is not a transient fault with e.g. the database, then it makes sense
to reduce the number of tasks that are falsely flagged as failed and
discarded.  Similarly, if a consumer finds that it keeps failing tasks, it
might make sense to kill the consumer, as it is likely in a bad state - it
should not be failing to consume tasks. <a href="#fnref:retry-delay" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:random-death">

      <p>Even if you have the most hardened kernel, and a proofed application, a
stray cosmic ray can still cause a bit to flip, and cause a process to
randomly die.  It’s not likely, but it’s possible.  Best to be sure, just
in case. <a href="#fnref:random-death" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:hard-problem">

      <p>It sorta feels like one of those problems that reduces to the halting
problem at its core - and if you could solve that, you’d be a very rich
person. <a href="#fnref:hard-problem" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:csq-tense">

      <p>This is actually false.  The consumer-specific queue should contain items
that the consumer <em>has claimed</em>, but not necessarily items that the
consumer <em>is currently working on</em>.  This is because the consumer might
have claimed an item, but not started working on it yet.  This is an
optimization, though - consumers might pull multiple items at once to
increase throughput of processing items.  But considering we’re expecting
each task here to take seconds, if not minutes, to complete, it’s not
necessary to optimize for a couple milliseconds of network latency. <a href="#fnref:csq-tense" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:task-exclusivity">

      <p>Assuming multiple consumers are running at the same time, then Redis will
have to decide which consumer gets a new item first.  While redis does
resolve this with FIFO semantics, the distribution of which consumers get
what when, and how “fair” that algorithm is, is something to keep in mind,
especially as you scale or get more complex systems. <a href="#fnref:task-exclusivity" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:round-trips">

      <p>The whole algorithm can’t be done in a simple Lua script, as according to
the <a href="https://redis.io/commands/eval/">Redis documentation</a>, <code class="language-plaintext highlighter-rouge">EVAL</code> scripts “<strong>should only</strong>
access keys whose names are given as input arguments. Scripts
<strong>should never</strong> access keys with programmatically-generated names or based
on the contents of data structures stored in the database.”  This means that
step (2) will have to be done in at least two parts; one to get the list of
consumers, and one to get the tasks from each consumer’s queue.  Step
(3) can be done in one script, though. <a href="#fnref:round-trips" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:stream">

      <p>Streams have a ton more uses, too, and give us so much flexibility in seeing
into the system; for example, we can inspect a stream at any point in time
to see what tasks have been processed and what will be processed, assuming
we haven’t trimmed the stream. <a href="#fnref:stream" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:extra-work">

      <p>It’s also not atomic, so we’d have to do this in a script.  And for a very
simple use-case where you have one type of consumer, it didn’t make sense
to add this as a feature. <a href="#fnref:extra-work" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:other-ids">

      <p>There are other special IDs, like <code class="language-plaintext highlighter-rouge">$</code>, which means “the last message in the
stream,” and <code class="language-plaintext highlighter-rouge">0</code>, which means “the first message in the stream.”  Note that
using these IDs with <code class="language-plaintext highlighter-rouge">XREADGROUP</code> <em>only read from the pending entries list</em>,
and not the stream itself; so this is a good way to recover from a failure. <a href="#fnref:other-ids" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:update-idle-time">

      <p>Though… apparently, these options are “mainly for internal use in order
to transfer the effects of XCLAIM or other commands to the AOF file and to
propagate the same effects to the replicas,” and “are unlikely to be useful
to normal users.”  So, uh, use at your own risk, I guess? <a href="#fnref:update-idle-time" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:idle-timeout">

      <p>We can again abuse <code class="language-plaintext highlighter-rouge">XCLAIM</code> (it’s getting a lot of abuse in this article)
to extend the idle time of a message, if we need to - if we specify
<code class="language-plaintext highlighter-rouge">JUSTID</code>, it won’t increment the attempted deliveries count, and will only
update the idle time - and only return the ID of the message.  We don’t need
the body of the message anyway, so this works!  There is one caveat - if
the message hit the idle timeout, and was claimed by another consumer, this
will silently steal it back, and now both consumers will be processing it.
This process should only be done if you are certain that the message is
still far within the idle threshold. <a href="#fnref:idle-timeout" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:approx">

      <p>It even offers an “approximate” trimming (because of the way streams are
stored internally) - if you specify a max length of “approximately” 1000,
it will trim the stream when it reaches 1000 messages, but it may include
a few more messages than that.  That’s probably fine for any use case of
this.  It also allows <code class="language-plaintext highlighter-rouge">MINID</code> selection - saying that the stream should
only keep messages with an ID greater than a certain ID. <a href="#fnref:approx" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:periodic-cleaning">

      <p><a href="https://redis.io/commands/xpending"><code class="language-plaintext highlighter-rouge">XPENDING</code></a> allows you to retrieve information about what messages are
currently pending, including the smallest and largest IDs of pending
messages.  If you get the smallest ID across all consumer groups, then
no messages before that smallest ID are pending, and can be safely
removed from the stream using <a href="https://redis.io/commands/xtrim"><code class="language-plaintext highlighter-rouge">XTRIM</code></a>’s <code class="language-plaintext highlighter-rouge">MINID</code> entry, which “Evicts
entries with IDs lower than <code class="language-plaintext highlighter-rouge">threshold</code>, where <code class="language-plaintext highlighter-rouge">threshold</code> is a stream ID.” <a href="#fnref:periodic-cleaning" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:fast-task-queue">

      <p>What kind of small web application are you building where you’ve out-grown
Redis?  Should I be concerned? <a href="#fnref:fast-task-queue" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:skill-issue">
      <p>I wouldn’t be surprised if it were just a skill issue. <a href="#fnref:skill-issue" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="blog" /><summary type="html"><![CDATA[A task queue is a great way to handle long-running tasks in a web application; but implementing them is no simple task. These are some of the things I wish I knew before I tried implementing a task queue; hopefully they'll help you avoid some of the pitfalls I encountered.]]></summary></entry></feed>