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

<channel>
	<title>The NeoSmart Files</title>
	<atom:link href="https://neosmart.net/blog/feed/" rel="self" type="application/rss+xml"/>
	<link>https://neosmart.net/blog</link>
	<description>Recovery software and more</description>
	<lastBuildDate>Sat, 07 Feb 2026 22:13:51 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://neosmart.net/blog/wp-content/uploads/cropped-NeoSmart-Padded-32x32.png</url>
	<title>The NeoSmart Files</title>
	<link>https://neosmart.net/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">1082592</site>	<xhtml:meta content="noindex" name="robots" xmlns:xhtml="http://www.w3.org/1999/xhtml"/><item>
		<title>EFTA00400459 has been cracked, DBC12.pdf liberated</title>
		<link>https://neosmart.net/blog/efta00400459-has-been-cracked-dbc12-pdf-liberated/</link>
					<comments>https://neosmart.net/blog/efta00400459-has-been-cracked-dbc12-pdf-liberated/#comments</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Sat, 07 Feb 2026 20:17:22 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[convolutional neural networks]]></category>
		<category><![CDATA[epstein]]></category>
		<category><![CDATA[forensics]]></category>
		<category><![CDATA[machine learning]]></category>
		<category><![CDATA[pdf]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=5409</guid>

					<description><![CDATA[<p>Three days ago, I wrote a post about stumbling across uncensored/unredacted plain-text content in the DOJ Epstein archives that could theoretically be reconstructed and reversed to reveal the uncensored originals of some of the files. Last night, I succeeded in &#8230; <a href="https://neosmart.net/blog/efta00400459-has-been-cracked-dbc12-pdf-liberated/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/efta00400459-has-been-cracked-dbc12-pdf-liberated/">EFTA00400459 has been cracked, DBC12.pdf liberated</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<p>Three days ago, I wrote a post about stumbling across uncensored/unredacted plain-text content in the DOJ Epstein archives <a href="https://neosmart.net/blog/recreating-epstein-pdfs-from-raw-encoded-attachments/" rel="follow">that could theoretically be reconstructed and reversed</a> to reveal the uncensored originals of some of the files. Last night, <a href="https://x.com/mqudsi/status/2019973242065416237?s=20" rel="follow">I succeeded in doing just that</a>, and was able to extract the DBC12 PDF file from the Epstein archive <a href="https://archive.org/details/efta-00400459" rel="follow">EFTA00400459</a> document. Don&#8217;t get too excited: as expected, the exfiltrated document (<a href="https://archive.org/details/dbc-12-one-page-invite-with-reply" rel="follow">available for download here</a>) turned out to be nothing too juicy and is &#8220;just&#8221; another exemplar of the cronyism and the incestuous circles of financial funding that Epstein and his colleagues engaged in.</p>
<p>There is a lot I could say in this post about the different approaches I took and the interesting rabbit holes I went down in trying to extract valid base64 data from the images included in the PDF; however, I am somewhat exhausted from all this and don&#8217;t have the energy to go into it all in as much detail as it deserves, so I&#8217;ll just post some bullet points with the main takeaways:</p>
<p><span id="more-5409"></span></p>
<p><strong>Properly extracting the PDF images</strong></p>
<p>I had made a mistake in how I converted the original PDF into images for processing. u/BCMM on r/netsec <a href="https://old.reddit.com/r/netsec/comments/1qw4sfa/recreating_uncensored_epstein_pdfs_from_raw/o3rmxa6/" rel="follow">reminded me</a> of a different tool from the <code>poppler-utils</code> package that could extract the images as originally embedded in the PDF, whereas my approach with <code>pdftoppm</code> was unnecessarily re-rasterizing the PDF and possibly reducing the quality (as well as increasing the size).</p>
<p>It turns out I had rasterized the PDF at a high enough resolution/density that it made little to no visual difference, but it did get rid of the Bates numbering/identification at the bottom of each page and was definitely the correct approach I should have used from the beginning. Funnily enough, when I started typing out <code>pdfima—</code> in my shell, a history completion from the last time I used it (admittedly long ago) popped up, so I can&#8217;t even claim to have been unaware of its existence.</p>
<p>I re-uploaded the images (of the redacted emails) from EFTA00400459 to a new archive and <a href="https://archive.org/details/efta-00400459_pages" rel="follow">you can download it here</a>.</p>
<p><strong>OCR is a no-go for base64 text</strong></p>
<p>I learned much more than I ever wanted to<sup id="rf1-5409"><a href="https://neosmart.net/blog/efta00400459-has-been-cracked-dbc12-pdf-liberated/#fn1-5409" title="Okay, that&rsquo;s not true. I love learning about random stuff and I don&rsquo;t know why people say this about anything." rel="footnote">1</a></sup> about OCR. Some takeaways that I plan on turning into a proper post sometime soon (famous last words, I know):</p>
<ul>
<li>I had assumed OCR would be less prone to &#8220;hallucinations&#8221; than an LLM because the traditional OCR tools are all heuristic/algorithm-driven. This is true, and they do not hallucinate in the same sense that an LLM does, but they are not meant to be used for faithful reconstructions where byte-for-byte accuracy is desired. It turns out they all (more or less) work by trying to make sensible words out of the characters they recognize at a lower level (this is why OCR needs to understand your language, not just its script/alphabet, for best results). This obviously doesn&#8217;t work for base64, or anything else that requires context-free reconstruction of the original.</li>
<li>Tesseract supposedly has a way to turn off at least some of these heuristics, but that did not improve its performance in any measurable way, though there&#8217;s always the chance that I was holding it wrong:
<p><div id="attachment_5411" style="width: 804px" class="wp-caption aligncenter"><a href="https://neosmart.net/blog/wp-content/uploads/2026/02/tessconfig.webp" rel="follow"><img fetchpriority="high" decoding="async" aria-describedby="caption-attachment-5411" class="size-full wp-image-5411 colorbox-5409" src="https://neosmart.net/blog/wp-content/uploads/2026/02/tessconfig.webp" alt="" width="794" height="434" /></a><p id="caption-attachment-5411" class="wp-caption-text">The tesseract(1) tunables I tried using to get it to work better with images containing base64 data.</p></div></li>
<li>The vast, vast majority of OCR usecases and training corpora are done with proportional fonts (the opposite of a monospace font). In my experience, all the engines ironically performed better at recognizing images with the much less regularly spaced (and, therefore, harder to algorithmically separate) proportional fonts than they did with monospace fonts.</li>
<li>Adobe Acrobat OCR is terrible. Just terrible. Don&#8217;t pay for it.</li>
</ul>
<p><strong>Trying a data science approach</strong></p>
<p>My first idea for tackling this without OCR was to identify the bounding boxes for each character on the page (in order, of course) and cluster them using kmeans with <em>k</em> set to <code>64</code> (or <code>65</code> if you want to consider the <code>=</code> padding). Theoretically, there&#8217;s no reason this shouldn&#8217;t work. In practice, it just didn&#8217;t.</p>
<p>I&#8217;m sure the approach itself is sound, but the problem was trying to get regular enough inputs. However hard I tried, I was unable to get the characters sliced regularly and perfectly enough to get <code>scikit-learn</code> to give me even remotely sane results with the <a href="https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html" rel="follow">KMeans</a> module. I tried feeding it the images thresholded to black-and-white, greyscaled, and even as three-dimensional color inputs (essentially the lossless data). Every time it would create repeated buckets for some letters while sticking the actually different characters into other buckets.</p>
<p>Instead of letting kmeans decide the buckets for me, I decided to try seeding the buckets myself, rendering each letter of the base64 alphabet in Courier New at the right scale/point size to match the scans, then using different methods to bucket the letters into the right bins. But despite these being monospaced fonts, perhaps as an artifact of the resolution the images were captured at, I couldn&#8217;t get some glyphs to include a hint of the letter that came before/after, and tweaking it one way (adding a border on one side) would solve it for some inputs but break others (e.g. if you clip even one pixel from the righthand side of the box surrounding the letter <code>O</code> at the font size and DPI the images were captured/rendered in, it turns into a <code>C</code>).</p>
<!-- Support Callout Start -->
<div style="
    margin: 35px 0;
    padding: 25px;
    background-color: #f8fbfe;
    border-left: 5px solid #1b8be0;
    border-top: 1px solid #e1eaf1;
    border-right: 1px solid #e1eaf1;
    border-bottom: 1px solid #e1eaf1;
    border-radius: 0 4px 4px 0;
    font-family: Georgia, 'Times New Roman', serif;
    line-height: 1.6;
    box-shadow: 0 2px 5px rgba(0,0,0,0.03);
">
    <p style="margin: 0; color: #333; font-size: 1.15em;">
        <span style="color: #1b8be0; font-weight: bold; font-family: Arial, sans-serif; text-transform: uppercase; font-size: 0.85em; letter-spacing: 1px; display: block; margin-bottom: 8px;">
            A note from the author
        </span>
        I do this for the love of the game and in the hopes of hopefully exposing some bad people. But I do have a wife and small children and this takes me away from my work that pays the bills. If you would like to support me on this mission, you can make a contribution
        <a href="https://mqudsi.com/donate/" style="color: #1b8be0; text-decoration: underline; font-weight: bold; transition: color 0.2s;">here</a>.
    </p>
</div>
<!-- Support Callout End -->
<p>I turned to an LLM for help, but they are really bad at deriving things from first principles even when you give them all the information and constraints beforehand. Gemini 3 kept insisting I try <a href="https://en.wikipedia.org/wiki/Histogram_of_oriented_gradients" rel="follow">HOG</a> to handle the inputs and would repeatedly insist on trying a <a href="https://en.wikipedia.org/wiki/Connected-component_labeling" rel="follow">connected components</a> approach to weed out noise (some on r/netsec were insisting that these images were 100% noise-free and without any encoding artifacts – this is patently not true), despite the fact that that absolutely destroys the recognition even if you apply the same algorithm to your seed buckets.</p>
<p>This approach with seeding the buckets yielded ~fair results, but it was completely stymied by the <code>l</code> vs <code>1</code> conundrum <a href="https://neosmart.net/blog/recreating-epstein-pdfs-from-raw-encoded-attachments/" rel="follow">discussed in the previous post</a>. Subpixel hinting differences between the ClearType algorithm used on the PCs the DoJ was using to originally render these emails and the font rendering engine used by OpenCV might look like nothing, but when the difference between an <code>l</code> and a <code>1</code> is 3 pixels at most, it adds up. Also, it didn&#8217;t help that my bounding box algorithm to draw a grid around the text and isolate each letter wasn&#8217;t perfect – I think there was a fractional-pixel mismatch everywhere in the 2x (losslessly) upscaled sources I was using, and that threw everything off (while using the 1x sources made it hard to slice individual characters, though I know the algorithm could have been tweaked to fix this).</p>
<p><strong>Image kernels can&#8217;t solve bad typography</strong></p>
<p>A number of people in the comments and on social media kept suggesting applying various kernels to the input in order to get better OCR results. But OCR wasn&#8217;t just getting <code>1</code> vs <code>l</code> wrong, it was making up or omitting letters altogether, giving me 75-81 characters per line instead of the very-much-fixed 78 monospaced characters I was feeding it. And using them to try and improve the situation with the classifier I wrote turned out to also be futile: if you darken pixels above a certain threshold, you make the difference between the <code>1</code> and the <code>l</code> more noticeable, but at that threshold you also darken the subpixel hinting around the lowercase <code>w</code> turning into a capital <code>N</code>.<sup id="rf2-5409"><a href="https://neosmart.net/blog/efta00400459-has-been-cracked-dbc12-pdf-liberated/#fn2-5409" title="If I remember correctly! I wasn&rsquo;t taking notes (I wish I had, and more screenshots, too) and it could have been two different characters." rel="footnote">2</a></sup> The kernels to make one pair of letters distinguishable from another would ultimately hurt the recognition of another pair, unless you knew in advance which to apply them to and which not to&#8230; which would imply you had already figured out which letter was which!</p>
<p>Except that&#8217;s not true: of course you could make a multi-level classifier to first just identify the ambiguous pairs like <code>l</code> and <code>1</code> and then feed those into a secondary classifier that applied the kernels, then tried bucketing these separately. But you know what? I didn&#8217;t think of that at the time. Mea culpa!</p>
<p><strong>CNNs really are powerful dark magic</strong></p>
<p>With the traditional image processing/data science classification approach not working too well, I decided to try another approach and see if I could train a <a href="https://en.wikipedia.org/wiki/Convolutional_neural_network" rel="follow">CNN</a> to handle both the discrepancies with the imperfect slicing around each character/cell, noise from adjacent cells abutting into the cell being analyzed, <em>and</em> the difference between <code>1</code> vs <code>l</code>. And you know what? I think I&#8217;m going to have to start using CNNs more!</p>
<p>They really are a powerful answer to a lot of this stuff. After just typing out two lines of base64 data from the first page of the PDF<sup id="rf3-5409"><a href="https://neosmart.net/blog/efta00400459-has-been-cracked-dbc12-pdf-liberated/#fn3-5409" title="Well, second page, actually. The first page is mostly just the correspondence with one line of base64, but the second page onwards are 100% base64 content." rel="footnote">3</a></sup> and training the CNN on those as ground truth, it was able to correctly identify <a href="https://x.com/mqudsi/status/2019813069099258193?s=20" rel="follow">the vast majority of characters</a> in the input corpus, or at least the ones I could visibly tell it had gotten right, meaning I couldn&#8217;t be sure if it had handled <code>l</code> vs <code>1</code> correctly or not.</p>
<p>It turns out the alignment grid was off by a vertical pixel or two by the time it reached the end of the page, so tweaking the training algorithm to train against the top x lines and the bottom x lines of the page separately got rid of the remaining recognition errors&#8230; or at least, it did for the first page.</p>
<div id="attachment_5412" style="width: 630px" class="wp-caption aligncenter"><a href="https://neosmart.net/blog/wp-content/uploads/2026/02/grid-comparison.png" rel="follow"><img decoding="async" aria-describedby="caption-attachment-5412" class="wp-image-5412 size-large colorbox-5409" src="https://neosmart.net/blog/wp-content/uploads/2026/02/grid-comparison-1024x359.png" alt="" width="620" height="217" srcset="https://neosmart.net/blog/wp-content/uploads/2026/02/grid-comparison-1024x359.png 1024w, https://neosmart.net/blog/wp-content/uploads/2026/02/grid-comparison-1536x538.png 1536w, https://neosmart.net/blog/wp-content/uploads/2026/02/grid-comparison-600x210.png 600w, https://neosmart.net/blog/wp-content/uploads/2026/02/grid-comparison-500x175.png 500w, https://neosmart.net/blog/wp-content/uploads/2026/02/grid-comparison.png 1758w" sizes="(max-width: 620px) 100vw, 620px" /></a><p id="caption-attachment-5412" class="wp-caption-text">It&#8217;s really hard to see, but while the grid is almost identically placed around the characters at the top-left vs bottom-right, the subtle difference is there.</p></div>
<p>But alignment issues with how the bounding box was drawn on subsequent pages pushed the error rate well above the necessary zero, and it wasn&#8217;t until I realized that since these pages weren&#8217;t scanned but rather rendered into images digitally, I could just have the training/inference script memorize the grid from the first page and reuse it on subsequent pages was I able to get rid of all the recognition errors on all pages. (As an interesting sidebar, despite augmenting the training data with artificially generated inputs introducing randomized subtle (<code>-2px</code> to <code>+2px</code>) vertical/horizontal shifts, that wasn&#8217;t enough to address the grid drift.)</p>
<p>Well, all except the same pesky <code>1</code> vs <code>l</code> which still plagued the outputs and led to decode errors.</p>
<p>I admit I wasted a lot of time here barking up the wrong tree: I spent hours tweaking the CNN&#8217;s layout, increasing the kernel size of the first layer, getting rid of the second MaxPool, playing with further augmenting the input samples, trying denoising and alignment techniques, and much more trying to figure out how to induce the CNN into correctly recognizing the two pixel difference between the ones and the ells, all to no avail. I kept trying to increase the training data, meticulously typing out line after line of base64 (becoming thoroughly sick and tired of zooming and panning) to see if that&#8217;s what it would take to nudge the error rate down further.</p>
<div id="attachment_5413" style="width: 630px" class="wp-caption aligncenter"><a href="https://neosmart.net/blog/wp-content/uploads/2026/02/Training-Typo-Checking.png" rel="follow"><img decoding="async" aria-describedby="caption-attachment-5413" class="size-large wp-image-5413 colorbox-5409" src="https://neosmart.net/blog/wp-content/uploads/2026/02/Training-Typo-Checking-1024x626.png" alt="" width="620" height="379" srcset="https://neosmart.net/blog/wp-content/uploads/2026/02/Training-Typo-Checking-1024x626.png 1024w, https://neosmart.net/blog/wp-content/uploads/2026/02/Training-Typo-Checking-1536x939.png 1536w, https://neosmart.net/blog/wp-content/uploads/2026/02/Training-Typo-Checking-600x367.png 600w, https://neosmart.net/blog/wp-content/uploads/2026/02/Training-Typo-Checking-491x300.png 491w, https://neosmart.net/blog/wp-content/uploads/2026/02/Training-Typo-Checking.png 1800w" sizes="(max-width: 620px) 100vw, 620px" /></a><p id="caption-attachment-5413" class="wp-caption-text">A debug view I added to the training script after I mistyped a character one time too many. It displays the greatest deviation between the average shape of all characters in each training-provided &#8220;ground truth&#8221; bucket and the max outlier in the same.</p></div>
<p>It was only after I took a break for my sanity and came back to doggedly tackle it again that I realized one of the errors <code>qpdf</code> reported for the recovered PDF never changed no matter how much I tweaked the CNN or how much additional training data I supplied that I realized the problem: despite zooming in and taking a good 5 or 10 seconds on each <code>1</code> vs <code>l</code> I was entering into the training data, <em>I had still gotten some wrong!</em> A second email forward/reply chain with the same base64 content was included in the DoJ archives (EFTA02154109) and while it wasn&#8217;t at a much better resolution or quality than the first, it was still <em>different</em> and the <code>1</code>s and <code>l</code>s were distinguished with different quirks. After I spotted one mistake I had made, I quickly found a few more (and this was even with zooming in on the training corpus debug view pictured above and verifying that the ones and ells had the expected differences!), and lo and behold, the recovered PDF validated and opened!</p>
<p><a href="https://github.com/mqudsi/monospace-ocr" rel="nofollow">I posted the code that solved it to GitHub</a>, but apologies in advance: I didn&#8217;t take the time to make the harness scripts properly portable. Everything works and they should run on your machine as well as they run on mine, except they&#8217;re using a very idiosyncratic mix of tooling. The scripts are written in <a href="https://github.com/fish-shell/fish-shell" rel="nofollow">fish</a> instead of <code>sh</code> or even <code>bash</code>, use <code>parallel</code> (which I actually hate), and have some WSL-isms baked into them for (my) practicality. The README was rushed, and some of the (relative) paths to input files are baked in instead of being configurable. Maybe I&#8217;ll get to it. We&#8217;ll see.</p>
<p>As to what&#8217;s next, well, there are more base64-encoded attachments where this one came from. But unfortunately I cannot reuse this approach as the interesting-looking ones I&#8217;ve found are all in proportional fonts this time around!</p>
<p><strong>Follow me at <a href="https://twitter.com/mqudsi" rel="follow">@mqudsi</a> or <a href="https://twitter.com/neosmart" rel="follow">@neosmart</a> for more updates or to have a look at the progress posts I&#8217;ve shared over the past 48 hours while trying to get to the bottom of this</strong>. </p>
<p>Thanks for tuning in!</p>
<!-- Support Callout Start -->
<div style="
    margin: 35px 0;
    padding: 25px;
    background-color: #f8fbfe;
    border-left: 5px solid #1b8be0;
    border-top: 1px solid #e1eaf1;
    border-right: 1px solid #e1eaf1;
    border-bottom: 1px solid #e1eaf1;
    border-radius: 0 4px 4px 0;
    font-family: Georgia, 'Times New Roman', serif;
    line-height: 1.6;
    box-shadow: 0 2px 5px rgba(0,0,0,0.03);
">
    <p style="margin: 0; color: #333; font-size: 1.15em;">
        <span style="color: #1b8be0; font-weight: bold; font-family: Arial, sans-serif; text-transform: uppercase; font-size: 0.85em; letter-spacing: 1px; display: block; margin-bottom: 8px;">
            A note from the author
        </span>
        I do this for the love of the game and in the hopes of hopefully exposing some bad people. But I do have a wife and small children and this takes me away from my work that pays the bills. If you would like to support me on this mission, you can make a contribution
        <a href="https://mqudsi.com/donate/" style="color: #1b8be0; text-decoration: underline; font-weight: bold; transition: color 0.2s;">here</a>.
    </p>
</div>
<!-- Support Callout End -->
<hr class="footnotes"><ol class="footnotes"><li id="fn1-5409"><p>Okay, that&#8217;s not true. I love learning about random stuff and I don&#8217;t know why people say this about anything.&nbsp;<a href="#rf1-5409" class="backlink" title="Jump back to footnote 1 in the text.">&#8617;</a></p></li><li id="fn2-5409"><p>If I remember correctly! I wasn&#8217;t taking notes (I wish I had, and more screenshots, too) and it could have been two different characters.&nbsp;<a href="#rf2-5409" class="backlink" title="Jump back to footnote 2 in the text.">&#8617;</a></p></li><li id="fn3-5409"><p>Well, second page, actually. The first page is mostly just the correspondence with one line of base64, but the second page onwards are 100% base64 content.&nbsp;<a href="#rf3-5409" class="backlink" title="Jump back to footnote 3 in the text.">&#8617;</a></p></li></ol>The post <a href="https://neosmart.net/blog/efta00400459-has-been-cracked-dbc12-pdf-liberated/">EFTA00400459 has been cracked, DBC12.pdf liberated</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/efta00400459-has-been-cracked-dbc12-pdf-liberated/feed/</wfw:commentRss>
			<slash:comments>19</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5409</post-id>	</item>
		<item>
		<title>Recreating uncensored Epstein PDFs from raw encoded attachments</title>
		<link>https://neosmart.net/blog/recreating-epstein-pdfs-from-raw-encoded-attachments/</link>
					<comments>https://neosmart.net/blog/recreating-epstein-pdfs-from-raw-encoded-attachments/#comments</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Wed, 04 Feb 2026 19:18:39 +0000</pubDate>
				<category><![CDATA[Random]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[base64]]></category>
		<category><![CDATA[courier new]]></category>
		<category><![CDATA[epstein]]></category>
		<category><![CDATA[fonts]]></category>
		<category><![CDATA[forensics]]></category>
		<category><![CDATA[ocr]]></category>
		<category><![CDATA[pdf]]></category>
		<category><![CDATA[textract]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=5373</guid>

					<description><![CDATA[<p>Heads-up: An update to this article has been posted. There have been a lot of complaints about both the competency and the logic behind the latest Epstein archive release by the DoJ: from censoring the names of co-conspirators to censoring &#8230; <a href="https://neosmart.net/blog/recreating-epstein-pdfs-from-raw-encoded-attachments/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/recreating-epstein-pdfs-from-raw-encoded-attachments/">Recreating uncensored Epstein PDFs from raw encoded attachments</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<p><em>Heads-up: An update to this article <a href="https://neosmart.net/blog/efta00400459-has-been-cracked-dbc12-pdf-liberated/" rel="follow">has been posted</a>.</em></p>
<p>There have been a lot of complaints about both the competency and the logic behind the latest Epstein archive release by the DoJ: from censoring the names of co-conspirators to <a href="https://x.com/Surajit_/status/2018007528110776656?s=20" rel="follow">censoring pictures of random women</a> in a way that makes individuals look guiltier <a href="https://x.com/FATCAed/status/2018258403336815092?s=20" rel="follow">than they really are</a>, <a href="https://x.com/vxunderground/status/2018914471834456465?s=20" rel="follow">forgetting to redact credentials</a> that made it possible for all of Reddit to log into Epstein&#8217;s account and trample over all the evidence, and the complete ineptitude that resulted in most of the latest batch being corrupted thanks to <a href="https://x.com/mqudsi/status/2017790922830893422?s=20" rel="follow">incorrectly converted Quoted-Printable encoding artifacts</a>, it&#8217;s safe to say that Pam Bondi&#8217;s DoJ did not put its best and brightest on this (admittedly gargantuan) undertaking. But the most damning evidence has all been thoroughly redacted… hasn&#8217;t it? Well, maybe not.</p>
<p>I was thinking of writing an article on the mangled quoted-printable encoding the day this latest dump came out in response to all the misinformed musings and conjectures that were littering social media (and my dilly-dallying cost me, as someone <a href="https://lars.ingebrigtsen.no/2026/02/02/whats-up-with-all-those-equals-signs-anyway/" rel="follow">beat me to the punch</a>), and spent some time searching through the latest archives looking  for some SMTP headers that I could use in the article when I came across a curious artifact: not only were the emails badly transcoded into plain text, but also some binary attachments were actually included in the dumps in their over-the-wire <code>Content-Transfer-Encoding: base64</code> format, and the unlucky intern that was assigned to the documents in question didn&#8217;t realize the significance of what they were looking at and didn&#8217;t see the point in censoring seemingly meaningless page after page of hex content!</p>
<p><span id="more-5373"></span></p>
<p>Just take a look at <a href="https://archive.org/details/efta-00400459" rel="follow">EFTA00400459</a>, an email from correspondence between (presumably) one of Epstein&#8217;s assistants and Epstein lackey/co-conspirator Boris Nikolic and his friend, Sam Jaradeh, inviting them to a ████████ benefit:</p>
<p><a href="https://neosmart.net/blog/wp-content/uploads/2026/02/EFTA00400459-Sample.webp" rel="follow"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-5374 colorbox-5373" src="https://neosmart.net/blog/wp-content/uploads/2026/02/EFTA00400459-Sample.webp" alt="" width="870" height="822" /></a></p>
<p>Those hex characters go on for 76 pages, and represent the file <code>DBC12 One Page Invite with Reply.pdf</code> encoded as base64 so that it can be included in the email without breaking the SMTP protocol. And converting it back to the original PDF is, theoretically, as easy as copy-and-pasting those 76 pages into a text editor, stripping the leading <code>&gt;</code> bytes, and piping all that into <code>base64 -d &gt; output.pdf</code>&#8230; or it would be, if we had the original (badly converted) email and not a partially redacted scan of a printout of said email with some shoddy OCR applied.</p>
<p>If you tried to actually copy that text as digitized by the DoJ from the PDF into a text editor, here&#8217;s what you&#8217;d see:</p>
<p><a href="https://neosmart.net/blog/wp-content/uploads/2026/02/DoJ-OCR-Sample.webp" rel="follow"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-5375 colorbox-5373" src="https://neosmart.net/blog/wp-content/uploads/2026/02/DoJ-OCR-Sample.webp" alt="" width="808" height="468" /></a></p>
<p>You can ignore the <code>EFTA00400459</code> on the second line; that (or some variant thereof) will be interspersed into the base64 text since it&#8217;s stamped at the bottom of every page to identify the piece of evidence it came from. But what else do you notice? Here&#8217;s a hint: this is what proper base64 looks like:</p>
<p><a href="https://neosmart.net/blog/wp-content/uploads/2026/02/Proper-base64.webp" rel="follow"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-5376 colorbox-5373" src="https://neosmart.net/blog/wp-content/uploads/2026/02/Proper-base64.webp" alt="" width="692" height="382" /></a></p>
<p>Notice how in this sample everything lines up perfectly (when using a monospaced font) at the right margin? And how that&#8217;s not the case when we copied-and-pasted from the OCR&#8217;d PDF? That&#8217;s because it wasn&#8217;t a great OCR job: extra characters have been hallucinated into the output, some of them not even legal base64 characters such as the <code>,</code> and <code>[</code>, while other characters have been omitted altogether, giving us content we can&#8217;t use:<sup id="rf1-5373"><a href="https://neosmart.net/blog/recreating-epstein-pdfs-from-raw-encoded-attachments/#fn1-5373" title="In case you&rsquo;re wondering, the shell session excerpts in this article are all in fish, which I think is a good fit for string wrangling because of its string builtin with extensive operations with a very human-readable syntax (at least compared to perl or awk, the usual go-tos for string manipulation), and it lets you compose multiple operations as separate commands while not devolving to performing pathologically because no external commands are fork/exec&lsquo;d because of its builtin nature. And I&rsquo;m not just saying that because of the blood, sweat, and tears I&rsquo;ve contributed to the project." rel="footnote">1</a></sup></p>
<pre>&gt; pbpaste \
     | string match -rv 'EFTA' \
     | string trim -c " &gt;" \
     | string join "" \
     | base64 -d &gt;/dev/null
<span style="color: #ff0000;">base64: invalid input</span>
</pre>
<p>I tried the easiest alternative I had at hand: I loaded up the PDF in Adobe Acrobat Pro and re-ran an OCR process on the document, but came up with even worse results, with spaces injected in the middle of the base64 content (easily fixable) in addition to other characters being completely misread and butchered – it really didn&#8217;t like the cramped monospace text at all. So I thought to do it manually with <code>tesseract</code>, which, while very far from state-of-the-art, can still be useful because it lets you do things like limit its output to a certain subset of characters, constraining the field of valid results and hopefully coercing it into producing better results.</p>
<p>Only one problem: <code>tesseract</code> can&#8217;t read PDF input (or not by default, anyway). No problem, I&#8217;ll just use <code>imagemagick</code>/<code>ghostscript</code> to convert the PDF into individual PNG images (to avoid further generational loss) and provide those to <code>tesseract</code>, right? But that didn&#8217;t quite work out, they seem (?) to try to load and perform the conversion of all 76 separate pages/png files all at once, and then naturally crash on too-large inputs (but only after taking forever and generating the 76 (invalid) output files that you&#8217;re forced to subsequently clean up, of course):</p>
<pre>&gt; convert -density 300 EFTA00400459.pdf \
        -background white -alpha remove \
        -alpha off out.png
<span style="color: #ff0000;">convert-im6.q16: cache resources exhausted `/tmp/magick-QqXVSOZutVsiRcs7pLwwG2FYQnTsoAmX47' @ error/cache.c/OpenPixelCache/4119.</span>
<span style="color: #ff0000;">convert-im6.q16: cache resources exhausted `out.png' @ error/cache.c/OpenPixelCache/4119.</span>
<span style="color: #ff0000;">convert-im6.q16: No IDATs written into file `out-0.png' @ error/png.c/MagickPNGErrorHandler/1643.</span>
</pre>
<p>So we turn to <code>pdftoppm</code> from the <code>poppler-utils</code> package instead, which does indeed handle each page of the source PDF separately and turned out to be up to the task, though incredibly slow:</p>
<pre><code class="language-bash">&gt; pdftoppm -png -r 300 EFTA00400459.pdf out.png
</code></pre>
<p>After waiting the requisite amount of time (and then some), I had files <code>out-01.png</code> through <code>out-76.png</code>, and was ready to try them with <code>tesseract</code>:</p>
<pre><code class="language-bash">for n in (printf "%02d\n" (seq 1 76))
    tesseract out-$n.png output-$n \
        --psm 6 \
        -c tessedit_char_whitelist='&gt;'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= \
        -c load_system_dawg=0 \
        -c load_freq_dawg=0
end
</code></pre>
<p>The above <code>fish-shell</code> command <a href="https://manpages.ubuntu.com/manpages/xenial/man1/tesseract.1.html" rel="follow">instructs </a><code><a href="https://manpages.ubuntu.com/manpages/xenial/man1/tesseract.1.html">tesseract</a>(1)</code> to assume the input is a single block of text (the <code>--psm 6</code> argument) and limit itself to decoding only legal base64 characters (and the leading <code>&gt;</code> so we can properly strip it out thereafter). My original attempt included a literal space in the valid char whitelist, but that gave me worse results: the very badly kerned base64 has significant apparent spacing between some adjacent characters (more on this later) and that caused tesseract to both incorrectly inject spaces (bad but fixable) and also possibly affect how it handled the character after the space (worse).</p>
<p>Unfortunately, while tesseract gave me <em>slightly</em> better output than either the original OCR&#8217;d DoJ text or the (terrible) Adobe Acrobat Pro OCR results, it too suffered from poor recognition and gave me very inconsistent line lengths&#8230; but it also suffered from something that I didn&#8217;t really think a heuristic-based, algorithm-driven tool like tesseract would succumb to, as it was more reminiscent of how first-generation LLMs would behave: in a few places, it would only read the first dozen or so characters on a line then leave the rest of the line blank, then pick up (correctly enough) at the start of the next line. Before I saw how generally useless the OCR results were and gave up on tesseract, I figured I&#8217;d just manually type out the rest of the line (the aborted lines were easy enough to find, thanks to the monospaced output), and <em>that</em> was when I ran into the <em>real</em> issue that took this from an interesting challenge to being almost mission impossible.</p>
<!-- Support Callout Start -->
<div style="
    margin: 35px 0;
    padding: 25px;
    background-color: #f8fbfe;
    border-left: 5px solid #1b8be0;
    border-top: 1px solid #e1eaf1;
    border-right: 1px solid #e1eaf1;
    border-bottom: 1px solid #e1eaf1;
    border-radius: 0 4px 4px 0;
    font-family: Georgia, 'Times New Roman', serif;
    line-height: 1.6;
    box-shadow: 0 2px 5px rgba(0,0,0,0.03);
">
    <p style="margin: 0; color: #333; font-size: 1.15em;">
        <span style="color: #1b8be0; font-weight: bold; font-family: Arial, sans-serif; text-transform: uppercase; font-size: 0.85em; letter-spacing: 1px; display: block; margin-bottom: 8px;">
            A note from the author
        </span>
        I do this for the love of the game and in the hopes of hopefully exposing some bad people. But I do have a wife and small children and this takes me away from my work that pays the bills. If you would like to support me on this mission, you can make a contribution
        <a href="https://mqudsi.com/donate/" style="color: #1b8be0; text-decoration: underline; font-weight: bold; transition: color 0.2s;">here</a>.
    </p>
</div>
<!-- Support Callout End -->
<p>I mentioned earlier the bad kerning, which tricked the OCR tools into injecting spaces where there were supposed to be none, but that was far from being the worst issue plaguing the PDF content. The real problem is that the text is rendered in possibly the worst typeface for the job at hand: Courier New.</p>
<p>If you&#8217;re a font enthusiast, I certainly don&#8217;t need to say any more – you&#8217;re probably already shaking with a mix of PTSD and rage. But for the benefit of everyone else, let&#8217;s just say that Courier New is&#8230; not a great font. It was a digitization of the venerable (though certainly primitive) <a href="https://en.wikipedia.org/wiki/Courier_(typeface)" rel="follow">Courier</a> fontface, commissioned by IBM in the 1950s. Courier was used (with some tweaks) for IBM typewriters, including <a href="https://en.wikipedia.org/wiki/IBM_Selectric" rel="follow">the IBM Selectric</a>, and in the 1990s it was &#8220;digitized directly from the golf ball of the IBM Selectric&#8221; by Monotype, and shipped with Windows 3.1, where it remained the default monospace font on Windows <a href="https://neosmart.net/blog/a-comprehensive-look-at-the-new-microsoft-fonts/" rel="follow">until Consolas shipped with Windows Vista</a>. Among the many issues with Courier New is that it was digitized from the Selectric golf ball &#8220;without accounting for the visual weight normally added by the typewriter&#8217;s ink ribbon&#8221;, which gives its characteristic &#8220;thin&#8221; look. Microsoft ClearType, which was only enabled by default with Windows Vista, addressed this major shortcoming to some extent, but Courier New has always struggled with general readability&#8230; and more importantly, with its poor distinction between characters.</p>
<div id="attachment_5377" style="width: 310px" class="wp-caption alignnone"><a href="https://neosmart.net/blog/wp-content/uploads/2026/02/Courier-Weight-Comparison.svg" rel="follow"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-5377" class="wp-image-5377 size-full colorbox-5373" src="https://neosmart.net/blog/wp-content/uploads/2026/02/Courier-Weight-Comparison.svg" alt="" width="300" height="121" /></a><p id="caption-attachment-5377" class="wp-caption-text">You can clearly see how downright anemic Courier New is when compared to the original Courier.</p></div>
<p>While not as bad as some typewriter-era typefaces that actually reused the same symbol for <code>1</code> (one) and <code>l</code> (ell), Courier New came pretty close. Here is a comparison between the two fonts when rendering these two characters, only considerably enlarged:</p>
<div id="attachment_5380" style="width: 310px" class="wp-caption alignnone"><a href="https://neosmart.net/blog/wp-content/uploads/2026/02/Courier-New-1-and-l-Horizontal-Comparison.svg" rel="follow"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-5380" class="wp-image-5380 size-full colorbox-5373" src="https://neosmart.net/blog/wp-content/uploads/2026/02/Courier-New-1-and-l-Horizontal-Comparison.svg" alt="" width="300" height="75" /></a><p id="caption-attachment-5380" class="wp-caption-text">Comparing Courier and Courier New when it comes to differentiating between 1 (one) and l (ell).</p></div>
<p>The combination of the two faults (the anemic weights and the even less distinction between 1 and l as compared to Courier) makes Courier New a terrible choice as a programming font. But as a font used for base64 output you want to OCR? You really couldn&#8217;t pick a worse option! To add fuel to the fire, you&#8217;re looking at SVG outlines of the fonts, meticulously converted and preserving all the fine details. But in the Epstein PDFs released by the DoJ, we only have low-quality JPEG scans at a fairly small point size. Here&#8217;s an actual (losslessly encoded) screenshot of the DoJ text at 100% – I challenge you to tell me which is a <code>1</code> and which is an <code>l</code> in the excerpt below:</p>
<p><a href="https://neosmart.net/blog/wp-content/uploads/2026/02/Scanned-1-vs-l.png" rel="follow"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-5382 colorbox-5373" src="https://neosmart.net/blog/wp-content/uploads/2026/02/Scanned-1-vs-l.png" alt="" width="349" height="213" /></a></p>
<p>It&#8217;s not that there isn&#8217;t <em>any</em> difference between the two, because there is. And sometimes you get a clear gut feeling which is which – I was midway through manually typing out one line of base64 text when I got stuck on identifying a one vs ell&#8230; only to realize that, at the same time, I had confidently transcribed one of them earlier that same line without even pausing to think about which it was. Here&#8217;s a zoomed-in view of the scanned PDF: you can clearly see all the JPEG DCT artifacts, the color fringing, and the smearing of character shapes, all of which make it hard to properly identify the characters. But at the same time, at least in this particular sample, you can see which of the highlighted characters have a straight serif leading out the top-left (the middle, presumably an ell) and which of those have the slightest of strokes/feet extending from them (the first and last, presumably ones). But whether that&#8217;s because that&#8217;s how the original glyph appeared or it&#8217;s because of how the image was compressed, it&#8217;s tough to say:</p>
<p><a href="https://neosmart.net/blog/wp-content/uploads/2026/02/Scanned-1-vs-l-zoomed-in.png" rel="follow"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-5383 colorbox-5373" src="https://neosmart.net/blog/wp-content/uploads/2026/02/Scanned-1-vs-l-zoomed-in.png" alt="" width="891" height="730" srcset="https://neosmart.net/blog/wp-content/uploads/2026/02/Scanned-1-vs-l-zoomed-in.png 891w, https://neosmart.net/blog/wp-content/uploads/2026/02/Scanned-1-vs-l-zoomed-in-600x492.png 600w, https://neosmart.net/blog/wp-content/uploads/2026/02/Scanned-1-vs-l-zoomed-in-366x300.png 366w" sizes="auto, (max-width: 891px) 100vw, 891px" /></a></p>
<p>But that&#8217;s getting ahead of myself: at this point, none of the OCR tools had actually given me usable results, even ignoring the very important question of <code>l</code> vs <code>1</code>. After having been let down by one open source offering (tesseract) and two commercial ones (Adobe Acrobat Pro and, presumably, whatever the DoJ used), I made the very questionable choice of writing a script to use yet another commercial offering, this time <a href="https://aws.amazon.com/textract/pricing/" rel="follow">Amazon/AWS Textract</a>, to process the PDF. Unfortunately, using it directly via the first-party tooling was (somewhat) of a no-go as it only supports smaller/shorter inputs for direct use; longer PDFs like this one need to be uploaded to S3 and then use the async workflow to start the recognition and poll for completion.</p>
<p>Amazon Textract did possibly the best out of all the tools I tried, but its output still had obvious line length discrepancies – albeit only one to two characters or so off on average. I decided to try again, this time blowing up the input 2x (using nearest neighbor sampling to preserve sharp edges) as a workaround for Textract not having a tunable I could set to configure the DPI the document is processed at, though I worried all inputs could possibly be prescaled to a fixed size prior to processing once more:<sup id="rf2-5373"><a href="https://neosmart.net/blog/recreating-epstein-pdfs-from-raw-encoded-attachments/#fn2-5373" title="I didn&rsquo;t want to convert the PNGs back to a single PDF as I didn&rsquo;t want any further loss in quality." rel="footnote">2</a></sup></p>
<pre><code class="language-bash">&gt; for n in (printf "%02d\n" (seq 01 76))
      convert EFTA00400459-$n.png -scale 200% \
              EFTA00400459-$n"_2x".png; or break
  end
&gt; parallel -j 16 ./textract.sh {} ::: EFTA00400459-*_2x.png
</code></pre>
<p>These results were notably better, and I&#8217;ve included them in an archive, but some of the pages scanned better than others. Textract doesn&#8217;t seem to be 100% deterministic from my brief experience with it, and their features page does make vague or unclear mentions to &#8220;ML&#8221;, though it&#8217;s not obvious when and where it kicks in or what it exactly refers to, but that could explain why a couple of the pages (like <code>EFTA00400459-62_2x.txt</code>) are considerably worse than others, even while the source images don&#8217;t show a good reason for that divergence.</p>
<p>With the Textract 2x output cleaned up and piped into <code>base64 -i</code> (which ignores garbage data, generating invalid results that can still be usable for forensic analysis), I can get far enough to see that the PDF within the PDF (i.e. the actual PDF attachment originally sent) was at least partially (de)flate-encoded. Unfortunately, PDFs are binary files with different forms of compression applied; you can&#8217;t just use something like <code>strings</code> to extract any usable content. <a href="https://man.archlinux.org/man/extra/qpdf/qpdf.1.en" rel="follow"><code>qpdf(1)</code> can be (ab)used</a> to decompress a PDF (while leaving it a PDF) via <code>qpdf --qdf --object-streams=disable input.pdf decompressed.pdf</code>, but, predictably, this doesn&#8217;t work when your input is garbled and corrupted:</p>
<pre>&gt; qpdf --qdf --object-streams=disable recovered.pdf decompressed.pdf
<span style="color: #ff0000;">WARNING: recovered.pdf: file is damaged
WARNING: recovered.pdf: can't find startxref
WARNING: recovered.pdf: Attempting to reconstruct cross-reference table
WARNING: recovered.pdf (object 34 0, offset 52): unknown token while reading object; treating as string
WARNING: recovered.pdf (object 34 0, offset 70): unknown token while reading object; treating as string
WARNING: recovered.pdf (object 34 0, offset 85): unknown token while reading object; treating as string
WARNING: recovered.pdf (object 34 0, offset 90): unexpected &gt;
WARNING: recovered.pdf (object 34 0, offset 92): unknown token while reading object; treating as string
WARNING: recovered.pdf (object 34 0, offset 116): unknown token while reading object; treating as string
WARNING: recovered.pdf (object 34 0, offset 121): unknown token while reading object; treating as string
WARNING: recovered.pdf (object 34 0, offset 121): too many errors; giving up on reading object
WARNING: recovered.pdf (object 34 0, offset 125): expected endobj
WARNING: recovered.pdf (object 41 0, offset 9562): expected endstream
WARNING: recovered.pdf (object 41 0, offset 8010): attempting to recover stream length
WARNING: recovered.pdf (object 41 0, offset 8010): unable to recover stream data; treating stream as empty
WARNING: recovered.pdf (object 41 0, offset 9616): expected endobj
WARNING: recovered.pdf (object 41 0, offset 9616): EOF after endobj
qpdf: recovered.pdf: unable to find trailer dictionary while recovering damaged file</span>
</pre>
<p>Between the inconsistent OCR results and the problem with the <code>l</code> vs <code>1</code>, it&#8217;s not a very encouraging situation. To me, this is a problem begging for a (traditional, non-LLM) ML solution, specifically leveraging the fact that we know the font in question and, roughly, the compression applied. Alas, I don&#8217;t have more time to lend to this challenge at the moment, as there are a number of things I set aside just in order to publish this article.</p>
<p>So here&#8217;s the challenge for anyone I can successfully nerdsnipe:</p>
<ul>
<li>Can you manage to recreate the original PDF from the <code>Content-Transfer-Encoding: base64</code> output included in the dump? It can&#8217;t be that hard, can it?</li>
<li>Can you find other attachments included in the latest Epstein dumps that might also be possible to reconstruct? Unfortunately, the contractor that developed <a href="https://www.justice.gov/epstein" rel="follow">the full-text search</a> for the Department of Justice did a pretty crappy job and full-text search is practically broken even accounting for the bad OCR and wrangled quoted-printable decoding (malicious compliance??); nevertheless, searching for <code>Content-Transfer-Encoding</code> and <code>base64</code> returns a number of results – it&#8217;s just that, unfortunately, most are uselessly truncated or only the SMTP headers from Apple Mail curiously extracted.</li>
</ul>
<p>I have uploaded <a href="https://www.justice.gov/epstein/files/DataSet%209/EFTA00400459.pdf" rel="follow">the original EFTA00400459.pdf</a> from Epstein Dataset 9 as downloaded from the DoJ website <a href="https://archive.org/details/efta-00400459" rel="follow">to the Internet Archive</a>, as well as <a href="https://archive.org/download/efta-00400459-lossless-webp" rel="follow">the individual pages losslessly encoded to WebP images</a> to save you the time and trouble of converting them yourself. If it&#8217;s of any use to anyone, I&#8217;ve also uploaded the very-much-invalid Amazon Textract OCR text (from the losslessly 2x&#8217;d images), <a href="https://neosmart.net/blog/wp-content/uploads/2026/02/EFTA00400459_2x-textract.zip" rel="follow">which you can download here</a>.</p>
<p>Oh, and one final hint: when trying to figure out <code>1</code> vs <code>l</code>, I was able to do this with 100% accuracy only via trial-and-error, decoding one line of base64 text at-a-time, but this only works for the plain-text portions of the PDF (headers, etc). For example, I started with my best guess for one line that I had to type out myself when trying with tesseract, and then was able to (in this case) deduce which particular <code>1</code>s or <code>l</code>s were flipped:</p>
<p><a href="https://neosmart.net/blog/wp-content/uploads/2026/02/Distinguishing-flipped-base64.webp" rel="follow"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-5387 colorbox-5373" src="https://neosmart.net/blog/wp-content/uploads/2026/02/Distinguishing-flipped-base64.webp" alt="" width="728" height="198" /></a></p>
<pre>&gt; pbpaste
SW5mbzw8L01sbHVzdHJhdG9yIDgxIDAgUj4+L1Jlc29lcmNlczw8L0NvbG9yU3BhY2U8PC9DUzAG
&gt; pbpaste | base64 -d
Info<span style="color: #ff0000;">&lt;</span>&lt;/<span style="color: #ff0000;">M</span>llustrator 81 0 R&gt;<span style="color: #ff0000;">&gt;</span>/Reso<span style="color: #ff0000;">e</span>rces<span style="color: #ff0000;">&lt;</span>&lt;/ColorSpace<span style="color: #ff0000;">&lt;</span>&lt;/CS0
&gt; 
&gt; # which I was able to correct:
&gt;
&gt; pbpaste
SW5mbzw8L0lsbHVzdHJhdG9yIDgxIDAgUj4+L1Jlc291cmNlczw8L0NvbG9yU3BhY2U8PC9DUzAG
&gt; pbpaste | base64 -d
Info<span style="color: #ff0000;">&lt;</span>&lt;/Illustrator 81 0 R&gt;<span style="color: #ff0000;">&gt;</span>/Resources<span style="color: #ff0000;">&lt;</span>&lt;/ColorSpace<span style="color: #ff0000;">&lt;</span>&lt;/CS0
</pre>
<p>&#8230;but good luck getting that to work once you get to the flate-compressed sections of the PDF.</p>
<p>I&#8217;ll be posting updates on Twitter <a href="https://x.com/mqudsi" rel="follow">@mqudsi</a>, and you can reach out to me on Signal at <code>mqudsi.42</code> if you have anything sensitive you would like to share. You can join in the discussion <a href="https://news.ycombinator.com/item?id=46890335" rel="follow">on Hacker News</a> or on <a href="https://old.reddit.com/r/netsec/comments/1qw4sfa/recreating_uncensored_epstein_pdfs_from_raw/?" rel="follow">r/netsec</a>. Leave a comment below if you have any ideas/questions, or if you think I missed something!</p>
<!-- Support Callout Start -->
<div style="
    margin: 35px 0;
    padding: 25px;
    background-color: #f8fbfe;
    border-left: 5px solid #1b8be0;
    border-top: 1px solid #e1eaf1;
    border-right: 1px solid #e1eaf1;
    border-bottom: 1px solid #e1eaf1;
    border-radius: 0 4px 4px 0;
    font-family: Georgia, 'Times New Roman', serif;
    line-height: 1.6;
    box-shadow: 0 2px 5px rgba(0,0,0,0.03);
">
    <p style="margin: 0; color: #333; font-size: 1.15em;">
        <span style="color: #1b8be0; font-weight: bold; font-family: Arial, sans-serif; text-transform: uppercase; font-size: 0.85em; letter-spacing: 1px; display: block; margin-bottom: 8px;">
            A note from the author
        </span>
        I do this for the love of the game and in the hopes of hopefully exposing some bad people. But I do have a wife and small children and this takes me away from my work that pays the bills. If you would like to support me on this mission, you can make a contribution
        <a href="https://mqudsi.com/donate/" style="color: #1b8be0; text-decoration: underline; font-weight: bold; transition: color 0.2s;">here</a>.
    </p>
</div>
<!-- Support Callout End -->
<p><strong>UPDATE</strong></p>
<p>This was solved last night (February 6, 2026). You can read about it <a href="https://neosmart.net/blog/efta00400459-has-been-cracked-dbc12-pdf-liberated/" rel="follow">in the follow-up to this article</a>.</p>
<hr class="footnotes"><ol class="footnotes"><li id="fn1-5373"><p>In case you&#8217;re wondering, the shell session excerpts in this article <a href="https://github.com/fish-shell/fish-shell/" rel="nofollow">are all in <code>fish</code></a>, which I think is a good fit for string wrangling because of its <a href="https://fishshell.com/docs/current/cmds/string.html" rel="follow"><code>string</code> builtin</a> with extensive operations with a very human-readable syntax (at least compared to <code>perl</code> or <code>awk</code>, the usual go-tos for string manipulation), and it lets you compose multiple operations as separate commands while not devolving to performing pathologically because no external commands are <code>fork</code>/<code>exec</code>&#8216;d because of its builtin nature. And I&#8217;m not just saying that because of the blood, sweat, and tears I&#8217;ve contributed to the project.&nbsp;<a href="#rf1-5373" class="backlink" title="Jump back to footnote 1 in the text.">&#8617;</a></p></li><li id="fn2-5373"><p>I didn&#8217;t want to convert the PNGs back to a single PDF as I didn&#8217;t want any further loss in quality.&nbsp;<a href="#rf2-5373" class="backlink" title="Jump back to footnote 2 in the text.">&#8617;</a></p></li></ol>The post <a href="https://neosmart.net/blog/recreating-epstein-pdfs-from-raw-encoded-attachments/">Recreating uncensored Epstein PDFs from raw encoded attachments</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/recreating-epstein-pdfs-from-raw-encoded-attachments/feed/</wfw:commentRss>
			<slash:comments>38</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5373</post-id>	</item>
		<item>
		<title>Sharding UUIDv7 (and UUID v3, v4, and v5) values with one function</title>
		<link>https://neosmart.net/blog/sharding-uuidv7-and-uuid-v3-v4-and-v5-with-one-function/</link>
					<comments>https://neosmart.net/blog/sharding-uuidv7-and-uuid-v3-v4-and-v5-with-one-function/#respond</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Wed, 28 Jan 2026 19:26:43 +0000</pubDate>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[rust]]></category>
		<category><![CDATA[sharding]]></category>
		<category><![CDATA[uuid]]></category>
		<category><![CDATA[uuidv7]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=5351</guid>

					<description><![CDATA[<p>UUIDv7 (wiki link) is seeing strong and eager adoption as a solution to problems that have long plagued the tech industry, providing a solution for generating collision-free IDs on the backend that could still be sorted chronologically to play nicer &#8230; <a href="https://neosmart.net/blog/sharding-uuidv7-and-uuid-v3-v4-and-v5-with-one-function/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/sharding-uuidv7-and-uuid-v3-v4-and-v5-with-one-function/">Sharding UUIDv7 (and UUID v3, v4, and v5) values with one function</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<p>UUIDv7 (<a href="https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_7_(timestamp_and_random)" rel="follow">wiki link</a>) is seeing strong and eager adoption as a solution to problems that have long plagued the tech industry, providing a solution for generating collision-free IDs on the backend that could still be sorted chronologically to play nicer with database indexes and other needs.<sup id="rf1-5351"><a href="https://neosmart.net/blog/sharding-uuidv7-and-uuid-v3-v4-and-v5-with-one-function/#fn1-5351" title="Of course, non-standardized solutions abound and UUIDv7 itself takes a lot of inspiration from predecessors like Ulid and others." rel="footnote">1</a></sup> As a quick briefer, a UUIDv7 is essentially composed of two parts: a timestamp half and a randomized bytes half, and they&#8217;re sorted by the timestamp:</p>
<p><a href="https://neosmart.net/blog/wp-content/uploads/2026/01/UUIDv7-Diagram-outlined.svg" rel="follow"><img decoding="async" class="aligncenter size-full wp-image-5352 colorbox-5351" src="https://neosmart.net/blog/wp-content/uploads/2026/01/UUIDv7-Diagram-outlined.svg" alt="" /></a><span id="more-5351"></span></p>
<p>Let&#8217;s say you want to shard based off a UUIDv7 key: maybe to assign data across a horizontally-distributed database, or to avoid having a couple of million files in the same directory. You can&#8217;t use the timestamp portion to derive a shard key, as not even the microseconds are necessarily uniformly distributed, so you need to shard based off the random portion of the UUID.</p>
<p>Lucky for us, the last eight bytes of the UUID contain random data not just in the case of a UUIDv7 – those bytes <em>also</em> contain random data in the case of UUID v3 (MD5-hashed), UUID v4 (randomized), and UUID v5 (like v3 but using SHA-1), letting us use one approach to hash all four of these UUID types.</p>
<p>With some judicious use of the stabilized subset of const generics, we can generate a shard key <code>N</code>-chars long without any allocation, and provide a wrapper <code>ShardKey</code> type to simplify both creating the shard key and passing it around in a way that lets it decompose to a <code>&amp;str</code> in a way that wouldn&#8217;t have been possible if we had returned a <code>[char; N]</code> instead.<sup id="rf2-5351"><a href="https://neosmart.net/blog/sharding-uuidv7-and-uuid-v3-v4-and-v5-with-one-function/#fn2-5351" title="As rust is famously one of the very few languages that uses different-sized units/types to represent a standalone char versus the smallest type used to compose a String; in rust, char is 4-bytes long and has UTF-32 encoding while a String or &amp;str uses an underlying 1-byte u8 type and are UTF8-encoded. This means you can&rsquo;t go from a String to a [char] without allocating, nor go from a [N; char] to an &amp;str without looping and allocating, either." rel="footnote">2</a></sup></p>
<pre><code class="language-rust">/// Derives a deterministic N-char sharding key from a UUID.
///
/// It extracts entropy starting from the last byte (the most random 
/// part of a UUIDv7) moving backwards, converting nibbles to 
/// hexadecimal characters. It uses only the last 8 bytes (the `rand_b`
/// section) to ensure the timestamp (first 6 bytes) is never touched, 
/// preventing "hot spot" sharding issues based on time.
///
/// # Panics
/// * Panics if the provided UUID is not shardable based off the last 
///   8 bytes (i.e. if the UUID is not one a v3, v4, v5, or v7 UUID).
/// * Panics if the requested shard key length `N` exceeds the 
///   available entropy.
pub fn shard&lt;const N: usize&gt;(uuid: &amp;uuid::Uuid) -&gt; ShardKey&lt;N&gt; {
    // Ensure UUID random bytes assumption
    if !matches!(uuid.get_version_num(), 3 | 4 | 5 | 7) {
        panic!("Provided UUID cannot be sharded with this interface!");
    }
    // Validate shard key length
    if N &gt; 15 {
        panic!("Requested shard key length exceeds available entropy!");
    }

    const HEX: &amp;[u8; 16] = b"0123456789abcdef";
    let bytes = uuid.as_bytes();
    let mut result = [b'\0'; N];

    // Generate N-char shard key
    for i in 0..N {
        // We extract data from the tail (byte 15) backwards to byte 8.
        // Bytes 8-15 in UUIDv7 contain 62 bits of randomness + 2 bits 
        // to indicate variant. We avoid bytes 0-7 (Timestamp + Version) 
        // to ensure uniform distribution.

        // Calculate byte index, consuming two nibbles per byte.
        let inverse_offset = i / 2;
        let byte_index = 15 - inverse_offset;

        let byte = bytes[byte_index];

        // Even indices take the low nibble; odd indices take the high nibble.
        let nibble = if i % 2 == 0 {
            byte &amp; 0x0F
        } else {
            (byte &gt;&gt; 4) &amp; 0x0F
        };

        result[i] = HEX[nibble as usize];
    }

    ShardKey { key: result }
}

#[repr(transparent)]
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct ShardKey&lt;const N: usize&gt; {
    key: [u8; N],
}

impl&lt;const N: usize&gt; ShardKey&lt;N&gt; {
    pub fn new(uuid: &amp;uuid::Uuid) -&gt; Self {
        shard(uuid)
    }

    pub fn as_str(&amp;self) -&gt; &amp;str {
        // Safe because only we have access to this field and we always
        // initialize it fully with ASCII-only characters.
        unsafe { std::str::from_utf8_unchecked(&amp;self.key) }
    }
}
</code></pre>
<p>The <code>shard()</code> function can be used standalone, or the <code>ShardKey</code> wrapper type can be created from a reference to a <code>&Uuid</code> and then passed around until the point where you need to retrieve the key with a quick <code>.as_str()</code> call. As promised, the length of the shard key itself is variable and parameterized, by providing different values for <code>N</code> you can generate shard keys from one to fifteen (or sixteen, if you prefer) characters long (past which point the available uniformly-distributed entropy has been exhausted and the function will panic to ensure contract is upheld).</p>
<hr class="footnotes"><ol class="footnotes"><li id="fn1-5351"><p>Of course, non-standardized solutions abound and UUIDv7 itself takes a lot of inspiration from predecessors like <a href="https://github.com/ulid/spec" rel="nofollow">Ulid</a> and others.&nbsp;<a href="#rf1-5351" class="backlink" title="Jump back to footnote 1 in the text.">&#8617;</a></p></li><li id="fn2-5351"><p>As rust is famously one of the very few languages that uses different-sized units/types to represent a standalone <code>char</code> versus the smallest type used to compose a <code>String</code>; in rust, <code>char</code> is 4-bytes long and has UTF-32 encoding while a <code>String</code> or <code>&str</code> uses an underlying 1-byte <code>u8</code> type and are UTF8-encoded. This means you can&#8217;t go from a <code>String</code> to a <code>[char]</code> without allocating, nor go from a <code>[N; char]</code> to an <code>&str</code> without looping and allocating, either.&nbsp;<a href="#rf2-5351" class="backlink" title="Jump back to footnote 2 in the text.">&#8617;</a></p></li></ol>The post <a href="https://neosmart.net/blog/sharding-uuidv7-and-uuid-v3-v4-and-v5-with-one-function/">Sharding UUIDv7 (and UUID v3, v4, and v5) values with one function</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/sharding-uuidv7-and-uuid-v3-v4-and-v5-with-one-function/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5351</post-id>	</item>
		<item>
		<title>How to (safely) swap the contents of two files opened in (neo)vim buffers</title>
		<link>https://neosmart.net/blog/how-to-swap-the-contents-of-two-files-opened-in-neovim-buffers/</link>
					<comments>https://neosmart.net/blog/how-to-swap-the-contents-of-two-files-opened-in-neovim-buffers/#respond</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Sat, 24 Jan 2026 17:54:18 +0000</pubDate>
				<category><![CDATA[Random]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[neovim]]></category>
		<category><![CDATA[tips and tricks]]></category>
		<category><![CDATA[vim]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=5319</guid>

					<description><![CDATA[<p>Raise your hand if you&#8217;ve been here before: you have file1 open in a vim or neovim buffer and you want to &#8220;fork&#8221; its contents over to file2, but you need to reference file1 while you do so. So you &#8230; <a href="https://neosmart.net/blog/how-to-swap-the-contents-of-two-files-opened-in-neovim-buffers/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/how-to-swap-the-contents-of-two-files-opened-in-neovim-buffers/">How to (safely) swap the contents of two files opened in (neo)vim buffers</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<p><a href="https://neosmart.net/blog/wp-content/uploads/2026/01/neovim.svg" rel="follow"><img loading="lazy" decoding="async" class="wp-image-5322 alignright colorbox-5319" src="https://neosmart.net/blog/wp-content/uploads/2026/01/neovim.svg" alt="" width="114" height="137" /></a>Raise your hand if you&#8217;ve been here before: you have <code>file1</code> open in a vim or neovim buffer and you want to &#8220;fork&#8221; its contents over to <code>file2</code>, but you need to reference <code>file1</code> while you do so. So you do the obvious: you open a split buffer with <code>:sp</code> or <code>:vsp</code>, run a quick <code>:saveas file2</code> then hack away at the file to make the changes you want followed by <code>:w</code> (or whatever shortcut you have mapped to the same) and call it a day… only to realize that you were in the wrong split and that you&#8217;ve accidentally switched <code>file1</code> and <code>file2</code> around?</p>
<p><span id="more-5319"></span></p>
<p>You <i>could</i> just do a quick <code>:saveas! file1</code> and <code>:saveas! file2</code> in the respective buffers to set things right, <em>but of course</em> you have <code>autoread</code> enabled, so correcting the file in one buffer will cause the other copy in the second buffer to be immediately lost!<sup id="rf1-5319"><a href="https://neosmart.net/blog/how-to-swap-the-contents-of-two-files-opened-in-neovim-buffers/#fn1-5319" title="Yes, of course you can undo the autoread and then :saveas again, but do you really want to be taking that risk with your hours of work?" rel="footnote">1</a></sup></p>
<p><a href="https://neosmart.net/blog/wp-content/uploads/2026/01/vim-help-0f.webp" rel="follow"><img loading="lazy" decoding="async" class="aligncenter wp-image-5327 size-full colorbox-5319" src="https://neosmart.net/blog/wp-content/uploads/2026/01/vim-help-0f.webp" alt=" :0file :0f[ile][!] Remove the name of the current buffer. The optional ! avoids truncating the message, as with :file. :buffers :files :ls List all the currently known file names. See windows.txt :files :buffers :ls." width="846" height="234" /></a></p>
<p>The solution is a quick <code>:0f</code> away: it&#8217;s a shortcut for <code>:filename ""</code> that clears the association between the buffer and the underlying file, leaving the contents in the buffer unchanged but severing the link with the underlying inode, meaning after you run <code>:0f</code> in one buffer, you can swap back to the other and save it to the correct path without risking the other buffer&#8217;s contents from being lost, as it&#8217;s been dissociated from the underlying file. Then you can go back to the now-unnamed buffer and save it to correct path too, and all will be well.</p>
<hr class="footnotes"><ol class="footnotes"><li id="fn1-5319"><p>Yes, of course you can undo the <code>autoread</code> and then <code>:saveas</code> again, but do you really want to be taking that risk with your hours of work?&nbsp;<a href="#rf1-5319" class="backlink" title="Jump back to footnote 1 in the text.">&#8617;</a></p></li></ol>The post <a href="https://neosmart.net/blog/how-to-swap-the-contents-of-two-files-opened-in-neovim-buffers/">How to (safely) swap the contents of two files opened in (neo)vim buffers</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/how-to-swap-the-contents-of-two-files-opened-in-neovim-buffers/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5319</post-id>	</item>
		<item>
		<title>Portable (Cartesian) brace expansion in your shell</title>
		<link>https://neosmart.net/blog/portable-cartesian-brace-expansion-in-your-shell/</link>
					<comments>https://neosmart.net/blog/portable-cartesian-brace-expansion-in-your-shell/#respond</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Fri, 23 Jan 2026 16:37:34 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[expansion]]></category>
		<category><![CDATA[fish]]></category>
		<category><![CDATA[shell]]></category>
		<category><![CDATA[tips and tricks]]></category>
		<category><![CDATA[unix]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=5303</guid>

					<description><![CDATA[<p>Cartesian expansion, also known as brace expansion, is an incredibly powerful feature of most unixy shells, but despite being fundamentally simple and incredibly empowering, it&#8217;s been traditionally relegated to the dark and shadowy corners of command line hacking, employed only &#8230; <a href="https://neosmart.net/blog/portable-cartesian-brace-expansion-in-your-shell/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/portable-cartesian-brace-expansion-in-your-shell/">Portable (Cartesian) brace expansion in your shell</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<p>Cartesian expansion, also known as brace expansion, is an incredibly powerful feature of most unixy shells, but despite being fundamentally simple and incredibly empowering, it&#8217;s been traditionally relegated to the dark and shadowy corners of command line hacking, employed only by greybeards looking to avoid repeating themselves at any cost. And, boy, does it really cut down on repetition.</p>
<p>Take for example this snippet from a <code>Dockerfile</code> that sets up permissions on certain directories:</p>
<pre><code class="language-bash">mkdir -p /var/log/php /var/log/unitd /var/log/mysql
chown -R user:user /var/log/php /var/log/unitd /var/log/mysql
chmod ug+rw /var/log/php /var/log/unitd /var/log/mysql
</code></pre>
<p><span id="more-5303"></span></p>
<p>You can immediately see how the <code>/var/log/</code> prefix is a source of repetition: wasting valuable horizontal screen real estate, extra room for typos or mistakes, and diluting the signal:noise ratio of the lines in question.</p>
<p>In most shells,<sup id="rf1-5303"><a href="https://neosmart.net/blog/portable-cartesian-brace-expansion-in-your-shell/#fn1-5303" title="Including, but not limited to, fish (the one true shell), bash, zsh, ksh (as of ksh88), zsh, and csh/tcsh (where this feature originated). But notably not supported by dash and not part of the posix sh spec." rel="footnote">1</a></sup> you can use braces (<code>{}</code>)<sup id="rf2-5303"><a href="https://neosmart.net/blog/portable-cartesian-brace-expansion-in-your-shell/#fn2-5303" title="These are called braces, or sometimes curly braces. They are not brackets ([]) and there is no such thing as a &ldquo;curly bracket&rdquo;." rel="footnote">2</a></sup> to accomplish the same with much less repetition:</p>
<pre><code class="language-bash">mkdir -p /var/log/{php,unitd,mysql}
chown -R user:user /var/log/{php,unitd,mysql}
chmod ug+rw /var/log/{php,unitd,mysql}
</code></pre>
<p>..which expands to the same, effectively operating as a declarative &#8220;for loop&#8221; running for each of the items within the braces; e.g. <code>echo {hello,goodbye}_world</code> prints <code>hello_world goodbye_world</code> (<em>m</em>x<em>n</em> complexity with each braced/non-braced component being one set) while <code>echo {hello,goodbye}_world{.com,.exe}</code> prints <code>hello_world.com goodbye_world.com hello_world.exe goodbye_world.exe</code>, or <em>m</em>x<em>n</em>x<em>o</em> complexity with <em>m</em> equal to two, <em>n</em> equal to one, and <em>o</em> equal to two for four results.</p>
<p>Depending on the shell, this functionality can be taken a step further and used to do questionable things, like combining with glob expansion to get a list of all files with a certain extension that appear in one of <em>n</em> different directories with an expression like <code>foo/{bar,baz}/*.{c,h}</code> or anything else your heart desires.</p>
<p>Alas, the one issue with Cartesian/brace expansion is that it isn&#8217;t formalized in the <a href="https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/V3_chap02.html" rel="follow">posix <code>sh</code> spec</a>, meaning you can&#8217;t use it in truly portable shell scripts or with a <code>#!/bin/sh</code> shebang. But fret not, because you can still accomplish the same thing with another feature: subshell output interpolation.</p>
<p>To jump right to it, here is portable syntax that will accomplish the same as our original demonstration snippet, except this is truly portable and you can use it in your Alpine Linux <code>Dockerfile</code> running <code>sh</code>:</p>
<pre><code class="language-bash">mkdir -p $(printf "/var/log/%s\n" php unitd mysql)
chown -R user:user /var/log/$(echo php unitd mysql)
chmod ug+rw /var/log/$(printf "%s\n" php unitd mysql)
</code></pre>
<p>As you can see, there are several ways it can be used, relying on <code>printf</code> to perform the expansion for you directly (taking advantage of the fact that <code>printf</code> is spec&#8217;d to repeat the input when it is provided with more parameters than placeholders), use <code>echo</code> instead of <code>printf</code> if your <code>$IFS</code> configuration splits on spaces, or use <code>printf %s\n</code> if you want to support spaces in paths and have <code>$IFS</code> set to split only on new lines (or you&#8217;re smart and sane and use <a href="https://github.com/fish-shell/fish-shell/" rel="nofollow"><code>fish</code></a> which defaults to splitting only on new lines anyway) – in which case the second example with <code>echo</code> instead of <code>printf %s\n</code> won&#8217;t work for you.</p>
<p>So keep it DRY<sup id="rf3-5303"><a href="https://neosmart.net/blog/portable-cartesian-brace-expansion-in-your-shell/#fn3-5303" title="Don&rsquo;t Repeat Yourself" rel="footnote">3</a></sup> and embrace the power of brace expansion (or Cartesian expansion in general, if you don&#8217;t have access to brace expansion in your shell of choice) the next time you&#8217;re banging out a shell script!</p>
<hr class="footnotes"><ol class="footnotes"><li id="fn1-5303"><p>Including, but not limited to, <a href="https://github.com/fish-shell/fish-shell/" rel="nofollow">fish</a> (the one true shell), bash, zsh, ksh (as of ksh88), zsh, and csh/tcsh (where this feature originated). But notably not supported by dash and not part of the posix <code>sh</code> spec.&nbsp;<a href="#rf1-5303" class="backlink" title="Jump back to footnote 1 in the text.">&#8617;</a></p></li><li id="fn2-5303"><p>These are called braces, or sometimes curly braces. They are not brackets (<code>[]</code>) and there is no such thing as a &#8220;curly bracket&#8221;.&nbsp;<a href="#rf2-5303" class="backlink" title="Jump back to footnote 2 in the text.">&#8617;</a></p></li><li id="fn3-5303"><p>Don&#8217;t Repeat Yourself&nbsp;<a href="#rf3-5303" class="backlink" title="Jump back to footnote 3 in the text.">&#8617;</a></p></li></ol>The post <a href="https://neosmart.net/blog/portable-cartesian-brace-expansion-in-your-shell/">Portable (Cartesian) brace expansion in your shell</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/portable-cartesian-brace-expansion-in-your-shell/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5303</post-id>	</item>
		<item>
		<title>Namecheap takes down domain hosting video archives of Israeli war crimes</title>
		<link>https://neosmart.net/blog/namecheap-com-revokes-domain-hosting-video-archives-of-israeli-war-crimes/</link>
					<comments>https://neosmart.net/blog/namecheap-com-revokes-domain-hosting-video-archives-of-israeli-war-crimes/#comments</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Thu, 01 Jan 2026 19:05:40 +0000</pubDate>
				<category><![CDATA[Random]]></category>
		<category><![CDATA[genocide.live]]></category>
		<category><![CDATA[namecheap]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=5232</guid>

					<description><![CDATA[<p>Namecheap.com, the popular domain name and webhosting platform, has disabled the Genocide.live domain name, which was home to a publicly accessible archive of over 16,000 videos documenting alleged Israeli war crimes, the vast majority of which were recorded since the &#8230; <a href="https://neosmart.net/blog/namecheap-com-revokes-domain-hosting-video-archives-of-israeli-war-crimes/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/namecheap-com-revokes-domain-hosting-video-archives-of-israeli-war-crimes/">Namecheap takes down domain hosting video archives of Israeli war crimes</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<p>Namecheap.com, the popular domain name and webhosting platform, has disabled the Genocide.live domain name, which was home to a publicly accessible archive of over 16,000 videos documenting alleged Israeli war crimes, the vast majority of which were recorded since the onset of the war on Gaza in late 2023. The archive, formerly known as TikTokGenocide, was previously submitted as &#8220;evidence on the State of Israel’s acts of genocide against the Palestinians in Gaza&#8221; by the South African UN delegation to the United Nations Security Council <a href="https://docs.un.org/en/S/2025/130" rel="follow">in February of 2025</a> and is also included in ongoing court proceedings of <a href="https://en.wikipedia.org/wiki/South_Africa%27s_genocide_case_against_Israel" rel="follow">the International Court of Justice case</a> <em>South Africa (et. al.) v. Israel</em>.</p>
<p><span id="more-5232"></span></p>
<p><a href="https://neosmart.net/blog/wp-content/uploads/2026/01/tiktok-genocide-social-preview.png" rel="follow"><img loading="lazy" decoding="async" class="aligncenter size-large wp-image-5256 colorbox-5232" src="https://neosmart.net/blog/wp-content/uploads/2026/01/tiktok-genocide-social-preview-1024x538.png" alt="" width="620" height="326" srcset="https://neosmart.net/blog/wp-content/uploads/2026/01/tiktok-genocide-social-preview-1024x538.png 1024w, https://neosmart.net/blog/wp-content/uploads/2026/01/tiktok-genocide-social-preview-600x315.png 600w, https://neosmart.net/blog/wp-content/uploads/2026/01/tiktok-genocide-social-preview-500x263.png 500w, https://neosmart.net/blog/wp-content/uploads/2026/01/tiktok-genocide-social-preview.png 1200w" sizes="auto, (max-width: 620px) 100vw, 620px" /></a></p>
<p>In a New Year&#8217;s tweet earlier today, the maintainer of the site going by the alias of <a href="https://x.com/receipts_lol" rel="follow">Zionism Observer</a> on Twitter detailed the suspension (with the registrar placing the domain under a <code>clientHold</code> suspension) of the Genocide.live domain name, under the seemingly ridiculous claim of its hosting material that &#8220;promotes, encourages, engages or displays cruelty to humans or animals:&#8221;</p>
<p><a href="https://x.com/receipts_lol/status/2006732606164152651" rel="follow"><img loading="lazy" decoding="async" class="aligncenter wp-image-5235 colorbox-5232" src="https://neosmart.net/blog/wp-content/uploads/2026/01/ZO-namecheap-suspension.png" alt="" width="416" height="433" srcset="https://neosmart.net/blog/wp-content/uploads/2026/01/ZO-namecheap-suspension.png 596w, https://neosmart.net/blog/wp-content/uploads/2026/01/ZO-namecheap-suspension-576x600.png 576w, https://neosmart.net/blog/wp-content/uploads/2026/01/ZO-namecheap-suspension-288x300.png 288w" sizes="auto, (max-width: 416px) 100vw, 416px" /></a></p>
<p>It’s true that the site&#8217;s content did indeed depict gross violations of human rights (<a href="https://neosmart.net/blog/wp-content/uploads/2026/01/genocide.live-animal-abuse.png" rel="follow">along with 70+ videos of animal rights violations</a>), but it could not be honestly (or even mistakenly) described as being posted for purposes of &#8220;promoting, encouraging,&#8221; or glorifying such violations – an accusation much akin to doggedly accusing Wikipedia of being a pornography site because it contains medical and anatomical depictions of the human body. The maintainer has also clarified that all sensitive videos and imagery were placed behind a click-to-view overlay with a warning about the violent or triggering nature of the content.</p>
<p>While the site has been reportedly mirrored and backed up to multiple locations, there remain obvious problems with this seizure. As various links to individual videos from the archive – all now broken – have been entered into evidence with various international and supranational courts in multiple ongoing proceedings opened against the State of Israel (and, separately, individual Israeli soldiers) over the past couple of years, the takedown of the domain could have severe legal ramifications, if not rectified posthaste, – and could potentially open Namecheap to accusations of obstruction of justice, which their General Counsel might wish to consider.<sup id="rf1-5232"><a href="https://neosmart.net/blog/namecheap-com-revokes-domain-hosting-video-archives-of-israeli-war-crimes/#fn1-5232" title="Interestingly, Namecheap does not publicly name their Chief Legal Officer/General Counsel, leading even the Office of the Attorney General of the State of New York to leave them unnamed in previous legal petitions." rel="footnote">1</a></sup></p>
<div id="attachment_5237" style="width: 1312px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-5237" class="size-full wp-image-5237 colorbox-5232" src="https://neosmart.net/blog/wp-content/uploads/2026/01/genocide.live-footnote.jpeg" alt="" width="1302" height="884" srcset="https://neosmart.net/blog/wp-content/uploads/2026/01/genocide.live-footnote.jpeg 1302w, https://neosmart.net/blog/wp-content/uploads/2026/01/genocide.live-footnote-600x407.jpeg 600w, https://neosmart.net/blog/wp-content/uploads/2026/01/genocide.live-footnote-1024x695.jpeg 1024w, https://neosmart.net/blog/wp-content/uploads/2026/01/genocide.live-footnote-442x300.jpeg 442w" sizes="auto, (max-width: 1302px) 100vw, 1302px" /><p id="caption-attachment-5237" class="wp-caption-text">A citation of the Genocide.live archives in legal proceedings accusing Israel of war crimes and genocide in Gaza, <a href="https://x.com/receipts_lol/status/1982872713283895503" rel="follow">provided by the site&#8217;s maintainer</a>.</p></div>
<div id="attachment_5233" style="width: 662px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-5233" class="wp-image-5233 size-full colorbox-5232" src="https://neosmart.net/blog/wp-content/uploads/2026/01/Genocide.live-in-UNSC-submission.png" alt="" width="652" height="102" srcset="https://neosmart.net/blog/wp-content/uploads/2026/01/Genocide.live-in-UNSC-submission.png 652w, https://neosmart.net/blog/wp-content/uploads/2026/01/Genocide.live-in-UNSC-submission-600x94.png 600w, https://neosmart.net/blog/wp-content/uploads/2026/01/Genocide.live-in-UNSC-submission-500x78.png 500w" sizes="auto, (max-width: 652px) 100vw, 652px" /><p id="caption-attachment-5233" class="wp-caption-text">An instance of Genocide.live, née TikTokGenocide.com, in UN Security Council proceedings <a href="https://docs.un.org/en/S/2025/130" rel="follow">from February 2025</a>.</p></div>
<p>The archive had already come into the legal spotlight earlier last year, when it was forced to change from its original name of TikTokGenocide to Genocide.live after cease-and-desist requests from the new owners of the popular social media platform targeted its use of the domain name – despite its arguably fair-use claim to the name.<sup id="rf2-5232"><a href="https://neosmart.net/blog/namecheap-com-revokes-domain-hosting-video-archives-of-israeli-war-crimes/#fn2-5232" title="The previous name was a not unsubtle jab at the public broadcasting of war crimes by IDF soldiers as they boasted about the destruction they wrought in Gaza." rel="footnote">2</a></sup></p>
<p><a href="https://x.com/receipts_lol/status/2006737285203870092?s=20" rel="follow"><img loading="lazy" decoding="async" class="aligncenter wp-image-5234 colorbox-5232" src="https://neosmart.net/blog/wp-content/uploads/2026/01/ZO-domain-name-change.png" alt="" width="432" height="461" srcset="https://neosmart.net/blog/wp-content/uploads/2026/01/ZO-domain-name-change.png 595w, https://neosmart.net/blog/wp-content/uploads/2026/01/ZO-domain-name-change-561x600.png 561w, https://neosmart.net/blog/wp-content/uploads/2026/01/ZO-domain-name-change-281x300.png 281w" sizes="auto, (max-width: 432px) 100vw, 432px" /></a></p>
<p>Namecheap is no stranger to accusations of frivolous or unjustified domain name suspensions – a search on Reddit reveals <a href="https://www.google.com/search?q=namecheap+domain+name+suspended+site:www.reddit.com" rel="follow">hundreds of such complaints in recent years</a> – but neither are any of the other large domain name registrars exempt from reports of such behavior. The site&#8217;s maintainer suggests that the decision to suspend the domain over the New Years holiday <a href="https://x.com/receipts_lol/status/2006746524844437525?s=20" rel="follow">might not have been coincidental</a>, as it robbed them of <a href="https://x.com/receipts_lol/status/2006735224575856986?s=20" rel="follow">a 48-hour notice of impending revocation</a> (with, apparently, no option to appeal or contest the charges), during which time they might have been able to transfer the domain to a different registrar.</p>
<div id="attachment_5238" style="width: 630px" class="wp-caption aligncenter"><a href="https://neosmart.net/blog/wp-content/uploads/2026/01/Genocide.live-trending-on-Twitter.jpeg" rel="follow"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-5238" class="size-large wp-image-5238 colorbox-5232" src="https://neosmart.net/blog/wp-content/uploads/2026/01/Genocide.live-trending-on-Twitter-1024x255.jpeg" alt="" width="620" height="154" srcset="https://neosmart.net/blog/wp-content/uploads/2026/01/Genocide.live-trending-on-Twitter-1024x255.jpeg 1024w, https://neosmart.net/blog/wp-content/uploads/2026/01/Genocide.live-trending-on-Twitter-600x149.jpeg 600w, https://neosmart.net/blog/wp-content/uploads/2026/01/Genocide.live-trending-on-Twitter-500x125.jpeg 500w, https://neosmart.net/blog/wp-content/uploads/2026/01/Genocide.live-trending-on-Twitter.jpeg 1124w" sizes="auto, (max-width: 620px) 100vw, 620px" /></a><p id="caption-attachment-5238" class="wp-caption-text">The seizure of the domain has gone somewhat viral on Twitter, where it has become a trending topic with users speculating on the motivation and suggesting that the domain registrar might have been the target of an email campaign by those unhappy with the archive&#8217;s existence or growing prominence.</p></div>
<p>In addition to hosting over 16,000 videos of evidence documenting evidence of war crimes by Israeli soldiers and examples of intent of genocide from Israeli military and civil leaders, the Genocide.live archive also included <a href="https://neosmart.net/blog/wp-content/uploads/2026/01/genocide.live-interactive-maps.png" rel="follow">an interactive map of Gaza</a> detailing IDF violations against the populace in each area, a <a href="https://neosmart.net/blog/wp-content/uploads/2026/01/genocide.live-geolocated-events.png" rel="follow">geolocated index of the videos</a> for which location data was positively determined, a <a href="https://neosmart.net/blog/wp-content/uploads/2026/01/genocide.live-categories.png" rel="follow">categorized listing of videos</a> detailing the nature of violations, an extensive index of <a href="https://neosmart.net/blog/wp-content/uploads/2026/01/genocide.live-targets.png" rel="follow">the different types of victims of Israeli agression</a>, a cross-indexed <a href="https://neosmart.net/blog/wp-content/uploads/2026/01/genocide.live-weapons.png" rel="follow">reference of various weapons of war used</a>, and, perhaps most sensitively of all, a cross-indexed <a href="https://neosmart.net/blog/wp-content/uploads/2026/01/genocide.live-idf.png" rel="follow">list of individual IDF brigades and battalions</a> tied to each of the hosted pieces of evidence, where that information was available.</p>
<p>The site&#8217;s maintainer noted with concern an anomaly in the traffic <a href="https://x.com/receipts_lol/status/2006806978732208286?s=20" rel="follow">the archive was receiving</a> from Israel, and had earlier reported <a href="https://x.com/receipts_lol/status/2001389379034472619" rel="follow">recent attempts to probe their infrastructure</a> from the same.</p>
<p>Genocide.live is a part of the <a href="https://databasesforpalestine.org/" rel="follow">Databases for Palestine</a> project, a collective founded in December of 2023 using tech to shed light on the terrible situation in Gaza and the acts of the Israeli government and army that contributed to Israel being credibly accused of committing genocide in Palestine by prominent human rights organizations including Amnesty International, Médecins Sans Frontières, and Human Rights Watch, among others.</p>
<p>Requests for comment from Namecheap have not yet been met with a response.</p>
<p class="info"><strong>Update January 5, 2026</strong><br />
Following approximately 96 hours of downtime, the <a href="http://Genocide.live" rel="follow">Genocide.live</a> video archive is now accessible once more. Namecheap agreed to un-suspend the domain in response to significant social media backlash, but refused to continue acting as the archive&#8217;s registrar. After some extended back-and-forth, the registrar-enforced <code>clientTransferProhibited</code> was ultimately removed, allowing the site&#8217;s maintainers to move the domain to <a href="trustname.com">Trustname</a>, their new registrar.</p>
<hr class="footnotes"><ol class="footnotes"><li id="fn1-5232"><p>Interestingly, Namecheap does not publicly name their Chief Legal Officer/General Counsel, leading even the Office of the Attorney General of the State of New York to leave them unnamed <a href="https://ag.ny.gov/sites/default/files/3.20.20_letter_concerning_namecheap_and_coronavirus_1.pdf" rel="follow">in previous legal petitions</a>.&nbsp;<a href="#rf1-5232" class="backlink" title="Jump back to footnote 1 in the text.">&#8617;</a></p></li><li id="fn2-5232"><p>The previous name was a not unsubtle jab at the public broadcasting of war crimes by IDF soldiers as they boasted about the destruction they wrought in Gaza.&nbsp;<a href="#rf2-5232" class="backlink" title="Jump back to footnote 2 in the text.">&#8617;</a></p></li></ol>The post <a href="https://neosmart.net/blog/namecheap-com-revokes-domain-hosting-video-archives-of-israeli-war-crimes/">Namecheap takes down domain hosting video archives of Israeli war crimes</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/namecheap-com-revokes-domain-hosting-video-archives-of-israeli-war-crimes/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5232</post-id>	</item>
		<item>
		<title>FreeBSD 15.0 post-upgrade reboot loop</title>
		<link>https://neosmart.net/blog/freebsd-15-0-post-upgrade-reboot-loop/</link>
					<comments>https://neosmart.net/blog/freebsd-15-0-post-upgrade-reboot-loop/#respond</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Tue, 09 Dec 2025 19:34:30 +0000</pubDate>
				<category><![CDATA[Software]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=5223</guid>

					<description><![CDATA[<p>This post is less of a deep dive into a bug I ran into upgrading an x86_64 machine from FreeBSD 14.3 to FreeBSD 15 and more of a PSA: I have a possible workaround for anyone that runs into the &#8230; <a href="https://neosmart.net/blog/freebsd-15-0-post-upgrade-reboot-loop/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/freebsd-15-0-post-upgrade-reboot-loop/">FreeBSD 15.0 post-upgrade reboot loop</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<p><img loading="lazy" decoding="async" class="alignright wp-image-5230 colorbox-5223" src="https://neosmart.net/blog/wp-content/uploads/2025/12/freebsd-icon.png" alt="" width="122" height="122" srcset="https://neosmart.net/blog/wp-content/uploads/2025/12/freebsd-icon.png 256w, https://neosmart.net/blog/wp-content/uploads/2025/12/freebsd-icon-150x150.png 150w" sizes="auto, (max-width: 122px) 100vw, 122px" /></p>
<p><em>This post is less of a deep dive into a bug I ran into upgrading an x86_64 machine from FreeBSD 14.3 to FreeBSD 15 and more of a PSA: I have a possible workaround for anyone that runs into the same, but I don&#8217;t have a full root analysis or proper diagnosis of what the underlying issue was.</em></p>
<p><a href="https://www.freebsd.org/releases/15.0R/announce/" rel="follow">FreeBSD 15.0 was released a week ago</a>, and I decided to try to upgrade one of my ZFS appliance servers (running nothing more than ZFS and some scripts) to it as a possible low-stakes trial run. The machine in question is a fairly old (but very reliable) Dell PowerEdge R720, running FreeBSD 14.3p2 and booting in UEFI mode from a ZFS <code>zroot</code> pool at the time.</p>
<p>As always, I started my FreeBSD upgrade with the usual <code>sudo zfs snap -r zroot@freebsd-14.3p2</code> prior to anything else (yes, I know about ZFS boot environments, but I also know that ZFS snapshots are fast and free). The first part of the upgrade went swimmingly after installing the newest version of <code>freebsd-rustdate</code> from the <code>pkg</code> repos and executing <code>freebsd-rustdate upgrade -r 15.0-RELEASE</code> followed by <code>freebsd-rustdate install</code>; the initial upgrade of the kernel components to <code>15.0-RELEASE</code> went well, and I was prompted to restart the system… and that&#8217;s when the troubles began.</p>
<p><span id="more-5223"></span></p>
<p>This was a headless machine racked elsewhere, and to my dismay I found that I wasn&#8217;t able to SSH into it after waiting the requisite amount of time. After discovering I wasn&#8217;t even able to ping the machine, I resigned myself to attaching a display and keyboard to the machine and went to see what was going on. Despite it being at least ten minutes (and probably more) since the initial part of the upgrade had succeeded, connecting the display caught the final moments of the machine as it was syncing disks in prep for a reboot. Had some service really taken that long to shut down after I had executed <code>sudo reboot</code>?</p>
<p>Despite seeing what finally looked like progress, I figured I might as well monitor the system come back online since I was already there with the display and keyboard hooked up. After the agonizingly long POST process followed by Dell&#8217;s inventory manager startup and the HBA additional boot-time payload execution (and Intel&#8217;s iSCSI firmware, and, and, and), the machine began to boot FreeBSD. I looked away for a second.. only to see the last vestiges of a shutdown notice as the system began to reboot just after coming up! Ten minutes later, this time with my iPhone recording video in slow motion,<sup id="rf1-5223"><a href="https://neosmart.net/blog/freebsd-15-0-post-upgrade-reboot-loop/#fn1-5223" title="A really dumb &ndash; yet undeniably useful &ndash; trick I learned to capture everything that takes place prior to a kernel panic or other sudden reboot, if the machine isn&rsquo;t connected over serial. Don&rsquo;t knock it until you&rsquo;ve tried it, the iPhone camera is fast enough to capture everything that gets dumped to screen and you can scrub through it as slowly as you like." rel="footnote">1</a></sup> I was able to unfortunately verify that it wasn&#8217;t a fluke, and the machine was indeed stuck in a reboot loop that kicked off just after all the services were brought up successfully and the kernel&#8217;s writes to the vtty quiesced. It didn&#8217;t look like anything had gone wrong: there was no kernel panic, services like <code>sshd</code> were being shut down in an orderly fashion, and the disks were synced before the machine would completely power cycle.</p>
<p>I naturally tried to boot up in single-user mode, both by pressing <code>2</code> at the UEFI bootloader menu and by manually executing <code>boot -s</code> at the bootloader prompt: they both failed in the same, exact manner. Stymied and <em>really</em> frustrated with how long each attempt took thanks to all the boot-time firmware applications and processes that had to happen before the system began to actually boot, I shut down the machine, yanked out the OS drive, and connected it to a spare desktop to be able to iterate more quickly.</p>
<p>And imagine my surprise when the FreeBSD 15 install booted up just fine with the disk in the test rig, with no hint of a reboot loop even in normal/multi-user mode!</p>
<p>I took the opportunity of having the machine fully boot up in normal mode to investigate the situation from the comfort of my regular environment, with my preferred keyboard layout, tools, and editors at my service (much better than a rescue shell!), and was surprised to see absolutely nothing out of the ordinary. The logs didn&#8217;t reveal any errors, no watchdog timers kicking in, no kernel panics (as I&#8217;d already surmised), no filesystem errors, no hardware failures (that were logged, at any rate). At this point, my best guess was that there was possibly something amiss with the ACPI support for this machine in the FreeBSD 15 release.<sup id="rf2-5223"><a href="https://neosmart.net/blog/freebsd-15-0-post-upgrade-reboot-loop/#fn2-5223" title="Although an errant interpretation of the ACPI data would normally result in a full shutdown, not a system reboot." rel="footnote">2</a></sup></p>
<p>Before trying to disable ACPI in the bootloader configuration, I figured I would try one last thing: finish the upgrade by updating the FreeBSD userland from 14.3 to 15.0, so I ran the requisite commands to bootstrap <code>pkg</code>, upgrade all installed packages, and then finish the <code>freebsd-rustdate</code>/<code>freebsd-update</code> install process. With the update fully complete, I ejected the root disk from the test machine and connected it back to the R720 server, though not with much hope of success.</p>
<p>But my skepticism was unwarranted: the fully upgraded userland didn&#8217;t exhibit the same symptoms I&#8217;d seen earlier and booted right into multiuser mode without a hint of a reboot loop! I had been prepared to really dive in to the issue, disabling ACPI to see if that fixed the problem, and bisecting my way through the system services to see if any were responsible (even though this happened in single user mode as well).</p>
<p>So if anyone finds themselves stuck with a reboot loop after upgrading to FreeBSD 15, try sticking the disk in another machine and completely upgrading the userland to FreeBSD 15 to see if that takes care of your problem. It did for me – though it certainly left me feeling particularly dissatisfied that I wasn&#8217;t able to figure out what actually caused the issue, and of course, always wondering if it would start happening again.<sup id="rf3-5223"><a href="https://neosmart.net/blog/freebsd-15-0-post-upgrade-reboot-loop/#fn3-5223" title="Which is the only healthy reaction to heisenbugs if you&rsquo;re not able to determine what&rsquo;s causing them!" rel="footnote">3</a></sup></p>
<hr class="footnotes"><ol class="footnotes"><li id="fn1-5223"><p>A really dumb – yet undeniably useful – trick I learned to capture everything that takes place prior to a kernel panic or other sudden reboot, if the machine isn&#8217;t connected over serial. Don&#8217;t knock it until you&#8217;ve tried it, the iPhone camera is fast enough to capture everything that gets dumped to screen and you can scrub through it as slowly as you like.&nbsp;<a href="#rf1-5223" class="backlink" title="Jump back to footnote 1 in the text.">&#8617;</a></p></li><li id="fn2-5223"><p>Although an errant interpretation of the ACPI data would normally result in a full shutdown, not a system reboot.&nbsp;<a href="#rf2-5223" class="backlink" title="Jump back to footnote 2 in the text.">&#8617;</a></p></li><li id="fn3-5223"><p>Which is the only healthy reaction to heisenbugs if you&#8217;re not able to determine what&#8217;s causing them!&nbsp;<a href="#rf3-5223" class="backlink" title="Jump back to footnote 3 in the text.">&#8617;</a></p></li></ol>The post <a href="https://neosmart.net/blog/freebsd-15-0-post-upgrade-reboot-loop/">FreeBSD 15.0 post-upgrade reboot loop</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/freebsd-15-0-post-upgrade-reboot-loop/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5223</post-id>	</item>
		<item>
		<title>The idiomatic ZFS on Linux quickstart cheat-sheet</title>
		<link>https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/</link>
					<comments>https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/#comments</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Wed, 10 Sep 2025 20:10:18 +0000</pubDate>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[freebsd]]></category>
		<category><![CDATA[systemd]]></category>
		<category><![CDATA[ubuntu]]></category>
		<category><![CDATA[zfs]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=5207</guid>

					<description><![CDATA[<p>I&#8217;m a FreeBSD guy that has had a long, serious, and very much monogamous relationship with ZFS. I experimented with Solaris 9 to learn about ZFS, adopted OpenSolaris (2008?) back in the &#8220;aughts&#8221; for my first ZFS server, transitioned my &#8230; <a href="https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/">The idiomatic ZFS on Linux quickstart cheat-sheet</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<p>I&#8217;m a FreeBSD guy that has had a long, serious, and very much monogamous relationship with ZFS. I experimented with Solaris 9 to learn about ZFS, adopted OpenSolaris (2008?) back in the &#8220;aughts&#8221; for my first ZFS server, transitioned my installations over to OpenIndiana after Oracle bought Sun Microsystems out, and then at some point switched to FreeBSD, which I found to be a better designed OS once I had moved everything headless and was ready to completely bid the need for a desktop environment goodbye. But every once in a while I have to stand up a ZFS installation on Ubuntu, and then I spend a little too much time trying to remember how to do ZFS things that FreeBSD makes easy out-of-the-box. After doing that one time too many, I decided to put down my Linux-specific notes in a post for others (and myself) to reference in the future.</p>
<h2>A fully functional ZFS setup following ZFS best practices and Linux/Ubuntu idiomatic approaches</h2>
<p>This guide will focus mainly on the Linux sysadmin side of things; note that basic knowledge and understanding of ZFS concepts and principles is assumed, but I&#8217;ll do my best to provide a succinct summary of what we&#8217;re doing and why we&#8217;re doing it at each point.</p>
<p><span id="more-5207"></span></p>
<h3>Step 1: Installing ZFS</h3>
<p>Unlike on FreeBSD, on Linux you need to manually install the ZFS kernel modules and userland tooling to bring in ZFS filesystem support and install the venerable <code>zfs</code> and <code>zpool</code> utils used to manage a ZFS installation. Canonical&#8217;s Ubuntu was, to my knowledge, the first to offer a pre-packaged ZFS option for Linux users (after gambling Oracle wouldn&#8217;t sue them for violating the CDDL license if they included ZFS support in their repos), and I believe it&#8217;s still the most popular Linux distribution for ZFS users, so the specific command line incantations below are for Ubuntu:</p>
<pre><code class="language-shell">sudo apt install zfs-dkms
</code></pre>
<p>This will download, build, and install the ZFS kernel modules to match the version of the Linux kernel you&#8217;re currently running. Unlike most kernel modules,<sup id="rf1-5207"><a href="https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/#fn1-5207" title="Largely due to licensing restriction workarounds" rel="footnote">1</a></sup> ZFS support isn&#8217;t built or distributed as part of the base kernel that Canonical maintains for its distributions, instead you have to manually build and load the kernel module that provides ZFS support (but this is automated by the <code>.deb</code> installed with <code>apt</code>) &#8211; and this needs to be done each time you upgrade the kernel.<sup id="rf2-5207"><a href="https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/#fn2-5207" title="This too is *normally* taken care of by the package manager, provided all the packages have been correctly built and uploaded to the repository by the time you try to install a newer kernel version. There&rsquo;s very little you have to do manually." rel="footnote">2</a></sup> All this really means is that installing <code>zfs-dkms</code> will take longer than installing most packages &#8211; expect the installation process to look like it&#8217;s stuck and be extra patient. Installing <code>zfs-dkms</code> will also pull in an automatic dependency on the userspace tools, <code>zfsutils-linux</code>, as well as other ZFS-related libraries and dependencies.</p>
<h3>Step 2: Setting up your zpool</h3>
<p>This part of the process is largely going to be the same regardless of which operating system you are using and is standard ZFS fare. You&#8217;ll need to identify the drives you wish to use in your zpool (the ZFS abstraction over the physical disks, arranged in the hierarchy/topography you desire) and use <code>sudo zpool create</code> to create your first zpool (traditionally named <code>tank</code>). The only thing of note here is that you should use a stable path available to identify your disks, so instead of doing something like <code>sudo zpool create tank mirror /dev/sda /dev/sdb</code> to create a two-disk mirror zpool comprised of the two disks <code>/dev/sda</code> and <code>/dev/sdb</code>, you should instead use a different path to the same device, such as via <code>/dev/disk/by-id/</code> or <code>/dev/disk/by-uuid/</code> (going with <code>by-id/</code> might make it easier to figure out which disk to use, as the contents of <code>by-uuid/</code> are all GUIDs).<sup id="rf3-5207"><a href="https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/#fn3-5207" title="You could use /dev/disk/by-path/ but that means if you physically swap disks around in their cages the references would become switched around, so it&rsquo;s best not to." rel="footnote">3</a></sup> On Linux, <code>lsblk</code> is your friend here to list disks attached to the system.</p>
<pre><code class="language-shell"># to create a mirror of the two volumes:
sudo zpool create -o ashift=12 tank mirror /dev/disk/by-id/scsi-0VOLUME_NAME_01 /dev/disk/by-id/scsi-0VOLUME_NAME_02
</code></pre>
<p>And you can verify that the operation has succeeded by using `zpool list` to see the list of zpools live on the system.</p>
<p>Most ZFS properties and features are configurable, can be set at any time,<sup id="rf4-5207"><a href="https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/#fn4-5207" title="The most notable exception to this is the ashift property set above with -o ashift=12, which is a decent value for any ssd or 4k/512e hdd." rel="footnote">4</a></sup> and are inherited from parent datasets. Let&#8217;s set some default properties that are good starting values (we can always change them or override them for specific child datasets at any time):</p>
<pre><code class="language-bash">sudo zfs set compression=lz4 tank # or =zstd if you're on the very newest versions
sudo zfs set recordsize=1m tank # i/o optimization for storing content that rarely changes
</code></pre>
<h3>Step 3: Creating your ZFS datasets</h3>
<p>The &#8220;zpool&#8221; is, as mentioned, the abstraction over the physical disks in your PC. It&#8217;s closest analogue is a &#8220;smart disk&#8221; comprised of multiple physical disks arranged in some specific topography with certain striping/redundancy/parity managed by the lower-level <code>zpool</code> command. Just as a zpool is a virtual disk, a dataset is a &#8220;smart partition&#8221; used to break up your &#8220;disk&#8221; into multiple logical storage units. Unlike real partitions, zfs datasets aren&#8217;t fixed in size, they rather straddle the line between a partition and a folder. They can be nested (like a folder), but you can&#8217;t rename a file across datasets (like a separate filesystem/partition). You can snapshot them individually (or altogether, atomically) for backup and cloning purposes (see below), and its the finest-grained level of control you have for turning on/off or re-configuring ZFS features and properties like the record size (only available to the time of creation), automatic block-level compression, etc.</p>
<p>While ZFS automatically creates a dataset for the root zpool (in this case we now have <code>/tank/</code> mounted and ready) but it&#8217;s <em>generally</em> not a good practice to write directly to this dataset. Instead, you should create one or more child datasets where most content will go. We&#8217;ll just create one dataset for now:</p>
<pre><code class="language-shell">sudo zfs create tank/data
</code></pre>
<p>and we can see all our datasets with `zfs list`, which shows them in their hierarchy/tree as configured.</p>
<h3>Step 4: Configuring automatic snapshots</h3>
<p>One of the coolest and most important ZFS features is undoubtedly its instant, zero-cost snapshotting (enabled by its copy-on-write design). This lets you freeze an image of any dataset (or all of them) as it exists at any point of time, then restore back to it (or selectively copy files/data back, as needed) at any point in the future, regardless of any changes you&#8217;ve made. (You only pay the storage cost of data added or deleted thereafter.) ZFS snapshots can be made manually with <code>sudo zfs snap -r tank@snapshot_name_or_date</code> (which snapshots <code>tank</code> and all its child datasets, instantly) or <code>sudo zfs snap tank/data@snapshot_name</code> (which snapshots only the one <code>tank/data</code> dataset). But since they&#8217;re virtually free, why not go a step further and automatically take snapshots of the data on a schedule? That way you&#8217;re protected in case of inadvertent data loss, not just when you take a snapshot before manually performing a known potentially destructive action.</p>
<p>On Linux, the best way to automate these snapshots is with <code>zfs-auto-snapshot</code>, which we&#8217;ll install with <code>sudo apt install zfs-auto-snapshot</code>. It&#8217;ll automatically create new snapshots every month/week/day/hour of designated zfs datasets, and delete the oldest ones too so you&#8217;re not paying the storage price forever.</p>
<p>After installing <code>zfs-auto-snapshot</code>, it&#8217;s time to choose which datasets we want to protect and how often we want to take the snapshots. Instead of using a configuration file, <code>zfs-auto-snapshot</code> uses zfs properties to determine which zfs datasets to include in each snapshot interval, and since zfs properties are inherited by default, if we set up snapshots for the root dataset, it&#8217;ll automatically do the same for all child datasets.</p>
<p>Let&#8217;s enable a daily snapshot of the root volume (and all child datasets):</p>
<pre><code class="language-shell">sudo zfs set com.sun:auto-snapshot:daily=true tank
</code></pre>
<p>You can repeat this but replace <code>daily</code> with one of <code>daily</code>, <code>monthly</code>, <code>weekly</code>, or <code>hourly</code> to (additionally) opt-into that frequency of snapshots. To <em>exclude</em> a dataset (and its children) from being included in a particular schedule, you can e.g. use <code>sudo zfs set com.sun:auto-snapshot:daily=false tank/no_backups</code> to turn off daily snapshots for the <code>tank/no_backups</code> dataset (assuming it exists).</p>
<p>You can check if this is working (after waiting the prescribed amount of time) by checking to see what snapshots you have listed:</p>
<pre>zfs list -t snap</pre>
<h3>Step 5: Automatic monthly ZFS scrubs on Linux with systemd</h3>
<p>One thing that makes ZFS stand out compared to other operating systems like <code>ext4</code> or even <code>xfs</code> is that it calculates the hash of each block of data you store on it. In the event of <a href="https://en.wikipedia.org/wiki/Data_degradation" rel="follow">bitrot</a> (the silent corruption of data already stored to disk), zfs can a) flag that a file has been silently corrupted, b) automatically restore a good copy from another disk or parity (assuming your zpool topography provides redunancy).<sup id="rf5-5207"><a href="https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/#fn5-5207" title="Or assuming you are using zfs set copies=2 tank (or greater)." rel="footnote">5</a></sup> It does this automatically every time you read a file, but what about if you have terabytes of data just sitting there, silently rotting away? How do you catch that corruption in time to fix it from a second copy on the zpool? The <code>zpool scrub tank</code> operation runs a low-priority scan in the background to detect and (hopefully) repair just that, but it needs to be scheduled (or run manually).</p>
<p>On FreeBSD, this would be accomplished with the help of a simple monthly periodic script, but on Linux (Ubuntu particularly) it&#8217;s not as simple. The idiomatic way of setting up monthly work on Ubuntu is via the use of systemd units (aka services) and timers, unfortunately this requires setting up two separate files, but the good news is that you can just copy and paste what I&#8217;ve provided below, only modifying the zpool name from <code>tank</code> to whatever you are using, as needed.</p>
<p>The first file we need to create is the actual systemd service, which is what is tasked with running running the <code>zfs scrub</code> operation. Copy the following to <code>/etc/systemd/system/zfs-scrub.service</code>:</p>
<pre><code class="language-ini">[Unit]
Description=ZFS scrub of all pools

[Service]
Type=oneshot
ExecStart=/usr/sbin/zpool scrub tank
</code></pre>
<p>And copy this timer file (which specifies when <code>zfs-scrub.service</code> is automatically run) to <code>/etc/systemd/system/zfs-scrub.timer</code>:</p>
<pre><code class="language-ini">[Unit]
Description=Run ZFS scrub service monthly

[Timer]
OnCalendar=monthly
# Run if missed while machine was off
Persistent=true
# Add some randomization to start time to prevent thundering herd
RandomizedDelaySec=30m
AccuracySec=1h

[Install]
WantedBy=timers.target
</code></pre>
<p>Then execute the following to get systemd to see and activate the monthly scrub service:</p>
<pre><code class="language-shell">systemctl daemon-reload
systemctl enable --now zfs-scrub.timer
</code></pre>
<p>and verify that the timer has been started with the following:</p>
<pre><code class="language-shell">systemctl list-timers zfs-scrub.timer
</code></pre>
<p>which should show you output along the lines of the following:</p>
<pre><code class="language-shell">$ systemctl list-timers zfs-scrub.timer
NEXT LEFT LAST PASSED UNIT ACTIVATES
Wed 2025-10-01 00:23:31 UTC 2 weeks 6 days - - zfs-scrub.timer zfs-scrub.service

1 timers listed.
</code></pre>
<p>You can see if this is working by checking when the last scrub took place with <code>zpool status</code>:</p>
<pre><code class="language-shell">$ zpool status
pool: tank
state: ONLINE
scan: scrub repaired 0B in 00:00:00 with 0 errors on Wed Sep 10 18:43:22 2025
</code></pre>
<p>And with that, you&#8217;re all set!</p>
<hr class="footnotes"><ol class="footnotes"><li id="fn1-5207"><p>Largely due to licensing restriction workarounds&nbsp;<a href="#rf1-5207" class="backlink" title="Jump back to footnote 1 in the text.">&#8617;</a></p></li><li id="fn2-5207"><p>This too is *normally* taken care of by the package manager, provided all the packages have been correctly built and uploaded to the repository by the time you try to install a newer kernel version. There&#8217;s very little you have to do manually.&nbsp;<a href="#rf2-5207" class="backlink" title="Jump back to footnote 2 in the text.">&#8617;</a></p></li><li id="fn3-5207"><p>You <em>could</em> use <code>/dev/disk/by-path/</code> but that means if you physically swap disks around in their cages the references would become switched around, so it&#8217;s best not to.&nbsp;<a href="#rf3-5207" class="backlink" title="Jump back to footnote 3 in the text.">&#8617;</a></p></li><li id="fn4-5207"><p>The most notable exception to this is the <code>ashift</code> property set above with <code>-o ashift=12</code>, which is a decent value for any ssd or 4k/512e hdd.&nbsp;<a href="#rf4-5207" class="backlink" title="Jump back to footnote 4 in the text.">&#8617;</a></p></li><li id="fn5-5207"><p>Or assuming you are using <code>zfs set copies=2 tank</code> (or greater).&nbsp;<a href="#rf5-5207" class="backlink" title="Jump back to footnote 5 in the text.">&#8617;</a></p></li></ol>The post <a href="https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/">The idiomatic ZFS on Linux quickstart cheat-sheet</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/zfs-on-linux-quickstart-cheat-sheet/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5207</post-id>	</item>
		<item>
		<title>Benchmarking rust compilation speedups and slowdowns from sccache and -Zthreads</title>
		<link>https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/</link>
					<comments>https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/#comments</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Mon, 01 Jul 2024 18:50:08 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[rust]]></category>
		<category><![CDATA[rustc]]></category>
		<category><![CDATA[sccache]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=5166</guid>

					<description><![CDATA[<p>Just a PSA from one rust developer to another: if you use sccache, take a moment to benchmark a clean build1 of your favorite or current project and verify whether or not having RUSTC_WRAPPER=sccache is doing you any favors. I&#8217;ve &#8230; <a href="https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/">Benchmarking rust compilation speedups and slowdowns from <code>sccache</code> and <code>-Zthreads</code></a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<p>Just a PSA from one rust developer to another: if you use <code>sccache</code>, take a moment to benchmark a clean build<sup id="rf1-5166"><a href="https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/#fn1-5166" title="sccache does not cache nor speed up incremental builds, and recent versions try to more or less bypass the caching pipeline altogether in an attempt to avoid slowing down incremental builds." rel="footnote">1</a></sup> of your favorite or current project and verify whether or not having <code>RUSTC_WRAPPER=sccache</code> is doing you any favors.</p>
<p>I&#8217;ve been an sccache user almost from the very start, when the Mozilla team first introduced it in the rust discussions on GitHub maybe seven years back or so, probably because of my die-hard belief in the one-and-only <code>ccache</code> earned over long years of saving my considerable time and effort on C++ development projects (life pro-tip: <code>git bisect</code> on large C or C++ projects is a night-and-day difference with versus without <code>ccache</code>). At the same time, I was always painfully aware of just how little <code>sccache</code> actually cached compared to its C++ progenitor, and I was left feeling perpetually discontented ever since learning to query its hit rate stats with <code>sccache -s</code> (something I never needed to do for <code>ccache</code>).</p>
<p>But my blind belief in the value of build accelerators led me to complacency, and I confess that with <code>sccache</code> <em>mostly</em> chugging away without issue in the background, I kind of forgot that I had <code>RUSTC_WRAPPER</code> set at all. But I recently remembered it and in a bout of procrastination, decided to benchmark how much time <code>sccache</code> was <em>actually</em> saving me&#8230; and the results were decidedly not great.</p>
<p><span id="more-5166"></span></p>
<p>The bulk of my rust use at $work is done on a workstation under WSLv1, while a lot of of my open source rust work is done on a &#8220;mobile workstation&#8221; running a Debian derivative. I began my investigation on the WSL desktop, originally spurred by a <del>need</del> desire to see what kind of speedups different values for<a href="https://blog.rust-lang.org/2023/11/09/parallel-rustc.html" rel="follow"> the ~recently added <code>-Z threads</code> flag</a> to parallelize the <code>rustc</code> frontend would net me, then remembered that I had <code>sccache</code> enabled and should disable that for deterministic results&#8230; leading me to include the impact of <code>sccache</code> in my benchmarks.</p>
<p>On a (well-cooled, not thermally throttled) 16-core (32-thread) AMD Ryzen ThreadRipper 1950X machine with 64 GB of DDR4 RAM and NVMe boot and (code) storage disks, using rustc 1.81.0-nightly to compile a relatively modest-sized rust project from scratch in debug mode (followed by exactly one clean rebuild in the case of sccache to see how much of a speedup it gives), I obtained the following results:</p>
<table border="1">
<thead class="bold">
<tr>
<th>-Zthreads</th>
<th>sccache</th>
<th>Time</th>
<th>Time (2nd run)</th>
</tr>
</thead>
<tbody>
<tr>
<td><em>not set</em></td>
<td>no</td>
<td>33.08s</td>
<td></td>
</tr>
<tr>
<td><em>not set</em></td>
<td>yes</td>
<td>1m 18s</td>
<td>56.20s</td>
</tr>
<tr>
<td>8</td>
<td>no</td>
<td>33.27s</td>
<td></td>
</tr>
<tr>
<td>8</td>
<td>yes</td>
<td>1m 32s</td>
<td>1m 00s</td>
</tr>
<tr>
<td>16</td>
<td>no</td>
<td>34.43s</td>
<td></td>
</tr>
<tr>
<td>16</td>
<td>yes</td>
<td>40.78s</td>
<td>56.06s</td>
</tr>
<tr>
<td>32</td>
<td>no</td>
<td>37.25s</td>
<td></td>
</tr>
<tr>
<td>32</td>
<td>yes</td>
<td>1m 14s</td>
<td>52.99s</td>
</tr>
</tbody>
</table>
<p>Shockingly enough, there was not a single configuration where the usage of <code>sccache</code> ended up speeding up compilation over the baseline without it — not even on a subsequent clean build of the same code with the same <code>RUSTFLAGS</code> value.</p>
<p>After inspecting the results of <code>sccache -s</code> and with some experimentation, it turned out that this pathological performance appeared to be caused – in part – by having a full sccache cache folder.<sup id="rf2-5166"><a href="https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/#fn2-5166" title="A similar (but not identical) issue was reported in the project&rsquo;s GitHub repo in December of 2019." rel="footnote">2</a></sup> Running <code>rm -rf ~/.cache/sccache/*</code> then re-running the benchmark (a few project commits later, so not to be directly compared to the above) revealed a significant improvement to the baseline build times with sccache… but probably not enough to justify its use:</p>
<div class="table-scroll">
<table border="1">
<thead class="bold">
<tr>
<th>-Zthreads</th>
<th>sccache</th>
<th>Time</th>
<th>Time<br />
(2nd Run)</th>
<th>-ZTHREADS</th>
<th>sccache<br />
(initial)</th>
<th>sccache<br />
(subsequent)</th>
</tr>
</thead>
<tbody>
<tr>
<td><em>not set</em></td>
<td>no</td>
<td>31.41s</td>
<td></td>
<td>1.000</td>
<td></td>
<td></td>
</tr>
<tr>
<td><em>not set</em></td>
<td>yes</td>
<td>42.57s</td>
<td>29.47s</td>
<td></td>
<td>1.355</td>
<td>0.938</td>
</tr>
<tr>
<td>0</td>
<td>no</td>
<td>35.94s</td>
<td></td>
<td>1.144</td>
<td></td>
<td></td>
</tr>
<tr>
<td>0</td>
<td>yes</td>
<td>44.26s</td>
<td>28.95s</td>
<td></td>
<td>1.231</td>
<td>0.806</td>
</tr>
<tr>
<td>2</td>
<td>no</td>
<td>30.53s</td>
<td></td>
<td>0.972</td>
<td></td>
<td></td>
</tr>
<tr>
<td>2</td>
<td>yes</td>
<td>58.94s</td>
<td>38.36s</td>
<td></td>
<td>1.931</td>
<td>1.256</td>
</tr>
<tr>
<td>4</td>
<td>no</td>
<td>29.69s</td>
<td></td>
<td>0.945</td>
<td></td>
<td></td>
</tr>
<tr>
<td>4</td>
<td>yes</td>
<td>1m 10s</td>
<td>43.57s</td>
<td></td>
<td>2.358</td>
<td>1.467</td>
</tr>
<tr>
<td>8</td>
<td>no</td>
<td>30.43s</td>
<td></td>
<td>0.969</td>
<td></td>
<td></td>
</tr>
<tr>
<td>8</td>
<td>yes</td>
<td>1m 17s</td>
<td>47.90s</td>
<td></td>
<td>2.530</td>
<td>1.574</td>
</tr>
<tr>
<td>16</td>
<td>no</td>
<td>32.39s</td>
<td></td>
<td>1.031</td>
<td></td>
<td></td>
</tr>
<tr>
<td>16</td>
<td>yes</td>
<td>1m 22s</td>
<td>52.67s</td>
<td></td>
<td>2.532</td>
<td>1.626</td>
</tr>
<tr>
<td>32</td>
<td>no</td>
<td>35.17s</td>
<td></td>
<td>1.120</td>
<td></td>
<td></td>
</tr>
<tr>
<td>32</td>
<td>yes</td>
<td>1m 26s</td>
<td>53.27s</td>
<td></td>
<td>2.445</td>
<td>1.515</td>
</tr>
</tbody>
</table>
</div>
<p>Looking at the chart above, using sccache slowed down the clean build by anywhere from 23% (in the case of no <code>-Zthreads</code>) to 153% (in the case of <code>-Zthreads</code> set to 8 or 16), while providing a speed up to subsequent builds with identical flags and unchanged code/toolchain only in the case of no <code>-Zthreads</code> (6% speedup) and by 19% in the case of <code>-Zthreads</code> set to <code>0</code> (its default value), <strong>but it still managed to slow down even subsequent, clean builds with a fully primed cache as compared to the no-sccache baseline </strong>by from 25% (<code>-Zthreads=2</code>) to 51% (<code>-Zthreads=32</code>).</p>
<p>Analyzing the benefits of <code>-Zthreads</code> is much harder. Looking at the cases with <code>-Zthreads</code> but no <code>sccache</code>, it appears that with a heavily saturated pipeline (building all project dependencies from scratch in non-incremental mode affords a lot of opportunities for parallelized codegen), the use of <code>-Zthreads</code> can provide <em>at best</em> a very modest 5% speed up to build times (in the case of <code>-Zthreads=4</code>) while actually <em>slowing down </em>compile times by 14% (the soon-to-be-default <code>-Zthreads=0</code>) and 12% (with <code>-Zthreads=16</code>).<sup id="rf3-5166"><a href="https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/#fn3-5166" title="I assumed -Zthreads=0 would mean &ldquo;default to the available concurrency&rdquo; (i.e. 32 threads, in my case) but that doesn&rsquo;t appear to be the case just by looking at the numbers." rel="footnote">3</a></sup></p>
<p>It&#8217;s interesting to note that the rust developers were rather more ebullient when <a href="https://blog.rust-lang.org/2023/11/09/parallel-rustc.html" rel="follow">introducing this feature</a> to the world, claiming wins of up to 50% with <code>-Zthreads=8</code> and suggesting that lower levels of parallelism would see lower speedups (the opposite of what I saw, where using 8 threads provided about half the benefit of using 4, and going any higher caused slow-downs rather than speed-ups). Note that I was compiling in the default dev/debug profile above, so maybe I should try and see what happens in release mode, though I would think the architectural limitations would persist.</p>
<p>Back to sccache, though.</p>
<p>One of the open source projects I contribute to most is <a href="https://github.com/fish-shell/fish-shell/" rel="nofollow">fish-shell</a>, which recently underwent a complete port from C++ to Rust (piece-by-piece while still passing all tests every step of the way). Some day I want to write at length about that experience, but the reason I&#8217;m bringing this up is because my fish experience has taught me that some things that are normally very fast under Linux can run much slower than expected under WSL, primarily due to I/O constraints caused by the filesystem virtualization layer. I haven&#8217;t dug into it yet, but going with the working theory that reading/writing some 550-800 MiB<sup id="rf4-5166"><a href="https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/#fn4-5166" title="The size of the target/ folder varies depending on the RUSTFLAGS used." rel="footnote">4</a></sup> was slowing down my builds (even with the code and the cache located on two separate NVMe drives), I moved on to my other machine (where I&#8217;m running Linux natively).</p>
<p>Running the same benchmark with the same versions of <code>rustc</code> and <code>sccache</code> on the 4-core/8-thread Intel Xeon E3-1545M v5 (Skylake) with 32GiB of DDR3 RAM gave the following results, which were <em>much</em> more in line with my expectations for sccache (though even more disappointing when it came to the frontend parallelization flag):</p>
<table border="1">
<thead class="bold">
<tr>
<th>-Zthreads</th>
<th>sccache</th>
<th>Time</th>
<th>Time (2nd Run)</th>
</tr>
</thead>
<tbody>
<tr>
<td>unset</td>
<td>no</td>
<td>1m 10s</td>
<td></td>
</tr>
<tr>
<td>unset</td>
<td>yes</td>
<td>1m 13s</td>
<td>14.12s</td>
</tr>
<tr>
<td>0</td>
<td>no</td>
<td>1m 12s</td>
<td></td>
</tr>
<tr>
<td>0</td>
<td>yes</td>
<td>1m 18s</td>
<td>14.26s</td>
</tr>
<tr>
<td>2</td>
<td>no</td>
<td>1m 10s</td>
<td></td>
</tr>
<tr>
<td>2</td>
<td>yes</td>
<td>1m 17s</td>
<td>14.20s</td>
</tr>
<tr>
<td>4</td>
<td>no</td>
<td>1m 11s</td>
<td></td>
</tr>
<tr>
<td>4</td>
<td>yes</td>
<td>1m 15s</td>
<td>14.44s</td>
</tr>
<tr>
<td>6</td>
<td>no</td>
<td>1m 12s</td>
<td></td>
</tr>
<tr>
<td>6</td>
<td>yes</td>
<td>1m 16s</td>
<td>14.90s</td>
</tr>
<tr>
<td>8</td>
<td>no</td>
<td>1m 14s</td>
<td></td>
</tr>
<tr>
<td>8</td>
<td>yes</td>
<td>1m 20s</td>
<td>15.04s</td>
</tr>
</tbody>
</table>
<p>Here at last are the <code>sccache</code> results I was expecting! A maximum slowdown of about 8% for uncached builds and speedups of about 80% across the board for a (clean) rebuild of the same project immediately after caching the artifacts.<sup id="rf5-5166"><a href="https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/#fn5-5166" title="This was, of course, the same version of sccache that was tested under WSL above." rel="footnote">5</a></sup></p>
<p>As for <code>-Zthreads</code>, the results here are consistent with what we saw above, at least once you take into account the fact that there are significantly fewer cores/threads to distribute the parallelized frontend work across. But we&#8217;re left with the same conclusion that at times when the CPU is already handling a high degree of concurrency with jobserver already saturated with work from the compilation pipeline across multiple compilation units from independent crates, adding further threads to the mix ends up hurting overall performance (to the tune of 14% in the worst case, when <code>-Zthreads</code> is set to total number of cores). The good news is that adding just a slight degree of parallelization with <code>-Zthreads=2</code> doesn&#8217;t hurt build times in this worst-case scenario and likely helps when the available threads aren&#8217;t already saturated with more work than they can handle, so that at least seems to be a safe value for the option for now.</p>
<p>I would have <em>expected</em> that <code>-Zthreads</code> wouldn&#8217;t &#8220;unilaterally&#8221; dictate the number of chunks frontend work was being broken up into. While I&#8217;m sure it integrates nicely with jobserver to prevent an insane number of threads from being spawned and overwhelming the machine, it would seem that dividing the frontend work into <em>n</em> chunks when there aren&#8217;t <em>n</em> threads immediately available ends up hurting overall build performance. So in that sense, I suppose it would be better if <code>-Zthreads</code> were a hint of sorts, treated as a &#8220;max chunks&#8221; limit, and if introspection of available threads happened <em>before</em> the decision was made to chunk the available work (and to what extent) so that the behavior of <code>-Zthreads</code>, even with a hard-coded number, would hopefully only ever improve build times and never hurt.</p>
<div class="sendy_widget" style='margin-bottom: 0.5em;'>
<p><em>If you would like to receive a notification the next time we release a rust library, publish a crate, or post some rust-related developer articles, you can subscribe below. Note that you'll only get notifications relevant to rust programming and development by NeoSmart Technologies. If you want to receive email updates for all NeoSmart Technologies posts and releases, please sign up in the sidebar to the right instead.</em></p>
<iframe tabIndex=-1 onfocus="sendy_no_focus" src="https://neosmart.net/sendy/subscription?f=BUopX8f2VyLSOb892VIx6W4IUNylMrro5AN6cExmwnoKFQPz9892VSk4Que8yv892RnQgL&title=Join+the+rust+mailing+list" style="height: 300px; width: 100%;"></iframe>
</div>
<script type="text/javascript">function sendy_no_focus(e) { e.preventDefault(); }</script>
<hr class="footnotes"><ol class="footnotes"><li id="fn1-5166"><p>sccache does not cache nor speed up incremental builds, and recent versions try to more or less bypass the caching pipeline altogether in an attempt to avoid slowing down incremental builds.&nbsp;<a href="#rf1-5166" class="backlink" title="Jump back to footnote 1 in the text.">&#8617;</a></p></li><li id="fn2-5166"><p>A <a href="https://github.com/mozilla/sccache/issues/629" rel="nofollow">similar (but not identical) issue</a> was reported in the project&#8217;s GitHub repo in December of 2019.&nbsp;<a href="#rf2-5166" class="backlink" title="Jump back to footnote 2 in the text.">&#8617;</a></p></li><li id="fn3-5166"><p>I <em>assumed</em> <code>-Zthreads=0</code> would mean &#8220;default to the available concurrency&#8221; (i.e. 32 threads, in my case) but that doesn&#8217;t appear to be the case just by looking at the numbers.&nbsp;<a href="#rf3-5166" class="backlink" title="Jump back to footnote 3 in the text.">&#8617;</a></p></li><li id="fn4-5166"><p>The size of the <code>target/</code> folder varies depending on the <code>RUSTFLAGS</code> used.&nbsp;<a href="#rf4-5166" class="backlink" title="Jump back to footnote 4 in the text.">&#8617;</a></p></li><li id="fn5-5166"><p>This was, of course, the same version of <code>sccache</code> that was tested under WSL above.&nbsp;<a href="#rf5-5166" class="backlink" title="Jump back to footnote 5 in the text.">&#8617;</a></p></li></ol>The post <a href="https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/">Benchmarking rust compilation speedups and slowdowns from <code>sccache</code> and <code>-Zthreads</code></a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/benchmarking-rust-compilation-speedups-and-slowdowns-from-sccache-and-zthreads/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5166</post-id>	</item>
		<item>
		<title>Using build.rs to integrate rust applications with system libraries like a pro</title>
		<link>https://neosmart.net/blog/using-build-rs-to-integrate-rust-applications-with-system-libraries-like-a-pro/</link>
					<comments>https://neosmart.net/blog/using-build-rs-to-integrate-rust-applications-with-system-libraries-like-a-pro/#respond</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Mon, 13 May 2024 16:24:30 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[fish]]></category>
		<category><![CDATA[open source]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[rsconf]]></category>
		<category><![CDATA[rust]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=5136</guid>

					<description><![CDATA[<p>I&#8217;m happy to announce the release of version 0.2 of the rsconf crate, with new support for informing Cargo about the presence of custom cfg keys and values (to work around a major change that has resulted in hundreds of &#8230; <a href="https://neosmart.net/blog/using-build-rs-to-integrate-rust-applications-with-system-libraries-like-a-pro/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/using-build-rs-to-integrate-rust-applications-with-system-libraries-like-a-pro/">Using <code>build.rs</code> to integrate rust applications with system libraries like a pro</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<p><img loading="lazy" decoding="async" class="alignright wp-image-4454 colorbox-5136" src="https://neosmart.net/blog/wp-content/uploads/rust-logo-600x600.png" alt="" width="109" height="109" srcset="https://neosmart.net/blog/wp-content/uploads/rust-logo-600x600.png 600w, https://neosmart.net/blog/wp-content/uploads/rust-logo-150x150.png 150w, https://neosmart.net/blog/wp-content/uploads/rust-logo-300x300.png 300w, https://neosmart.net/blog/wp-content/uploads/rust-logo.png 1024w" sizes="auto, (max-width: 109px) 100vw, 109px" />I&#8217;m happy to announce the release of version 0.2 of <a href="https://github.com/mqudsi/rsconf" rel="nofollow">the rsconf crate</a>, with new support for informing Cargo about the presence of custom <code>cfg</code> keys and values (to work around a major change that has resulted in hundreds of warnings for many popular crates under 1.80 nightly).</p>
<p>rsconf itself is a newer crate that was born out of the need (in the <a href="http://github.com/fish-shell/fish-shell/" rel="nofollow">fish-shell</a> transition from C++ to rust) for a replacement for some work that&#8217;s traditionally been relegated to the build system (e.g. CMake or autoconf) in order to &#8220;feature detect&#8221; various native system capabilities in the kernel, selected runtime (e.g. <code>libc</code>), or installed libraries. It (optionally) integrates with the popular <a href="https://crates.io/crates/cc" rel="nofollow">cc crate</a> so you can test and configure the build toolchain for various underlying features or behavior, and then unlock conditional compilation of native rust code that interops with the system or external libraries accordingly.</p>
<p><span id="more-5136"></span></p>
<p>While Cargo is an impressive build tool and normally more than sufficient for the needs of the majority of rust crates shipping standalone, contained libraries or packages, for those of us transitioning &#8220;more brittle&#8221; system software or libraries that rely on functionality of the native kernel, libc, or external libraries – and often have to support older versions thereof, lacking in features or enhancements – it doesn&#8217;t offer feature parity with some of the build tools we&#8217;ve been traditionally using in the C and C++ world.</p>
<p>Fortunately, the language and tooling devs behind rust recognized early on the need for a more flexible and involved approach to building certain crates and came up with the <code>build.rs</code> approach that lets developers run what is effectively a rust script prior to the traditional build steps invoked by Cargo, influencing how Cargo works, what flags are passed to <code>rustc</code> (the actual rust compiler), and what libraries Cargo asks your linker to include when generating the final binary. Importantly, at this stage devs can inspect the host/target systems and tell the compiler which Cargo <em>features</em> and rust <em>cfgs</em> should be enabled, letting you conditionally compile (or not) various bits and pieces of regular rust code to take advantage of functionality discovered to be present at build time or work around capabilities found to be missing or lacking.</p>
<p>In addition to supporting external libraries (like <code>gettext</code> and, once upon a time, <code>curses</code>), it also runs on quite a number of different unixy systems, starting with Linux, macOS, and the BSDs, and with a long tail of support for various other fun posix-compatible operating systems and projects.<sup id="rf1-5136"><a href="https://neosmart.net/blog/using-build-rs-to-integrate-rust-applications-with-system-libraries-like-a-pro/#fn1-5136" title="Unfortunately, as a part of the transition from C++ to rust, we have currently lost support for the vast majority of the legacy OSes and other non-tier-1 unix platforms we used to support due to issues with availability of the rust toolchain and incompatible dependencies like the libc crate, but we are keen to get them back." rel="footnote">1</a></sup> As you can imagine, as a shell, fish does a lot of stuff outside the purview of the rust standard library and a lot of the codebase deals with low-level integration with the operating system – and the details of this change quite a bit just from one kernel version to the other, let alone across different OS kernels and distributions altogether.</p>
<p>A lot of the crates in the rust ecosystem seem to rely on OS detection to determine what feature should or shouldn&#8217;t be available, but the C/C++ world moved from that to feature detection a long time ago. Fish uses <code>build.rs</code> to determine what low-level operating system features are available (regardless of what OS we are targeting) and enables or disables the compilation of rust code sitting behind <code>#[cfg(key = "value")]</code> depending on the results.</p>
<p>Cargo exposes a very bare-bones mechanism for <code>build.rs</code> to influence which <code>cfg</code> or <code>feature</code> will be enabled/disabled at build-time by means of intercepting specially prefixed <code>stdout</code> messages. Generally, this would look like</p>
<ol>
<li>In <code>build.rs</code>, check for the presence/absence of some feature <em>somehow</em>,</li>
<li>In response, execute <code>println!("cargo:rustc-cfg=foo");</code></li>
</ol>
<p>Which prints to <code>stdout</code> a line of text Cargo will intercept when it is running the compiled <code>build.rs</code> binary and, based off of that, tell <code>rustc</code> to enable the cfg <code>foo</code>. This &#8220;unlocks&#8221; code behind a <code>#[cfg(foo)]</code>, allowing <code>rustc</code> to see and compile it (normally it would be as if it weren&#8217;t there at all).</p>
<p>This is all well and good, but it has a few obvious drawbacks. The first is that <em><em>somehow</em></em> that glares at us from point number one above. How exactly <em>does</em> one check if a system feature is present or not? Why doesn&#8217;t Cargo help us in this regard? In the world of legacy build systems, this is part and parcel of what a build system does and, in fact, a <em>raison d&#8217;être</em> for their existence in a world that predates package managers, semantic versioning, and all the other nice stuff we can now take for granted in a rust-native ecosystem.</p>
<p>It&#8217;s from this need that rsconf was born. The crate offers some of the functionality typically made available by &#8220;legacy&#8221; build systems, wrapped in an easy-to-use and rust-friendly api. Some examples of the available functionality:</p>
<ul>
<li><code>system.has_library(libname)</code></li>
<li><code>system.has_header(name)</code></li>
<li><code>system.has_symbol(symbol)</code></li>
<li><code>system.has_symbol_in(symbol, &amp;[libname])</code></li>
<li><code>system.has_type(type_name)</code></li>
<li><code>system.get_macro_value(name)</code></li>
<li><code>system.get_{u32,u64,i32,i64}_value(ident)</code></li>
<li><code>system.ifdef(define)</code></li>
<li><code>system.if(expr)</code></li>
<li><code>add_library_search_path(path)</code></li>
<li><code>link_library(libname, LinkType::Static/Dynamic)</code></li>
</ul>
<p>The names of these methods should make what they do quite clear, and there are various convenience functions to simplify some common patterns around these same principles. The methods themselves are largely implemented as build-time attempts to compile or link minimal C source code to determine the truthiness of the expressions, while the latter two direct Cargo as to how it should attempt find and use external libraries pre-installed on the build host/target.<sup id="rf2-5136"><a href="https://neosmart.net/blog/using-build-rs-to-integrate-rust-applications-with-system-libraries-like-a-pro/#fn2-5136" title="One thing I really appreciate about Cargo is that it goes to great lengths to support cross-compilation out-of-the-box, clarifying operations and configurations taken from/applying to the machine you are building on (the host) vs the machine you are building for (the target). rsconf is written in a way that similarly respects this divide." rel="footnote">2</a></sup></p>
<p>In addition, there are methods that make it easier to perform &#8220;regular&#8221; Cargo stuff in a <code>build.rs</code> script, offering a &#8220;strongly typed&#8221; api instead of the usual <code>println!()</code> stuff that is prone to typos, mangled types, and more:</p>
<ul>
<li><code>enable_cfg(cfg_name)</code></li>
<li><code>enable_feature(feature_name)</code></li>
<li><code>set_cfg_value(cfg_name, cfg_value)</code></li>
<li><code>rebuild_if_env_changed(env_var_name)</code></li>
<li><code>rebuild_if_path_changed(path)</code></li>
</ul>
<p>New to the 0.2.0 rsconf release (as hinted at above) are variations on <code>enable_cfg()</code> and <code>set_cfg_value()</code>, necessitated by changes to the rust compiler <a href="https://blog.rust-lang.org/2024/05/06/check-cfg.html" rel="follow">that will land in 1.80</a>.<sup id="rf3-5136"><a href="https://neosmart.net/blog/using-build-rs-to-integrate-rust-applications-with-system-libraries-like-a-pro/#fn3-5136" title="You can also refer to this rustc documentation page for more on how this new feature works." rel="footnote">3</a></sup> The compiler will begin checking expressions such as <code>#[cfg(foo)]</code>, <code>#[cfg(foo = "bar")]</code>, and <code>#[cfg(feature = "baz")]</code>, to make sure that all of <code>foo</code>, <code>bar</code>, and <code>baz</code> are valid constraints (not typos or hallucinations). As you can imagine, if the compiler comes across the attribute <code>cfg!(hello)</code> while the <code>hello</code> cfg is enabled, it knows that it&#8217;s a valid cfg name. But what about when it&#8217;s <em>not</em> enabled? So now we have to let rustc know up front not only which cfg or feature names/values are valid and active, we also have to let it know which are valid even when they&#8217;re <em>in</em>active. To that end, rsconf 0.2.0 introduces the following:</p>
<ul>
<li><code>declare_cfg(cfg_name: &amp;str, enabled: bool)</code></li>
<li><code>declare_cfg_values(cfg_name: &amp;str, valid_values: &amp;[&amp;str])</code></li>
<li><code>declare_feature(feature: &amp;str, enabled: bool)</code></li>
</ul>
<p>The first can be used directly in lieu of the old <code>enable_cfg()</code> to both declare a cfg and specify that it is to be enabled (or disabled), but, for now, the second needs to be used in conjunction with the existing <code>set_cfg_value(cfg_name, value)</code> to let the compiler know in advance all the valid values, regardless of whether they&#8217;re defined for the current compilation or not.<sup id="rf4-5136"><a href="https://neosmart.net/blog/using-build-rs-to-integrate-rust-applications-with-system-libraries-like-a-pro/#fn4-5136" title="A separate &ldquo;builder-style&rdquo; api will be added at some point, but there are decisions to make about its shape. For example, it could look like add_cfg(&quot;cfg_name&quot;).with_values(&amp;[&quot;value1&quot;, &quot;value2&quot;]).set_value(&quot;value1&quot;) or along the lines of add_cfg(&quot;cfg_name&quot;).set_values(&quot;active_value&quot;, &amp;[&quot;other value 1&quot;, &quot;other value 2&quot;])" rel="footnote">4</a></sup></p>
<p>If you&#8217;re curious, you can take a quick look at fish-shell&#8217;s current <code>build.rs</code> to get an idea of what <a href="https://github.com/fish-shell/fish-shell/blob/a19ff4989a1322cde2feed7567d1bc06d26f75ee/build.rs" rel="nofollow">real-world <code>rsconf</code> usage</a> in the wild looks like. It&#8217;s a short build script, and quite easy to understand.</p>
<p>The rsconf crate itself is still in its early days and will, DV, continue to evolve and see new features. If there are build-related tests or tasks that you feel would fall under its purview, do open an issue <a href="https://github.com/mqudsi/rsconf" rel="nofollow">in the repository</a> and let us know. Feedback about the proposed builder api for declaring cfg values is also welcome! Otherwise, please give it a try and see if it can help you make your <code>build.rs</code> sane and easier to understand. It&#8217;s intentionally written to be fast and lite, with only a dependency on the <code>cc-rs</code> crate (which you&#8217;ll almost certainly already be taking a dependency on if you&#8217;re compiling against system libraries or headers), so you only stand to benefit from making the switch!</p>
<p><strong><em>Looking for something else to read or learn? Take a look at <a href="https://neosmart.net/blog/?s=rust" rel="follow">my other rust posts</a>, especially this one about <a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/" rel="follow">designing truly safe semaphores</a> in rust or learn about <a href="https://neosmart.net/blog/using-simd-acceleration-in-rust-to-create-the-worlds-fastest-tac/" rel="follow">using simd to speed up rust applications</a> significantly. Sign up below to get emails about open source rust stuff or add my blog to your RSS reader!</em></strong></p>
<div class="sendy_widget" style='margin-bottom: 0.5em;'>
<p><em>If you would like to receive a notification the next time we release a rust library, publish a crate, or post some rust-related developer articles, you can subscribe below. Note that you'll only get notifications relevant to rust programming and development by NeoSmart Technologies. If you want to receive email updates for all NeoSmart Technologies posts and releases, please sign up in the sidebar to the right instead.</em></p>
<iframe tabIndex=-1 onfocus="sendy_no_focus" src="https://neosmart.net/sendy/subscription?f=BUopX8f2VyLSOb892VIx6W4IUNylMrro5AN6cExmwnoKFQPz9892VSk4Que8yv892RnQgL&title=Join+the+rust+mailing+list" style="height: 300px; width: 100%;"></iframe>
</div>
<script type="text/javascript">function sendy_no_focus(e) { e.preventDefault(); }</script>
<hr class="footnotes"><ol class="footnotes"><li id="fn1-5136"><p>Unfortunately, as a part of the transition from C++ to rust, we have currently lost support for the vast majority of the legacy OSes and other non-tier-1 unix platforms we used to support due to issues with availability of the rust toolchain and incompatible dependencies like the <code>libc</code> crate, but we are keen to get them back.&nbsp;<a href="#rf1-5136" class="backlink" title="Jump back to footnote 1 in the text.">&#8617;</a></p></li><li id="fn2-5136"><p>One thing I really appreciate about Cargo is that it goes to great lengths to support cross-compilation out-of-the-box, clarifying operations and configurations taken from/applying to the machine you are building <em>on</em> (the host) vs the machine you are building <em>for</em> (the target). rsconf is written in a way that similarly respects this divide.&nbsp;<a href="#rf2-5136" class="backlink" title="Jump back to footnote 2 in the text.">&#8617;</a></p></li><li id="fn3-5136"><p>You can also refer to <a href="https://doc.rust-lang.org/nightly/rustc/check-cfg.html" rel="follow">this <code>rustc</code> documentation page</a> for more on how this new feature works.&nbsp;<a href="#rf3-5136" class="backlink" title="Jump back to footnote 3 in the text.">&#8617;</a></p></li><li id="fn4-5136"><p>A separate &#8220;builder-style&#8221; api will be added at some point, but there are decisions to make about its shape. For example, it could look like <code>add_cfg("cfg_name").with_values(&amp;["value1", "value2"]).set_value("value1")</code> or along the lines of <code>add_cfg("cfg_name").set_values("active_value", &amp;["other value 1", "other value 2"])</code>&nbsp;<a href="#rf4-5136" class="backlink" title="Jump back to footnote 4 in the text.">&#8617;</a></p></li></ol>The post <a href="https://neosmart.net/blog/using-build-rs-to-integrate-rust-applications-with-system-libraries-like-a-pro/">Using <code>build.rs</code> to integrate rust applications with system libraries like a pro</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/using-build-rs-to-integrate-rust-applications-with-system-libraries-like-a-pro/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5136</post-id>	</item>
		<item>
		<title>Embed only the video from another post on X or Twitter</title>
		<link>https://neosmart.net/blog/embed-only-the-video-from-another-post-on-x-or-twitter/</link>
					<comments>https://neosmart.net/blog/embed-only-the-video-from-another-post-on-x-or-twitter/#comments</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Sun, 22 Oct 2023 17:33:56 +0000</pubDate>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[ios]]></category>
		<category><![CDATA[twitter]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=5104</guid>

					<description><![CDATA[<p>Twitter has a new-ish feature that lets you embed only the video from another post or tweet in a post/tweet of your own (without quote-replying the source tweet itself). Only the video is then embedded in your post, and a &#8230; <a href="https://neosmart.net/blog/embed-only-the-video-from-another-post-on-x-or-twitter/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/embed-only-the-video-from-another-post-on-x-or-twitter/">Embed only the video from another post on X or Twitter</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<p>Twitter has a new-ish feature that lets you embed only the video from another post or tweet in a post/tweet of your own (without quote-replying the source tweet itself). Only the video is then embedded in your post, and a small attribution appears at the bottom identifying where the video came from:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-5106 colorbox-5104" src="https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-embedded-video-600x557.png" alt="" width="600" height="557" srcset="https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-embedded-video-600x557.png 600w, https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-embedded-video-1024x951.png 1024w, https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-embedded-video-323x300.png 323w, https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-embedded-video.png 1251w" sizes="auto, (max-width: 600px) 100vw, 600px" /></p>
<p>In the screenshot above, Sarah is sharing a video that was originally shared by Luc, but she&#8217;s not embedding/quoting Luc&#8217;s tweet itself – only the video. This post will cover how to do that yourself, both on the desktop/web and in the iOS Twitter app on iPhone.</p>
<p><span id="more-5104"></span></p>
<p>All of Twitter&#8217;s features are really just special-cased handling of URLs, and video embedding is no different. If you want to quote-reply, you are actually just posting something followed by the URL of the original tweet you want to quote. For example,</p>
<pre>Look at the size of this crowd!
https://twitter.com/LucAuffret/status/1716085946016252251</pre>
<p>ends up with the following quote reply:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-5107 colorbox-5104" src="https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-quote-reply-600x508.png" alt="" width="600" height="508" srcset="https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-quote-reply-600x508.png 600w, https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-quote-reply-1024x867.png 1024w, https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-quote-reply-354x300.png 354w, https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-quote-reply.png 1251w" sizes="auto, (max-width: 600px) 100vw, 600px" /></p>
<p>And similarly, embedding just the video from a tweet is as simple as appending <code>/video/1</code> to the URL of the source tweet. In this case:</p>
<pre>Look at the size of that crowd! #LibérezPalestine
https://twitter.com/LucAuffret/status/1716085946016252251<strong>/video/1</strong></pre>
<p>becomes</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-5108 colorbox-5104" src="https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-video-embedded-600x400.png" alt="" width="600" height="400" srcset="https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-video-embedded-600x400.png 600w, https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-video-embedded-1024x682.png 1024w, https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-video-embedded-451x300.png 451w, https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-video-embedded.png 1251w" sizes="auto, (max-width: 600px) 100vw, 600px" /></p>
<p>On iOS, in the Twitter/X app, this is all done for you automatically. If you just long-press on a video, you can use the &#8220;Post Video&#8221; option in the menu that pops up to have twitter copy-and-paste the full tweet URL with the <code>/video/1</code> already appended for you:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-5109 colorbox-5104" src="https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-iOS-embed-video-600x514.jpeg" alt="" width="600" height="514" srcset="https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-iOS-embed-video-600x514.jpeg 600w, https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-iOS-embed-video-1024x877.jpeg 1024w, https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-iOS-embed-video-350x300.jpeg 350w, https://neosmart.net/blog/wp-content/uploads/2023/10/twitter-iOS-embed-video.jpeg 1170w" sizes="auto, (max-width: 600px) 100vw, 600px" /></p>
<p>If the source tweet/post contains more than one video, you can change the <code>1</code> in <code>/video/1</code> to a different number in order to embed a video other than the first one in the post.</p>
<p><strong>Liked this post? Follow me on twitter <a href="https://twitter.com/mqudsi" rel="follow">@mqudsi</a> or subscribe to new posts via email from the sidebar to the right!</strong></p>The post <a href="https://neosmart.net/blog/embed-only-the-video-from-another-post-on-x-or-twitter/">Embed only the video from another post on X or Twitter</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/embed-only-the-video-from-another-post-on-x-or-twitter/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5104</post-id>	</item>
		<item>
		<title>Increment only numbers matching regex in Vim</title>
		<link>https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/</link>
					<comments>https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/#comments</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Fri, 13 Oct 2023 17:47:14 +0000</pubDate>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[hacks]]></category>
		<category><![CDATA[productivity]]></category>
		<category><![CDATA[vim]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=5071</guid>

					<description><![CDATA[<p>Long-time vim or neovim users are probably already aware that visually selecting a block of text then pressing CTRL + A in vim will result in any numbers in the selected block of text being incremented by 1. This works &#8230; <a href="https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/">Increment only numbers matching regex in Vim</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<p>Long-time vim or neovim users are probably already aware that visually selecting a block of text then pressing <kbd>CTRL</kbd> + <kbd>A</kbd> in vim will result in any numbers in the selected block of text being incremented by 1. This works even if the block contains non-numeric text: each group of digits gets treated as a number and is incremented.<sup id="rf1-5071"><a href="https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/#fn1-5071" title="To be pedantic, only the first group-of-digits/number on each line gets incremented; like many vim commands this only works on the first match per line of text unless some sort /g global modifier is used." rel="footnote">1</a></sup></p>
<p>For example, here&#8217;s a video that shows what happens when you select some text in vim and then use <kbd>CTRL</kbd> + <kbd>A</kbd> to increment the values:</p>
<p><!--[video width="1317" height="608" webm="https://neosmart.net/blog/wp-content/uploads/2023/10/vim-increment-numbers.webm" mp4="https://neosmart.net/blog/wp-content/uploads/2023/10/vim-regular-increment.mp4"][/video]--></p>
<p><video controls playsinline><source src="https://neosmart.net/blog/wp-content/uploads/2023/10/vim-increment-numbers.webm" type="video/webm" /><source src="https://neosmart.net/blog/wp-content/uploads/2023/10/vim-regular-increment.mp4" type="video/mp4" /></video></p>
<p><span id="more-5071"></span></p>
<p>(It&#8217;s also a fact that a lot of vim users learned about this functionality the terribly hard way: accidentally pressing <kbd>CTRL</kbd> + <kbd>A</kbd> then later realizing that all the numbers in their document were off-by-one for some unknown reason.)</p>
<p>But in all honesty, this isn&#8217;t a very useful mapping because it&#8217;s rare (at least in the programming world) to have numeric and text content completely separate: you usually have numbers in certain, key places while also possibly having numbers intermixed with the remainder of the text in the document. And we&#8217;ll often need to only increment certain numeric values but not the rest.</p>
<p>Here&#8217;s how you can use increment only numbers matching against a regular expression (including multiple numbers on the same line) while leaving the rest intact:</p>
<ol>
<li>Write a regex that matches against only the numbers you want to change. By doing this in normal mode, we can get vim to highlight the matches as we edit the regular expression, allowing us to visually confirm that the regex matches the numbers we want to increment. To do this, just use our trusty, old friend <code>:s/foo</code> (you can match against numeric content by using <code>\d\+</code> to select all consecutive digits)</li>
<li>For the second half of the <code>/s/foo/bar/</code> expression (<code>bar</code>, the replacement value), we&#8217;ll use the magic of a vim expression to increment (or otherwise manipulate) the matching value. Remember that regex capture groups are made with <code>(match here)</code>, match group 0 is the entirety of the match, and our manually captured groups (via the parentheses) are then counted from left-to-right from number 1 onward. The magic bits are <code>\=submatch(n)+1</code> which replaces the <em>n</em>th match group with the incremented value.</li>
</ol>
<p>Here&#8217;s an example where we want to insert some text in the middle of a numbered/indexed structured body of text then update all the indexes afterwards by bumping them up by one:</p>
<p>We have some subtitles in the SRT format and we want to insert a new caption in the middle, then update all the caption numbers <em>but not the timestamps</em> to reflect the insertion in the middle of the list. We have this text to start with:</p>
<pre>1
00:00:03,400 --&gt; 00:00:06,177
In this episode, we'll be talking about
the importance of strong typing in programming.

2
00:00:010,000 --&gt; 00:00:11,200
Strongly-typed languages have many benefits over
their loosely-typed counterparts.

3
00:00:11,500 --&gt; 00:00:13,655
Using strongly-typed languages can actually make
you more productive.</pre>
<p>And we want to insert the following subtitles between <code>1</code> and <code>2</code>, but not have to increment all the indexes that come after one-by-one by hand, which is time-consuming, error-prone, and a chore:</p>
<pre>00:00:06,600 --&gt; 00:00:09,220
Hang on to your hats because this is going to be fun!</pre>
<p>We&#8217;ll do this by pasting the text where we want it to go, selecting the <em>remainder</em> of the text (where we need to increment the subtitle index number), and then using the vim expression <code>:s/^\d\+$/\=submatch(0)+1/g</code> to match a line that contains only numeric content (so it&#8217;ll match the subtitle index number but not the timestamps, which we absolutely <em>don&apos;t</em> want to inadvertently increment in the process):</p>
<p><!--[video width="1233" height="1067" webm="https://neosmart.net/blog/wp-content/uploads/2023/10/vim-smart-increment-numbers-demo.webm" mp4="https://neosmart.net/blog/wp-content/uploads/2023/10/vim-regex-increment.mp4"][/video]--></p>
<p><video controls playsinline><source src="https://neosmart.net/blog/wp-content/uploads/2023/10/vim-smart-increment-numbers-demo.webm" type="video/webm" /><source src="https://neosmart.net/blog/wp-content/uploads/2023/10/vim-regex-increment.mp4" type="video/mp4" /></video></p>
<p>As you can see, it&#8217;s simply a matter of selecting the text you want to replace in (in our case, everything past the captions we just entered) and then coming up with a regex that matches only the numbers we want to replace but not the numbers we don&#8217;t. If we had used <kbd>CTRL</kbd> + <kbd>A</kbd> here instead, we would have ended up with the first timestamp in each caption in our selection incremented, in addition to incrementing the index.</p>
<p>I think the syntax for this one is easy enough to remember that you probably don&#8217;t need a plugin or a custom key mapping to do this for you. The trickiest part is just the regex, and odds are in most cases judicious application of <code>^</code> (start of line), <code>$</code> (end of line), and whitespace will probably suffice to get you a regex that matches only the values you need. Unlike some other vim expressions that have really inscrutable names or incantations, using <code>\=submatch(0)</code> (or <code>\=submatch(4)</code> or whatever) a few times is probably all it will take for you to memorize the syntax and soon enough it&#8217;ll be second nature.</p>
<p>If you enjoyed this tip, consider subscribing to blog posts via RSS or via email from the sidebar to the right and follow me <a href="https://twitter.com/mqudsi" rel="follow">on twitter<strong> @mqudsi</strong></a> for more fun hacking or programming stuff! (If you&#8217;re an emacs user, it&#8217;s highly unlikely I&#8217;ll have any text editing hacks for you at any time, unfortunately!)</p>
<hr class="footnotes"><ol class="footnotes"><li id="fn1-5071"><p>To be pedantic, only the first group-of-digits/number on each line gets incremented; like many vim commands this only works on the first match per line of text unless some sort <code>/g</code> global modifier is used.&nbsp;<a href="#rf1-5071" class="backlink" title="Jump back to footnote 1 in the text.">&#8617;</a></p></li></ol>The post <a href="https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/">Increment only numbers matching regex in Vim</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/increment-only-numbers-matching-regex-in-vim/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		<enclosure length="115664" type="video/webm" url="https://neosmart.net/blog/wp-content/uploads/2023/10/vim-increment-numbers.webm"/>
<enclosure length="500294" type="video/webm" url="https://neosmart.net/blog/wp-content/uploads/2023/10/vim-smart-increment-numbers-demo.webm"/>
<enclosure length="609160" type="video/mp4" url="https://neosmart.net/blog/wp-content/uploads/2023/10/vim-regular-increment.mp4"/>
<enclosure length="846424" type="video/mp4" url="https://neosmart.net/blog/wp-content/uploads/2023/10/vim-regex-increment.mp4"/>

		<post-id xmlns="com-wordpress:feed-additions:1">5071</post-id>	</item>
		<item>
		<title>tcpproxy 0.4 released</title>
		<link>https://neosmart.net/blog/tcpproxy-0-4-released/</link>
					<comments>https://neosmart.net/blog/tcpproxy-0-4-released/#respond</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Sun, 08 Oct 2023 18:53:36 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[open source]]></category>
		<category><![CDATA[rust]]></category>
		<category><![CDATA[tcpproxy]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=5060</guid>

					<description><![CDATA[<p>This blog post was a bit delayed in the pipeline, but a new release of tcproxy, our educational async (tokio) rust command line proxy project, is now available for download (precompiled binaries or install via cargo). I was actually surprised &#8230; <a href="https://neosmart.net/blog/tcpproxy-0-4-released/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/tcpproxy-0-4-released/">tcpproxy 0.4 released</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<div id="attachment_5065" style="width: 610px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-5065" class="wp-image-5065 size-medium colorbox-5060" src="https://neosmart.net/blog/wp-content/uploads/2023/10/Programming-with-Rust-600x363.jpg" alt="" width="600" height="363" srcset="https://neosmart.net/blog/wp-content/uploads/2023/10/Programming-with-Rust-600x363.jpg 600w, https://neosmart.net/blog/wp-content/uploads/2023/10/Programming-with-Rust-496x300.jpg 496w, https://neosmart.net/blog/wp-content/uploads/2023/10/Programming-with-Rust.jpg 800w" sizes="auto, (max-width: 600px) 100vw, 600px" /><p id="caption-attachment-5065" class="wp-caption-text">Image courtesy of Hack A Day</p></div>
<p>This blog post was a bit delayed in the pipeline, but a new release of <a href="https://github.com/mqudsi/tcpproxy" rel="nofollow"><code>tcproxy</code></a>, our educational async (tokio) rust command line proxy project, is now available for download (<a href="https://github.com/mqudsi/tcpproxy/releases/tag/0.4.0" rel="nofollow">precompiled binaries</a> or <a href="https://crates.io/crates/tcpproxy" rel="nofollow">install via <code>cargo</code></a>).</p>
<p>I was actually surprised to find that we haven&#8217;t written about tcpproxy before (you can see our other rust-related posts <a href="https://neosmart.net/blog/tag/rust/" rel="follow">here</a>), but it&#8217;s a command line tcp proxy &#8220;server&#8221; written with two purposes in mind: a) serving as a real-world example of an async (tokio-based) rust networking project, and b) serving as a minimal but-still-useful tcp proxy you can run and use directly from the command line, without needing complex installation or configuration procedures. (You can think of it as being like <a href="https://en.wikipedia.org/wiki/Minix" rel="follow">Minix</a>, but for rust and async networking.)</p>
<p>The tcpproxy project has been around for quite some time, originally published in 2017 before rust&#8217;s <code>async</code> support was even stabilized. At the time, it manually chained futures to achieve scalability without relying on the thread-per-connection model – but today its codebase is a lot easier to follow and understand thanks to rust&#8217;s first-class async/await support.</p>
<p><span id="more-5060"></span></p>
<p>That doesn&#8217;t mean that there aren&#8217;t &#8220;gotchas&#8221; that rust devs need to be aware of when developing long-lived async-powered applications, and tcpproxy&#8217;s purpose here is to serve as a real-world illustration of the correct way to handle some of the thornier issues such as tying the lifetime of various connections (or halves of connections) to one-another and aborting all remaining tasks when the first terminates (without blocking or polling).</p>
<p><a href="https://github.com/mqudsi/tcpproxy/releases/tag/0.4.0" rel="nofollow">The 0.4.0 release</a> doesn&#8217;t contain any major changes but tweaks a number of things to improve both the usability of the application and to model the correct way of handling a few things (such as not using an <code>Arc&lt;T&gt;</code> to share state that remains alive (and static) for the duration of the program&#8217;s execution<sup id="rf1-5060"><a href="https://neosmart.net/blog/tcpproxy-0-4-released/#fn1-5060" title="In cases like this, the recommendation is to actually just leak the memory instead to reduce cache coherency traffic in the MESI or MOESI protocols that is caused when each new task increments or decrements the shared reference count bits in the Arc&lt;T&gt;. If you know the value is going to live until the end of the application&rsquo;s lifetime anyway, there&rsquo;s no need to incur that cost and any future (read-only) access to the shared variable from any thread on any core will be ~free." rel="footnote">1</a></sup>)</p>
<p>One of the user-visible changes in this release is that <code>ECONNRESET</code> and <code>ECONNABORT</code> are no longer treated as exceptional, meaning that tcpproxy proceeds as if the connection in question were closed normally and uneventfully. While a compliant TCP client shouldn&#8217;t just abort a tcp connection (and a server shouldn&#8217;t reset one), these things happen quite often in the real world, and since all tcpproxy connections are stateless, there&#8217;s really no reason to handle these any differently than a normal, compliant tcp connection tear-down. Since we <em>don&#8217;t</em> report a connection error in these cases, tcpproxy prints (when executed in debug <code>-d</code> mode, that is) the normal messages about the number of bytes proxied in each direction, <a href="https://github.com/mqudsi/tcpproxy/issues/3" rel="nofollow">hopefully leading to less confusion</a>.</p>
<p>For those of you hearing about the tcpproxy project for the first time, I invite you to look over <a href="https://github.com/mqudsi/tcpproxy/blob/99f64a3b3d7509ca77fbfa5e9cade48d72202166/src/main.rs#L75" rel="nofollow">the core event loop</a> which remains fairly small even when correctly handling all the cases we need to account for and synchronizing lifetimes the way we like. If you spot something that&#8217;s wrong, not quite right, or could be done in a more idiomatic way, please do leave a comment, send an email, or open an issue &#8211; tcpproxy is an open source project and it takes a village to raise and nurture even the smallest of projects to a healthy state!</p>
<p><strong>You can follow me on twitter <a href="https://twitter.com/mqudsi" rel="follow">@mqudsi</a> or sign up below for our rust-only mailing list to receive a heads-up when new rust educational content or rust open source crates are released. If you&#8217;re in a position to do so, I am also experimenting with accepting sponsors <a href="https://www.patreon.com/mqudsi" rel="follow">on my Patreon page</a> and would greatly appreciate your patronage and support!</strong></p>
<div class="sendy_widget" style='margin-bottom: 0.5em;'>
<p><em>If you would like to receive a notification the next time we release a rust library, publish a crate, or post some rust-related developer articles, you can subscribe below. Note that you'll only get notifications relevant to rust programming and development by NeoSmart Technologies. If you want to receive email updates for all NeoSmart Technologies posts and releases, please sign up in the sidebar to the right instead.</em></p>
<iframe tabIndex=-1 onfocus="sendy_no_focus" src="https://neosmart.net/sendy/subscription?f=BUopX8f2VyLSOb892VIx6W4IUNylMrro5AN6cExmwnoKFQPz9892VSk4Que8yv892RnQgL&title=Join+the+rust+mailing+list" style="height: 300px; width: 100%;"></iframe>
</div>
<script type="text/javascript">function sendy_no_focus(e) { e.preventDefault(); }</script>
<hr class="footnotes"><ol class="footnotes"><li id="fn1-5060"><p>In cases like this, the recommendation is to actually just leak the memory instead to reduce cache coherency traffic in the MESI or MOESI protocols that is caused when each new task increments or decrements the shared reference count bits in the <code>Arc&lt;T&gt;</code>. If you know the value is going to live until the end of the application&#8217;s lifetime anyway, there&#8217;s no need to incur that cost and any future (read-only) access to the shared variable from any thread on any core will be ~free.&nbsp;<a href="#rf1-5060" class="backlink" title="Jump back to footnote 1 in the text.">&#8617;</a></p></li></ol>The post <a href="https://neosmart.net/blog/tcpproxy-0-4-released/">tcpproxy 0.4 released</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/tcpproxy-0-4-released/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5060</post-id>	</item>
		<item>
		<title>CallerArgumentExpression and extension methods don’t mix</title>
		<link>https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/</link>
					<comments>https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/#respond</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Mon, 11 Sep 2023 17:17:55 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[.net]]></category>
		<category><![CDATA[c#]]></category>
		<category><![CDATA[callerargumentexpression]]></category>
		<category><![CDATA[dot net]]></category>
		<category><![CDATA[dotnet]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[stringification]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=5037</guid>

					<description><![CDATA[<p>This post is for the C# developers out there and takes a look at the interesting conjunction of [CallerArgumentExpression] and static extension methods – a mix that at first seems too convenient to pass up. A quick recap: [CallerArgumentExpression] landed &#8230; <a href="https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/">CallerArgumentExpression and extension methods don’t mix</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<p><img loading="lazy" decoding="async" class="alignright size-thumbnail wp-image-5053 colorbox-5037" src="https://neosmart.net/blog/wp-content/uploads/2023/09/c-sharp-150x150.jpg" alt="" width="150" height="150" />This post is for the C# developers out there and takes a look at the interesting conjunction of <code>[CallerArgumentExpression]</code> and static extension methods – a mix that at first seems too convenient to pass up.</p>
<p>A quick recap: <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/caller-argument-expression" rel="follow"><code>[CallerArgumentExpression]</code> landed</a> as part of the C# 10.0 language update and helps to reduce the (often brittle!) boilerplate involved in, among other uses, creating useful error messages capturing the names of variables or the text of expressions. You tag an optional <code>string</code> method parameter with <code>[CallerArgumentExpression("argName")]</code> where <code>argName</code> is the name of the method argument you want stringified, and the compiler does the rest.</p>
<p><span id="more-5037"></span></p>
<p>Here&#8217;s a quick demo of how <code>[CallerArgumentExpression]</code> works:</p>
<pre><code class="language-csharp">using System;
using System.Runtime.CompilerServices;

public class Program
{
    static string Stringify(object obj,
        [CallerArgumentExpression("obj")] string expr = "")
    {
        return expr;
    }

    public static class Foo
    {
        public string Bar = "bar";
    }

    public static void Main()
    {
        var expr = Stringify(Foo.Bar);
        Console.WriteLine(expr); // prints "Foo.Bar"
        expr = Stringify(Foo.Bar + Foo.Bar);
        Console.WriteLine(expr); // prints "Foo.Bar + Foo.Bar"
    }
}
</code></pre>
<p>And you can try it online yourself <a href="https://dotnetfiddle.net/BQPJCs" rel="nofollow">in this .NET Fiddle</a>.</p>
<p>It&#8217;s really cool and it opens the door to a lot of possibilities (though I&#8217;m still stuck trying to figure some of them out, such as reliably setting/clearing model binding errors that involve array expressions).</p>
<p>As mentioned, this shipped with C# 10. And of course, C# 8 shipped &#8220;the big one:&#8221; <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/nullable-reference-types" rel="follow">nullable reference types</a>. Since then, the following pattern has become familiar in many a codebase while devs figure out where variables actually can or can&#8217;t be null:</p>
<pre><code class="language-csharp">using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

static class Extensions
{
    public static T ThrowIfNull&lt;T&gt;([NotNull] T? value, string expr)
    {
        if (value is null) {
            throw new ArgumentNullException(expr);
        }
        return value;
    }
}
</code></pre>
<p>This does exactly what you think it does: it verifies that a value isn&#8217;t null or throws an exception if it is. And it lets the compiler know that downstream of this call, the passed-in value is non-<code>null</code>. To make it useful, it&#8217;s common enough to extend it with more caller attribute magic:</p>
<pre><code class="language-csharp">using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

static class Extensions
{
    public static T ThrowIfNull&lt;T&gt;(
        [NotNull] T? value,
        string expr,
        [CallerMemberName] string callerName = "",
        [CallerFilePath] string filePath = "",
        [CallerLineNumber] int lineNumber = 0)
    {
        if (value is null) {
            throw new InvalidOperationException(
                $"{expr} unexpectedly null in {callerName} "
                + $"at {filePath}:{lineNumber}");
        }
        return value;
    }
}
</code></pre>
<p>Now we get useful exceptions that we&#8217;ll hopefully log and revisit to help us find any places in our codebase where we are assuming a value can&#8217;t be <code>null</code> but it turns out that, in fact, it can be.</p>
<p>But what about if we try to add our new best buddy <code>[CallerArgumentExpression]</code> here, to get rid of the need to manually specify the text of the argument via <code>argName</code> in our <code>ThrowIfNull()</code>?</p>
<pre><code class="language-csharp">using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

static class Extensions
{
    public static T ThrowIfNull&lt;T&gt;(
        [NotNull] T? value,
        [CallerArgumentExpression("value")] string expr = "",
        [CallerMemberName] string callerName = "",
        [CallerFilePath] string filePath = "",
        [CallerLineNumber] int lineNumber = 0)
    {
        if (value is null) {
            throw new InvalidOperationException(
                $"{expr} unexpectedly null in {callerName} "
                + $" at {filePath}:{lineNumber}");
        }
        return value;
    }
}
</code></pre>
<p>At first blush, this works great. Use it with a single variable directly, as in <code>foo.ThrowIfNull()</code>, and everything will work swimmingly and it&#8217;ll do exactly what it says on the tin. But try using it in a more-complicated setting, say like <code>foo?.bar?.ThrowIfNull()</code>, and you&#8217;ll see what I mean: here, <code>argName</code> will only capture the last token in the chain and you&#8217;ll see that <code>argName</code> is only <code>bar</code> and not <code>foo.bar</code>!</p>
<p>It&#8217;s actually not particularly surprising behavior. Even without knowing what Roslyn desugars the above code to, you could logically think of it as being an expression (conditionally) invoked on/with the final variable <code>bar</code> itself – after all, <code>T</code> here would have been <code>bar.GetType()</code>, so it&#8217;s not a huge stretch of the imagination to guess that <code>expr</code> might only span <code>bar</code> as well.<sup id="rf1-5037"><a href="https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/#fn1-5037" title="Except expr is actually not bar but rather .bar!" rel="footnote">1</a></sup></p>
<p>Indeed, when you look at what the code compiles to, you&#8217;ll see why. For the following code fragment:</p>
<pre><code class="language-csharp">public class Foo {
    public string? Bar;
}

public class C {
    public void M(Foo? foo) {
        foo?.Bar.ThrowIfNull();
    }
}
</code></pre>
<p><a href="https://sharplab.io/#v2:EYLgZgpghgLgrgJwgZwLQGED2AbbEDGMAlpgHYAyRMECU2yAPgAIBMAjALABQTADAARM2AOgAiRKAHNSmZMXzJhWACYQAgqToBPZEWQBubn0EiASnFLEAthCWYrAByJ4EAZRoA3IvhSGuRgGZBFn4AMUxMfgBvbn44wSChXgB+fgAhKAQ/AF9uQOD+dGjY+KZEgBZ+AFkACnDMVLAIgEpirniO/iaG4QyEYQAVAAsETAB3AEkwADk4XBrmvw7crhX8oQA2AoBRAA9qUl0yZDaOgG0mAHYQfmnMGFncAF0SuLKTLYH+YdHJmbnsAAeAYAPhqZzuDwBT34MCGem+qQ8dDgEAANPwzug6C41AhJHAbJY9g4kMgjqQagAiZHYVFU5owpKpCC7Un8AC8/FIANaMXanTiRDA/BqtNR/ARPNwfNegvicN+3IgY34eIJRKhuD2PgcxDINVZpMWcuWptKl344ogS3iK2yQA==" rel="nofollow">We get</a></p>
<pre><code class="language-csharp">public class Foo
{
    [System.Runtime.CompilerServices.Nullable(2)]
    public string Bar;
}

public class C
{
    [System.Runtime.CompilerServices.NullableContext(2)]
    public void M(Foo foo)
    {
        if (foo != null)
        {
            Extensions.ThrowIfNull(foo.Bar, ".Bar");
        }
    }
}
</code></pre>
<p>Which, while still helpful, is not exactly what we want. Although as C# developers we are somewhat allergic to calling static helper utilities directly instead of cleverly turning them into their more ergonomic extension method counterparts, in this we don&#8217;t have any other choice.</p>
<p>When we change <code>ThrowIfNull()</code> from an extension method to a regular static method though, we get the result we <em>really</em> wanted:</p>
<pre><code class="language-csharp">public static class Utils
{
    public static T ThrowIfNull&lt;T&gt;(
        [NotNull] T? value,
        [CallerArgumentExpression("value")] string? expr = null) 
    {
        if (value is null) {
            throw new ArgumentNullException(expr);
        }
        return value;
    }
}

public class Foo
{
    public string? Bar;
}

public class C
{
    public void M(Foo? foo)
    {
        Utils.ThrowIfNull(foo?.Bar);
    }
}
</code></pre>
<p><a href="https://sharplab.io/#v2:EYLgZgpghgLgrgJwgZwLQGED2AbbEDGMAlpgHYAyRMECU2yAPgAIBMAjALABQTADAARM2AOgAiRKAHNSmZMXzJhWACYQAgqToBPZEWQBubn0EiASnFLEAthCWYrAByJ4EAZRoA3IvhSGuRgGYTADZBFn4AVWJ6fgBvbn5E/gBtJgB2EH4AOUwYLLhcAF0EpKYgoVCAFX5KgAsETAB3AEkwfNwAHkqAPgAKZJy8guxCmoB+fg86OAgAGhT0Ohc1BEk4G0sAUQAPByRkXTJegCIp7BnjgEpRoV4JiF2EfgBeflJhy7iSpKSiMH5emcZvw9G8Pl8uD8oUkYPUmm8II1+Cs1hshrgdj4HMQjg89pc/NDEgBfb5Q9KTaYQQlJUlcOmBML8ABimEwEJ+ZRMd34ACEoAg/AyeOVwugOaVygAWfgAWV6rMwEzAbM+8UhRKizkUdQaLTaw16KqVwn5CAJZLpxKAA=" rel="nofollow">Desugaring to</a>:</p>
<pre><code class="language-csharp">public class C
{
    [System.Runtime.CompilerServices.NullableContext(2)]
    public void M(Foo foo)
    {
        Utils.ThrowIfNull((foo != null) ? foo.Bar : null, "foo?.Bar");
    }
}
</code></pre>
<p>Liked this post? Follow me on twitter <a href="https://twitter.com/mqudsi" rel="follow">@mqudsi</a> and like this tweet for more .NET awesomeness!</p>
<blockquote class="twitter-tweet" data-width="550" data-dnt="true">
<p lang="en" dir="ltr">Do you know how to effectively use [CallerArgumentExpression] to supercharge C# <a href="https://twitter.com/dotnet?ref_src=twsrc%5Etfw" rel="follow">@dotnet</a> codebase? Here&#39;s the number one mistake to look out for. <a href="https://t.co/sReLDSS3iw" rel="follow">https://t.co/sReLDSS3iw</a></p>
<p>&mdash; Mahmoud Al-Qudsi (@mqudsi) <a href="https://twitter.com/mqudsi/status/1701285293372965228?ref_src=twsrc%5Etfw" rel="follow">September 11, 2023</a></p></blockquote>
<p><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p>
<div class="sendy_widget" style='margin-bottom: 0.5em;'>
<p><em>If you would like to receive a notification the next time we release a nuget package for .NET or release resources for .NET Core and ASP.NET Core, you can subscribe below. Note that you'll only get notifications relevant to .NET programming and development by NeoSmart Technologies. If you want to receive email updates for all NeoSmart Technologies posts and releases, please sign up in the sidebar to the right instead.</em></p>
<iframe tabIndex=-1 onfocus="sendy_no_focus" src="https://neosmart.net/sendy/subscription?f=BUopX8f2VyLSOb892VIx6W4BB8V5K2ReYGLVwsfKUZLXCc892Ffz8rIgRyIGoE22cZVr&title=Join+the+dotnet+mailing+list" style="height: 300px; width: 100%;"></iframe>
</div>
<script type="text/javascript">function sendy_no_focus(e) { e.preventDefault(); }</script>
<hr class="footnotes"><ol class="footnotes"><li id="fn1-5037"><p>Except <code>expr</code> is actually not <code>bar</code> but rather <code>.bar</code>!&nbsp;<a href="#rf1-5037" class="backlink" title="Jump back to footnote 1 in the text.">&#8617;</a></p></li></ol>The post <a href="https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/">CallerArgumentExpression and extension methods don’t mix</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/callerargumentexpression-and-extension-methods-dont-mix/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5037</post-id>	</item>
		<item>
		<title>Implementing truly safe semaphores in rust</title>
		<link>https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/</link>
					<comments>https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#comments</comments>
		
		<dc:creator><![CDATA[Mahmoud Al-Qudsi]]></dc:creator>
		<pubDate>Mon, 03 Oct 2022 20:11:34 +0000</pubDate>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[concurrency]]></category>
		<category><![CDATA[multithreading]]></category>
		<category><![CDATA[open source]]></category>
		<category><![CDATA[release]]></category>
		<category><![CDATA[rust]]></category>
		<category><![CDATA[semaphore]]></category>
		<guid isPermaLink="false">https://neosmart.net/blog/?p=4958</guid>

					<description><![CDATA[<p>Discuss this article on r/rust or on Hacker News. Low-level or systems programming languages generally strive to provide libraries and interfaces that enable developers, boost productivity, enhance safety, provide resistance to misuse, and more &#8212; all while trying to reduce &#8230; <a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/">Continue reading <span class="meta-nav">&#8594;</span></a></p>
The post <a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/">Implementing truly safe semaphores in rust</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></description>
										<content:encoded><![CDATA[<p><em class="onload">Discuss this article <a href="https://old.reddit.com/r/rust/comments/xvm9ul/implementing_truly_safe_semaphores_in_rust_and/" rel="follow">on r/rust</a> or <a href="https://news.ycombinator.com/item?id=33084442" rel="follow">on Hacker News</a>.</em></p>
<p>Low-level or systems programming languages generally strive to provide libraries and interfaces that enable developers, boost productivity, enhance safety, provide resistance to misuse, and more &#8212; all while trying to reduce the runtime cost of such initiatives. Strong type systems turn runtime safety/sanity checks into compile-time errors, optimizing compilers try to reduce an enforced sequence of api calls into a single instruction, and library developers think up of clever hacks to even completely erase any trace of an abstraction from the resulting binaries. And as anyone that&#8217;s familiar with them can tell you, the rust programming language and its developers/community have truly embraced this ethos of zero-cost abstractions, perhaps more so than any others.</p>
<p>I&#8217;m not going to go into detail about what the rust language and standard library do to enable zero-cost abstractions or spend a lot of time going over some of the many examples of zero-cost interfaces available to rust programmers, though I&#8217;ll just quickly mention a few of my favorites: iterators and <a href="https://doc.rust-lang.org/nightly/std/iter/trait.Iterator.html" rel="follow">all the methods the <code>Iterator</code> trait exposes</a> have to be at the top of every list given the amount of black magic voodoo the compiler has to do to turn these into their loop-based equivalents, zero-sized types make developing embedded firmware in rust a dream and it&#8217;s really crazy to see how <a href="https://docs.rs/embedded-hal/latest/embedded_hal/" rel="nofollow">all the various peripheral abstractions</a> can be completely erased giving you small firmware blobs despite all the safety abstractions, and no list is complete the newest member of the team, <code>async</code>/<code>await</code> and how rust manages to turn an entire web server api into a single state machine and event loop. (And to think this can be used even on embedded without a relatively heavy async framework like tokio and with even zero allocations to boot!)</p>
<p><span id="more-4958"></span></p>
<p>But the tricky thing with abstractions is that the <em>relative</em> price you pay scales rather unfairly with the size of the interface you are abstracting over. While a byte here and a byte there may mean nothing when we&#8217;re talking framework-scale interfaces, when you are modeling smaller and finer-grained abstractions, every byte and every instruction begin to count.</p>
<p>A couple of weeks ago, we released an update to <a href="https://github.com/neosmart/rsevents" rel="nofollow"><code>rsevents</code></a>, our crate that contains a rusty cross-platform equivalent to WIN32 events for signaling between threads and writing your own synchronization primitives, and <a href="https://github.com/neosmart/rsevents-extra" rel="nofollow"><code>rsevents-extra</code></a> a companion crate that provides a few handy synchronization types built on top of the manual- and auto-reset events from the <code>rsevents</code> crate. Aside from the usual awesome helpings of performance improvements, ergonomics enhancements, and more, <strong>this latest version of <code>rsevents-extra</code> includes <a href="https://docs.rs/rsevents-extra/latest/rsevents_extra/struct.Semaphore.html" rel="nofollow">a <code>Semaphore</code> synchronization primitive</a></strong> &#8211; something that the rust standard library surprisingly lacks&#8230; but not without good reason.</p>
<h2>What makes a semaphore a semaphore?</h2>
<p>Semaphores are well-documented and fairly well-understood underpinnings of any concurrency library or framework and essential Computer Science knowledge. So why doesn&#8217;t the rust standard library have a semaphore type?</p>
<p>Unlike the synchronization types that the rust standard library currently provides (such as <code><a href="https://doc.rust-lang.org/nightly/std/sync/struct.Mutex.html">Mutex&lt;T&gt;</a></code> and <a href="https://doc.rust-lang.org/nightly/std/sync/struct.RwLock.html" rel="follow"><code>RwLock&lt;T&gt;</code></a>, a semaphore is somewhat harder to model as it doesn&#8217;t so much restrict concurrent access to a single object or variable so much as it does limit concurrency within a <em>region</em> of code.</p>
<p>Of course it can be argued that in traditional programming a semaphore is just a more general case of a mutex, and just like mutexes traditionally protected a region of code from concurrent access<sup id="rf1-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn1-4958" title="In fact, in some languages/apis a &ldquo;critical section&rdquo; is another name for a mutex." rel="footnote">1</a></sup> but were converted into synchronization primitives <em>owning</em> the data they protect and marshalling access to it, there&#8217;s no reason a rust semaphore couldn&#8217;t do the same. But therein lies the problem: a mutex and a read-write lock can both be understood in terms of <em>readers</em> and <em>writers</em>,<sup id="rf2-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn2-4958" title="For a mutex, they are one and the same as it mandates exclusive access to a region." rel="footnote">2</a></sup> a semaphore makes no such guarantees. And rust is quite fundamentally built on the concept of read ^ write: it <em>needs</em> to know if a thread/scope is reading or writing from a variable or memory location in order to uphold its most basic memory safety guarantee: there can either be multiple &#8220;live&#8221; read-only references to an object or a single write-enabled (<code>&amp;mut</code>) reference to the same &#8212; <strong>but a semaphore doesn&#8217;t make that distinction</strong>!</p>
<p>While a strictly binary semaphore (max concurrency == 1) can guarantee that there will never be multiple writers accessing a memory region, there&#8217;s not much theoretical benefit to such a binary semaphore over a mutex &#8211; in fact, they&#8217;re interchangeable. What makes a semaphore truly special is that it can be created (or even dynamically modified) with a concurrency limit <em>n</em> and then uphold its core precondition, guaranteeing that at any given time there will never be more than <em>n</em> threads/stacks<sup id="rf3-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn3-4958" title="Semaphores are generally non-reentrant so recursively obtaining a semaphore will count against its limit!" rel="footnote">3</a></sup> accessing a semaphore-protected region at any given time.</p>
<p>The problem is that with <em>n</em> &gt; 1, there&#8217;s no concept of a &#8220;privileged&#8221; owning thread and all threads that have &#8220;obtained&#8221; the semaphore do so equally. Therefore, a <em>rust</em> semaphore can only ever provide read-only (<code>&amp;T</code>) access to an underlying resource, limiting the usefulness of such a semaphore almost to the point of having no utility. As such, the only safe &#8220;owning&#8221; semaphore with read-write access that can exist in the rust world would be  <code>Semaphore&lt;()&gt;</code>,<sup id="rf4-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn4-4958" title="The empty parentheses/tuple () is rust-speak for void, for those of you not familiar with the language." rel="footnote">4</a></sup> or one that actually owns <em>no </em>data and can only be used for its side effect of limiting concurrency while the semaphore is &#8220;owned,&#8221; so to speak.<sup id="rf5-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn5-4958" title="In rust, there&rsquo;s actually (out of necessity) an entire class of&nbsp; types or values that can be changed even through read-only references, a concept known as interior mutability and exposed via types like AtomicXXX and the various std::cell Cell types &mdash; but those are generally to be used on a fine-grained level and in general you wouldn&rsquo;t be using them to make entire objects writeable via read-only references." rel="footnote">5</a></sup> (Actual mutation of accessed resources within the concurrency-limited region, if needed, would continue to be marshalled via <code>Mutex&lt;T&gt;</code> or <code>RwLock&lt;T&gt;</code> on a fine-grained level.)</p>
<p>Ok, so this explains why the rust standard library doesn&#8217;t contain a <code>Semaphore&lt;T&gt;</code> type to mirror <code>Mutex&lt;T&gt;</code> and its friends, but then what&#8217;s so hard about shipping a non-owning <code>std::sync::Semaphore</code> instead?</p>
<h2>Designing a safe <code>Semaphore</code> for rust</h2>
<p>To answer this, we need to look at what a semaphore API generally looks like in other languages. While the names and calling semantics differ, a semaphore is generally found as a type that provides the following, starting with the most fundamental to <em>de facto</em> properties:</p>
<ul>
<li>It is a type that can be used to limit concurrency to a resource or region of code, up to a dev-defined limit <em>n</em>.</li>
<li>It is a type that has a concept of &#8220;currently available concurrency,&#8221; which represents and tracks the remaining number of threads/stacks that can &#8220;obtain&#8221; the semaphore, thereby reducing its available concurrency and generally giving the calling thread access to the concurrency-limited region,</li>
<li>A semaphore can be created/declared with an &#8220;initially available concurrency&#8221; and a &#8220;maximum possible concurrency,&#8221; which may differ (indeed, &#8220;initially available concurrency&#8221; is often zero),</li>
<li>Semaphores don&#8217;t generally have a concept of ownership, meaning a thread (or <em>any</em> thread) can increment (up to the pre-defined limit) or decrement (down to zero) the available concurrency for a semaphore without having &#8220;obtained&#8221; or &#8220;created&#8221; it. (This is necessary otherwise it&#8217;d be impossible to initialize a semaphore with a lower initial concurrency limit than its maximum, because no thread could then increase it.)</li>
</ul>
<p>It&#8217;s the last of these points that makes a semaphore so tricky to model in any language that prides itself on safety. While a semaphore that acts strictly as a variable-occupancy mutex (i.e. initial concurrency equals the max concurrency and each time it is obtained, it must be subsequently released by the same thread that obtained it), that&#8217;s not generally a requirement that semaphores impose, and such a requirement would considerably limit the utility that a semaphore could offer.</p>
<p>Let&#8217;s look at some ways we might design such a semaphore in rust, some of which we actually tried while prototyping <code>rsevents_extra::Semaphore</code>.</p>
<p>Before anything else, let&#8217;s get the hard part out of the way by introducing you to <a href="https://docs.rs/rsevents/latest/rsevents/struct.AutoResetEvent.html" rel="nofollow"><code>rsevents::AutoResetEvent</code></a>, a one-byte<sup id="rf6-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn6-4958" title="Actually, the AutoResetEvent implementation only takes all of two bits, but let&rsquo;s just call it a byte to make everything nice and easy." rel="footnote">6</a></sup> synchronization primitive that takes care of putting threads to sleep when the event isn&#8217;t signalled/available and allowing one-and-only-one waiting thread to either consume the event (if it&#8217;s not already asleep) or to wake up (if it&#8217;s asleep waiting for the event) when the event is signalled (after which the event is atomically reset to a &#8220;not signalled&#8221; state). It doesn&#8217;t even have any spurious waits, making it really nice and easy to work with in a safe fashion. All of our <code>Semaphore</code> implementations will use this auto-reset event to take care of the synchronization and we&#8217;ll omit the details of when and where to call <a href="https://docs.rs/rsevents/latest/rsevents/struct.AutoResetEvent.html#method.set" rel="nofollow"><code>AutoResetEvent::set()</code></a> and <a href="https://docs.rs/rsevents/latest/rsevents/struct.AutoResetEvent.html#method.reset" rel="nofollow"><code>AutoResetEvent::reset()</code></a> for now.</p>
<p>So here&#8217;s what our initial semaphore skeleton looks like. We know we need an internal <code>count</code> of some integral type to keep track of the current concurrency (since we already established that it&#8217;s going to be variable and not just zero or one), and we know that at minimum a semaphore&#8217;s interface needs to provide a wait to &#8220;obtain&#8221; the semaphore (decrementing the available concurrency for future callers) and a way to &#8220;release&#8221; the semaphore (at least to be used by a thread that has already obtained a &#8220;concurrency token&#8221; to re-increment the count after it is done and wants to give up its access to the concurrency-restricted region for some other caller to take).</p>
<pre><code class="language-rust">struct Semaphore {
    event: AutoResetEvent,
    count: AtomicU32,
    // TODO: Fill in the rest
}

impl Semaphore {
    /// Create a new `Semaphore`
    pub fn new(/* TODO */) -&gt; Semaphore { /* TODO */ }

    /// Obtain the `Semaphore`, gaining access to the protected concurrency
    /// region and reducing the semaphore's internal count. If the 
    /// `Semaphore` is not currently available, blocks sleeping until it
    /// becomes available and is successfully obtained.
    pub fn wait(&amp;self) -&gt; ??? { ... }

    /// Increment the semaphore's internal count, increasing the available
    /// concurrency limit.
    pub fn release(&amp;self, ...) { ... }
}
</code></pre>
<p>Our goal now is to fill in the blanks, attempting to make a semaphore that&#8217;s maximally useful and as safe as possible, while limiting its size and runtime checks (the costs of said safety).</p>
<h3>A constant maximum concurrency?</h3>
<p>We&#8217;ve already established that a semaphore needs to offer a tunable &#8220;maximum concurrency&#8221; parameter that decides the maximum number of threads that can have access to the concurrency-limited region at any given time. This number is typically supplied at the time the semaphore is instantiated, and it&#8217;s quite normal for it to be fixed thereafter: while the <em>current</em> available concurrency may be artificially constrained beyond the number of threads that have actually borrowed/obtained the semaphore, it&#8217;s OK for the absolute maximum to be unchangeable after a semaphore is created.</p>
<p>We really have only two choices here: we either add a <code>max_count</code> integral struct member to our <code>Semaphore</code> or we take advantage of <a href="https://practice.rs/generics-traits/const-generics.html" rel="follow">rust&#8217;s const generics</a> (another brilliant zero-cost abstraction!) to eliminate this field from our struct altogether&#8230; but at a considerable cost.</p>
<p>Let&#8217;s consider some constraints that might determine the maximum concurrency a semaphore can have:</p>
<ul>
<li>We&#8217;ve experimentally determined that our backup application works best with up to eight simultaneous threads in the file read part of the pipeline and up to two simultaneous threads in the file write part of the pipeline, to balance throughput against maxing out the disk&#8217;s available IOPS. Here we can use two separate semaphores, each with a different hard-coded maximum concurrency.</li>
<li>Almost all modern web browsers limit the maximum number of live TCP connections per network address to a hard-coded limit, typically 16. Internally, the browser has a semaphore for each domain name with an active TCP connection (perhaps in a <code>HashMap&lt;DomainName, Semaphore&gt;</code>), and blocks before opening a new connection if the maximum concurrency is exceeded.</li>
</ul>
<p>In these cases, we could get away with changing our <code>Semaphore</code> declaration to <code>Semaphore&lt;const MAX: usize&gt;</code> and using const generics when initializing a semaphore to specify the hard-coded maximum concurrency limit, e.g. <code>let sem = Semaphore::&lt;16&gt;::new(...)</code>. But const generics doesn&#8217;t just lock us into a hard-coded value specified at the time of object initialization, it locks us into a hard-coded value specified <em>at the time of writing the source code for the object&#8217;s initialization.</em> That means we <em>can&#8217;t</em> use a const generic parameter in lieu of a <code>max_concurrency</code> field in cases like the following, which we unfortunately can&#8217;t just ignore:</p>
<ul>
<li>We want to limit the number of threads accessing a code section or running some operation to the number (or a ratio of the number) of CPU cores the code is running on (not <em>compiled</em> on).</li>
<li>We want to let the user select the max concurrency at runtime, perhaps by parsing a <code>-j THREADS</code> argument or letting the user choose from a drop-down menu or numselect widget in a GUI application.</li>
<li>Going back to our backup application example, we want to use different read/write concurrency limits in the case of an SSD vs in the case of an old-school HDD.</li>
</ul>
<p>This means that we&#8217;re going to have to approximately double the size of our <code>Semaphore</code> object, mirroring whatever type we&#8217;re using for <code>Semaphore::count</code> to add a <code>Semaphore::max_concurrency</code> member (they have to be the same size because <code>count</code> can vary from zero to <code>max_concurrency</code>), giving us the following, fairly complete, struct declaration.</p>
<h3>A functionally complete semaphore</h3>
<p>As we&#8217;ve determined, we unfortunately can&#8217;t use rust&#8217;s const generics to roughly halve the space a <code>Semaphore</code> object takes in memory, giving us the following declaration:</p>
<pre><code class="language-rust">struct Semaphore {
    event: AutoResetEvent,
    count: AtomicU32,
    max_count: AtomicU32,
}
</code></pre>
<p>While we were forced to add a <code>max_count</code> field to store the maximum allowed concurrency for the semaphore, this wasn&#8217;t at all a violation of the &#8220;zero-cost abstraction&#8221; principle: if we want to allow the user to specify something at runtime and match against it later, this is the cost we invariably pay (whether in rust or in assembly).</p>
<p>As bare as our <code>Semaphore</code> structure is, this is actually enough to implement a completely functional and &#8211; for the most part &#8211; safe semaphore. Thanks to how <code>AutoResetEvent</code> is implemented internally and its own safety guarantees, we can get away with just some extremely careful use of atomics, without a lock or mutex of any sort:</p>
<pre><code class="language-rust">impl Semaphore {
    /// Create a new `Semaphore`
    pub fn new(initial_count: u32, max_count: u32) -&gt; Semaphore {
        Semaphore { 
            event: AutoResetEvent::new(EventState::Unset),
            count: initial_count,
            max_count,
        }
    }

    /// Obtain the `Semaphore`, gaining access to the protected concurrency
    /// region and reducing the semaphore's internal count. If the 
    /// `Semaphore` is not currently available, blocks sleeping until it
    /// becomes available and is successfully obtained.
    pub fn wait(&amp;self) {
        let mut count = self.count.load(Ordering::Relaxed);

        loop {
            count = if self.count == 0 {
                // Semaphore isn't available, sleep until it is.
                self.event.wait();
                // Refresh the count after waking up
                self.count.load(Ordering::Relaxed)
            } else {
                // We can't just fetch_sub(1) because it might underflow in a race
                match self.count.compare_exchange(count, count - 1, 
                    Ordering::Acquire, Ordering::Relaxed)
                {
                    Ok(_) =&gt; {
                        // We officially obtained the semaphore.
                        // If the (now perhaps stale) new `count` value is non-zero, 
                        // it's our job to wake someone up (or let the next caller in).
                        if count - 1 &gt; 0 {
                            self.event.set();
                        }
                        break;
                    },
                    Err(count) =&gt; count, // Refresh the cached value and try again
                }
            }
        }

        // This must hold true at all times!
        assert!(count &lt;= self.max_count); 
        return ();
    }

    /// Increment the semaphore's internal count, increasing the available
    /// concurrency limit. 
    pub fn release(&amp;self) { 
        // Try to increment the current count, but don't exceed max_count
        let old_count = self.count.fetch_add(1, Ordering::Release);
        if old_count + 1 &gt; self.max_count {
            <span style="color: #ff0000;"><strong>panic!("Attempt to release past max concurrency!");</strong></span>
        }
        // Check if we need to wake a sleeping waiter
        if old_count == 0 {
            self.event.set();
        }
    }
}
</code></pre>
<p>If you&#8217;re familiar with atomic <abbr title="compare-and-swap">CAS</abbr>, the annotated source code above should be fairly self-explanatory. In case you&#8217;re not, briefly, <code>AtomicXXX::compare_and_exchange()</code> is how most lock-free data structures work: you first read the old value (via <code>self.count.load()</code>) and decide based on it what the new value should be, then use <code>compare_and_exchange()</code> to change <code>$old</code> to <code>$new</code>, <em>if and only if</em> the value is still <code>$old</code> and hasn&#8217;t been changed by another thread in the meantime (if it <em>has</em> changed, we re-read to get the new value and try all over again until we succeed).</p>
<p>With the <code>Semaphore</code> skeleton filled out, we now have a functionally complete semaphore with features comparable to those available in other languages. At the moment, <code>Semaphore::wait()</code> returns nothing and any thread that&#8217;s obtained the semaphore must ensure that <code>Semaphore::release()</code> is called before it returns, but that&#8217;s just an ergonomic issue that we can easily work around by returning a concurrency token that calls <code>Semaphore::release()</code> when it&#8217;s dropped instead.<sup id="rf7-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn7-4958" title="We would still have to keep Semaphore::release() around and make sure it can be publicly called so that a semaphore initialized with { count: m, max_count: n, ... } with&nbsp;m &ge; 0 and&nbsp;n &gt; m can be used." rel="footnote">7</a></sup></p>
<p>But can we make it safer?</p>
<h3>The problem with <code>Semaphore::release()</code></h3>
<p>For the remainder of this article, we&#8217;ll be focusing on the ugly part of a semaphore, the part that makes writing a truly safe semaphore so challenging: <code>Semaphore::release()</code>.</p>
<p>In rust, the standard library concurrency primitives all return &#8220;scope guards&#8221; that automatically release (or poison) their associated synchronization object at the end of the scope or in case of panic. This actually isn&#8217;t just a question of ergonomics (as we mentioned before), it&#8217;s a core part of their safety. On Windows, you can create a mutex/critical section with <code>InitializeCriticalSection()</code> and obtain it with <code>EnterCriticalSection()</code> but any thread can call <code>LeaveCriticalSection()</code>, <a href="https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-leavecriticalsection#:~:text=If%20a%20thread%20calls%20LeaveCriticalSection%20when%20it%20does%20not%20have%20ownership%20of%20the%20specified%20critical%20section%20object%2C%20an%20error%20occurs%20that%20may%20cause%20another%20thread%20using%20EnterCriticalSection%20to%20wait%20indefinitely." rel="follow">even if it invokes undefined behavior that may cause a deadlock</a>. On Linux and its pals, <code>pthread_mutex_init()</code> can be used to create a mutex and <code>pthread_mutex_lock()</code> can be used to enter the mutex. While <code>pthread_mutex_unlock()</code> <a href="https://linux.die.net/man/3/pthread_mutex_unlock#:~:text=The%20pthread_mutex_unlock()%20function%20may,not%20own%20the%20mutex." rel="follow">is documented as <em>may</em> return an <code>EPERM</code> error</a> if the current thread doesn&#8217;t own the mutex, the notes clarify that the current owning thread id isn&#8217;t stored and deadlocks are allowed in order to avoid overhead &#8211; meaning its implementation-defined whether or not <code>pthread_mutex_unlock()</code> actually protects against unlocking from a different thread. In practice, it doesn&#8217;t.<sup id="rf8-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn8-4958" title="You can see a sample application testing for pthread_mutex_unlock() safety here and try it out for yourself online here." rel="footnote">8</a></sup></p>
<p>Rust, as a partially object-oriented language, sidesteps all this via our good friend <abbr title="Resource Acquisition Is Initialization">RAII</abbr> by simply making the equivalent of <code>Mutex&lt;T&gt;::unlock(&amp;self)</code> unavailable except via the scope guard (<a href="https://doc.rust-lang.org/nightly/std/sync/struct.MutexGuard.html" rel="follow"><code>MutexGuard</code></a>) returned by <code>Mutex&lt;T&gt;::lock(&amp;self)</code> (and the same for <code>RwLock&lt;T&gt;</code>). The type system prevents you from calling the equivalent of <code>pthread_mutex_unlock()</code> unless you already own a <code>MutexGuard</code>, which can only be acquired as a result of a successful call to <code>Mutex::lock()</code> &#8211; without needing any runtime code to check whether or not the calling thread is the owning thread, because the type system provides that safety guarantee at zero cost.</p>
<p>Unfortunately, as I mentioned earlier in passing, even if we did make our <code>Semaphore::wait()</code> function return some sort of concurrency token/scope guard, it would at best by an <em>ergonomic</em> improvement to make sure one doesn&#8217;t forget to call <code>Semaphore::release()</code> before exiting the scope, but it wouldn&#8217;t allow us to eliminate a publicly callable <code>Semaphore::release()</code> function that any thread could call at any time, at least not without making it impossible to create a semaphore with an initial count that doesn&#8217;t equal the maximum count, and not without making the &#8220;current maximum concurrency&#8221; limit non-adjustable at runtime.</p>
<h3>Do we even need <code>Semaphore::release()</code> anyway?</h3>
<p>At this point, you might be tempted to ask if we really truly absolutely need these options and questioning what purpose they serve &#8211; which is a good and very valid question, given the cost we have to pay to support it and especially because in all the cases we mentioned above (those with a fixed <code>max_count</code> and those without), you&#8217;d always create a semaphore with <code>initial_count == max_count</code>. Here are some hopefully convincing reasons:</p>
<ul>
<li>Semaphores aren&#8217;t just used to limit concurrency up to a maximum pre-defined limit, they&#8217;re also used to temporarily artificially constrain available concurrency to a value in the range of <code>[0, max_count]</code> &#8211; in fact, this is where you might find their greatest utility.</li>
<li>CS principle: Preventing a &#8220;<a href="https://en.wikipedia.org/wiki/Thundering_herd_problem" rel="follow">thundering herd</a>&#8221; when a number of threads are blocked waiting on a semaphore to allow them access to a resource or code region. You can have up to <em>n</em> threads performing some task at the same time, but you don&#8217;t want them to all start synchronously because it&#8217;ll cause unwanted contention on some shared state, e.g.
<ul>
<li>Perhaps an atomic that needs to be incremented, and you don&#8217;t want to waste cycles attempting to satisfy <code>compare_exchange()</code> calls under contention or you otherwise have a traditional fine-grained lock or even lock-free data structure where uncontended access is ~free but contention wastes CPU cycles busy-waiting in a spinlock or even puts threads to sleep;</li>
<li>Starting operations at ~the same time can run into real-world physical limitations, e.g. it takes <em>n</em> IO threads to achieve maximum saturation of available <em>bulk</em> bandwidth but the initial seeks can&#8217;t satisfy as many IOPS without unduly increasing latency and decreasing response times.</li>
<li>The semaphore controls the number of threads downloading in parallel but you don&#8217;t want to DOS the DNS or HTTP servers by flooding them with simultaneous requests, though you do ultimately want <em>n</em> threads handling the response, downloading the files, etc. in parallel.</li>
</ul>
</li>
<li>CS principle: Making available concurrency accrue over time, up to a maximum saturation limit (represented by <code>max_count</code>), for example:
<ul>
<li>A family of AWS EC2 virtual machine instances have what is referred to as &#8220;<a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances.html" rel="follow">burstable performance</a>&#8221; where for every <em>m</em> minutes of low CPU usage, you get <em>t</em> time slices of &#8220;more than what you paid for&#8221; instantaneous CPU performance, up to <em>n</em> maximum time slices accrued. As a simplified example, you &#8220;rent&#8221; a virtual machine with a lower nominal CPU speed of 2.0 GHz and for every 5 minutes with a p95 per-5-second-time-slice CPU utilization below 50%, you get a &#8220;free&#8221; 5-second-time-slice of 3.0 GHz compute. If you launch a burstable instance and immediately try running Prime95, you&#8217;ll be capped to 2.0 GHz, but if you are running nginx and serving web requests under relatively no load, then after 10 minutes you&#8217;ll have accrued 10 seconds of &#8220;free&#8221; 3.0 GHz performance. When you suddenly get a burst of traffic because a link to your site was just shared on an iMessage or WhatsApp group and 200 phones are hitting your server to generate a web preview, you can &#8220;cash in&#8221; those accrued 3.0 GHz time slices to satisfy those requests quickly, after which you&#8217;re constrained to the baseline 2.0 GHz performance until the requests settle down and you begin to accrue 3.0 GHz time slices once again.</li>
<li>While you can implement a rate limiter by giving each connected user/IP address a maximum number of requests they can make per 5 minutes and resetting that limit every 5 minutes, that still means your server can be DDOS&#8217;d by n clients saturating their limit of 100-requests-per-5-minutes in the first few seconds after establishing a connection. You can instead give each client a semaphore<sup id="rf9-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn9-4958" title="After all, if we use an AtomicU8 to represent the max/initial count, they can be as small as three bytes each!" rel="footnote">9</a></sup> with an initial available count of, say, 2 and then every three seconds increment the available concurrency by 3; meaning clients would have to be connected for a full 5 minutes before they can hit you with 100 requests in one go (making it more expensive to mount an attack and giving your firewall&#8217;s heuristics a chance to disconnect them).</li>
</ul>
</li>
</ul>
<p>Hopefully the above reasons demonstrate the utility in being able to create a <code>Semaphore</code> with an initial count lower than the maximum count, and therefore, the need to have a free-standing <code>Semaphore::release()</code> function that, at the very least, the creator of the semaphore can call even without having previously made a corresponding call to <code>Semaphore::wait()</code>.</p>
<h3>Can we make <code>Semaphore::release()</code> safer?</h3>
<p>There&#8217;s an immediately obvious way to make <code>Semaphore::release()</code> safer to call, and that&#8217;s to replace it with a <code>Semaphore::try_release()</code> method that first checks to ensure that the semaphore&#8217;s core integrity predicate (<code>self.count &lt;= self.max_count</code>) is upheld instead of blindly incrementing <code>self.count</code> and then panicking if it then exceeds <code>self.max_count</code>, returning <code>true</code> or <code>false</code> instead to denote whether the operation completed successfully or not.</p>
<p>It&#8217;s actually not that hard of a change to make, provided you&#8217;re familiar with <a href="https://en.wikipedia.org/wiki/Compare-and-swap" rel="follow">compare-and-exchange atomics</a> (which we used above in safely implementing <code>Semaphore::wait()</code> to prevent two threads from racing to increment <code>self.count</code> after checking that it&#8217;s less than <code>self.max_count</code>), which we&#8217;ll use to write our <code>try_release()</code> method now:</p>
<pre><code class="language-rust">impl Semaphore {
    /// Attempts to increment the `Semaphore`'s internal count,
    /// returning `false` if it would exceed the maximum allowed.
    pub fn try_release(&amp;self) -&gt; bool {
        let mut prev_count = self.count.load(Ordering::Relaxed);
        loop {
            if prev_count == self.max_count {
                // Incrementing self.count would violate our core precondition
                return false;
            }

            match self.count.compare_exchange_weak(
                prev_count, // only exchange `count` if it's still `prev_count`
                prev_count + 1, // what to exchange `count` with
                Ordering::Release, Ordering::Relaxed)
            {
                // The CAS succeeded
                Ok(_) =&gt; {
                    if prev_count == 0 {
                        // Wake a waiting thread
                        self.event.set();
                    }
                    return true;
                }
                // If it failed, refresh `prev_count` and retry, failing if 
                // another thread has caused `prev_count` to equal `max_count`.
                Err(new_count) =&gt; prev_count = new_count,
            }
        }
    }
}
</code></pre>
<p>This <em>is</em> much better. We only increment <code>count</code> if we can guarantee that it won&#8217;t cause it to exceed <code>max_count</code>, and relay our results to the caller. We can now guarantee that call to <code>Semaphore::try_release()</code> that wasn&#8217;t paired with a previous <code>Semaphore::wait()</code> won&#8217;t inadvertently violate the absolute limit on the allowed concurrency.</p>
<p>But can you spot the problem? Look at the code sample carefully, and consider it not in the context of a single call to <code>Semaphore::try_release()</code> but as a whole.</p>
<p>If you think you&#8217;ve figured it out or you just want to skip to the answers, read on to find out where the problem still lies. (Yes, this is just a filler paragraph to avoid your eyes immediately seeing the answer while you think this over. Hopefully you haven&#8217;t already scrolled down too much so that the answer is already in view. Sorry, I can&#8217;t really do better than this, my typesetting system doesn&#8217;t let me insert empty vertical whitespace very elegantly.)</p>
<p>The issue is that we&#8217;ve prevented <em>this</em> call to <code>Semaphore::try_release()</code> from incrementing <code>count</code> past <code>max_count</code>, but we might already have extant threads chugging away in parallel that have already obtained the semaphore, oblivious to the fact that <code>count</code> has changed from under them. The problem is easy to spot if we keep a copy of our old <code>Semaphore::release()</code> around and mix-and-match between it and <code>try_release()</code>, with calls we <em>expect</em> to always succeed using the former and calls that may overflow the current count using the latter:</p>
<pre><code class="language-rust">fn main() {
    // Create a semaphore with a count of 1 and max count of 2
    let semaphore = Semaphore::new(1, 2); 

    // Create a number of threads that will use the semaphore 
    // to make sure concurrency limits are observed.
    
    for n in 0..NUM_CPUS {
        std::thread::spawn(|| {
            // Make sure to obtain a concurrency token before beginning work
            semaphore.wait();

            // TODO: Replace `sleep()` with actual work!
            std::thread::sleep(Duration::from_secs(5));

            // Release the concurrency token when we're done so another 
            // thread may enter this code block and do its thing.
            semaphore.release();
        });
    }

    while !work_finished {
        // &lt;Read user input from keyboard here&gt;
        
        // In response to a user command, increase concurrency 
        if !semaphore.try_release() {
            eprintln!("Cannot raise limit, max already reached!");
        }
    }
}
</code></pre>
<p>We&#8217;re using our semaphore to limit the number of simultaneous worker threads, originally operating at 50% of our maximum concurrency limit (<code>count == 1</code>). To start, one of <em>NUM_CPUS</em><sup id="rf10-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn10-4958" title="For the sake of this example, let&rsquo;s assume NUM_CPUS is a sufficiently large number like 4 or 8, so that enough worker threads will try to enter the semaphore-protected region." rel="footnote">10</a></sup> worker threads obtains the semaphore (<code>count == 0</code>) to access the concurrency limited region and the rest are locked out.</p>
<p>In the main thread&#8217;s event loop, we wait for work to finish or a user command to be entered at the keyboard (omitted from the example).<sup id="rf11-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn11-4958" title="rsevent-extra&lsquo;s CountdownEvent might just be the perfect tool for this job, btw!" rel="footnote">11</a></sup> The user keys in an input that&#8217;s interpreted as &#8220;try to speed things up by increasing the concurrency limit,&#8221; and in response the main thread calls <code>Semaphore::try_release()</code> and succeeds in incrementing its internal <code>self.count</code> (now <code>count == 1</code> again), thereby allowing a second thread to enter the semaphore-protected region (giving <code>count == 0</code>) and contribute to progress.</p>
<p>But what happens now that two threads are already in the semaphore-protected scope but the user triggers a second call to <code>Semaphore::try_release()</code>? As far as the semaphore knows, <code>count</code> (equal to 0) is less than <code>max_count</code>, and can be safely incremented, yielding <code>count == 1</code> and thereby unblocking yet another worker thread sleeping in <code>semaphore::wait()</code> (reducing <code>count</code> to zero).</p>
<p>But what does that mean? Even though our core precondition <em>hasn&#8217;t</em> been violated  in this instant (<code>Semaphore::count &lt;= Semaphore::max_count</code>), we now have <em>three</em> worker threads in the concurrency-limited region, exceeding the hard-coded <code>max_count</code> provided during the semaphore initialization! In our example above where each worker thread, having first obtained a concurrency token via <code>Semaphore::wait()</code>, assumes that it can call the infallible <code>Semaphore::release()</code> method safely when it&#8217;s done, but when the last worker thread finishes its work, it&#8217;ll end up incrementing <code>count</code> past <code>max_count</code> and panicking in the process.</p>
<p>Of course, the panic isn&#8217;t the problem &#8211; it&#8217;s just reporting the violation when it sees it. We could replace all <code>Semaphore::release()</code> calls with <code>Semaphore::try_release()</code> and we&#8217;d still have a problem: there are more worker threads in the semaphore-protected region than allowed, and one of the &#8220;shouldn&#8217;t fail&#8221; calls to <code>Semaphore::try_release()</code> will eventually return <code>false</code>, whether that triggers a panic or not.</p>
<p>The crux of the matter is that we borrow from <code>Semaphore::count</code> but don&#8217;t have a way to track the total &#8220;live&#8221; concurrency count available (the remaining concurrency tokens in <code>self.count</code> plus any temporarily-unavailable-but-soon-to-be-returned concurrency tokens borrowed by the various worker threads). And, importantly, <strong>we don&#8217;t need this for any core semaphore functionality but rather only to protect against a user/dev calling <code>Semaphore::release()</code> more times than they&#8217;re allowed to</strong>. In a perfect world, it would be the developer&#8217;s responsibility to make sure that there are never more outstanding concurrency tokens than allowed, and we could omit these safety checks altogether. She could statically ensure it&#8217;s the case by only ever pairing together <code>wait()</code> and <code>release()</code> calls (perhaps after issuing a fixed, verifiable number of <code>release()</code> calls upon init), or by tracking <em>when and where needed</em> the number of free-standing calls to <code>release()</code> in an external variable to make sure the limit is never surpassed.</p>
<h3>At last, a truly safe semaphore?</h3>
<p>While we could offload the cost of verifying that there will never be more than <code>max_count</code> calls to <code>Semaphore::release()</code> to the developer, we&#8217;re rust library authors and we hold ourselves to a higher standard. We can most certainly do just that, but we would have to mark <code>Semaphore::release()</code> as an <code>unsafe</code> function to warn users that there are preconditions to calling this and dangers to be had if calling it willy-nilly without taking them into account. But what if we want a synchronization type that&#8217;s easier to use and worthy of including in the standard library, being both safe and easy-to-use? What then?</p>
<p>There are actually several approaches we can take to solving this gnarly problem. The easiest and safest solution would be to simply change our <code>Semaphore</code> definition, adding another field of the same integral count type, perhaps called <code>total_count</code> or <code>live_count</code> that would essentially track <code>initial_count</code> plus the number of successful free-standing calls to <code>Semaphore::release()</code>, <em>but not decrement it when a call to <code>Semaphore::wait()</code> is made</em>.<sup id="rf12-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn12-4958" title="Another way would be to pack count and max_count into a single double-width atomic (assuming such an atomic of this size exists) and to decrement&nbsp;both count and max_count when a call to Semaphore::wait() is made. This way, any calls to Semaphore::release() would compare the potential increase in count against a similarly decremented max_count and can catch any violations of our core precept. The issues described in the remainder of this article persist regardless of which method was chosen." rel="footnote">12</a></sup> This way each time <code>try_release()</code> is called, we can check if <code>total_count</code> (and not <code>count</code>) would exceed <code>max_count</code> &#8212; and continue to use just <code>self.count</code> for the core semaphore functionality in <code>Semaphore::wait()</code>:</p>
<pre><code class="language-rust">struct Semaphore {
	event: AutoResetEvent,
	count: AtomicU32,
	total_count: AtomicU32,
    max_count: AtomicU32,
} 

impl Semaphore {
    /// Attempts to increment the `Semaphore`'s internal count,
    /// returning `false` if it would exceed the maximum allowed.
    pub fn try_release(&amp;self) -&gt; bool {
        // First try incrementing `total_count`:
        let mut prev_count = self.total_count.load(Ordering::Relaxed);
        loop {
            if prev_count == self.max_count {
                // Incrementing self.total_count would violate our core precondition
                return false;
            }

            match self.total_count.compare_exchange_weak(
                prev_count, // only exchange `total_count` if it's still `prev_count`
                prev_count + 1, // what to exchange `total_count` with
                Ordering::Relaxed, Ordering::Relaxed)
            {
                // If the CAS succeeded, continue to the next phase
                Ok(_) =&gt; break,
                // If it failed, refresh `prev_count` and retry, failing if 
                // another thread has caused `prev_count` to equal `max_count`.
                Err(new_count) =&gt; prev_count = new_count,
            }
        }

        // Now increment the actual available count:
        let prev_count = self.count.fetch_add(1, Ordering::Release);
        if prev_count == 0 {
            // Wake up a sleeping waiter, if any
            self.event.set();
        }

        return true;
    }
}
</code></pre>
<h3>But now, something else breaks!</h3>
<p>Let&#8217;s go back to our last example with many worker threads attempting to access a concurrency-restricted region and a main event loop that reads user commands that can affect the available concurrency limit. To make this more relatable, let&#8217;s use a real-world example: we&#8217;re developing a command-line BitTorrent client and we want the user to control the number of simultaneous uploads or downloads, up to some limit (that might very well be <code>u32::MAX</code>). In the last example we were focused on user commands that <em>increased</em> the available concurrency limit, in our real-world example perhaps reflecting a user trying to speed up ongoing downloads or seeds by allowing more concurrent files or connections.</p>
<p>But this isn&#8217;t a one-way street! Just as a user may wish to unthrottle BitTorrent downloads, they might very well wish to throttle them to make sure they&#8217;re just seeding in the background without saturating their Top Tier Cable Provider&#8217;s pathetic upload speed and killing their internet connection in the process. How do we do <em>that</em> safely?</p>
<p>One way would be to introduce a method to directly decrement our semaphore&#8217;s <code>self.total_count</code> and <code>self.count</code> fields (down to zero), but what do we do if <code>total_count</code> was non-zero but <code>count</code> was zero (i.e. all available concurrency was currently in use)? Apart from the fact that we are using unsigned atomic integers to store the counts, we could decrement <code>count</code> (but not <code>total_count</code>) past zero, for example to <code>-1</code>, and let the &#8220;live&#8221; concurrency eventually settle at the now-reduced <code>total_count</code> after a sufficient number of threads finish their work and leave the concurrency-limited region.</p>
<p>But we don&#8217;t actually need to do any of that: by its nature a semaphore already provides a way to artificially reduce the available concurrency by means of a free-standing call to <code>Semaphore::wait()</code>, i.e. calling <code>wait()</code> without calling <code>release()</code> afterwards. It ensures that the <code>count</code> isn&#8217;t reduced until it&#8217;s non-zero and that <code>count</code> never exceeds <code>max_count</code> or <code>total_count</code> at any time, not even temporarily.</p>
<p>Unfortunately, herein lies a subtle problem. With our revised semaphore implementation, we increase both <code>count</code> and <code>total_count</code> when the free-standing <code>release()</code> is called and assume that each call to <code>Semaphore::wait()</code> will have a matching call to some <code>Semaphore::special_release()</code> that increases <code>count</code> without touching <code>total_count</code>. This way, <code>total_count</code> tracks the &#8220;total available&#8221; concurrency, assuming that it&#8217;s equal to &#8220;remaining concurrency limit&#8221; plus &#8220;outstanding calls to <code>wait()</code> that haven&#8217;t yet called <code>xxx_release()</code>.</p>
<p>While free-standing calls to <code>Semaphore::release()</code> were our problem before, here we&#8217;ve shifted that to an issue with free-standing calls to <code>Semaphore::wait()</code> &#8211; an admittedly less hairy of a situation but, as we have seen, still not one that we can afford to ignore!</p>
<p>More importantly, even if we weren&#8217;t using free-standing calls to <code>Semaphore::wait()</code> to artificially reduce the available concurrency, we actually <em>can&#8217;t</em> guarantee that <code>release()</code> is always called after <code>wait()</code>: it&#8217;s a form of the halting problem, and <em>even if</em> we ignore panics and have <code>wait()</code> return a scope guard that automatically calls <code>release()</code> when it&#8217;s dropped, it&#8217;s <em>still</em> completely safe for a user to call <code>std::mem::forget(scope_guard)</code> thereby preventing <code>release()</code> from being called!<sup id="rf13-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn13-4958" title="In rust parlance, memory leaks do not fall under the safety guarantees of the language and it&rsquo;s perfectly &ldquo;safe&rdquo; if not exactly cromulent to write code that doesn&rsquo;t drop() RAII resources." rel="footnote">13</a></sup></p>
<p>Fundamentally, we can&#8217;t really solve this problem. We either err on the side of potentially allowing too many free-standing calls to <code>release()</code> to be made, with safety checks delaying the overflow of <code>max_count</code> until the last borrowed concurrency token is returned after a call to <code>wait()</code>; or we err on the side of prudence and incorrectly prevent a free-standing call to <code>release()</code> from going through because we don&#8217;t know (and <em>can&#8217;t</em> know) that a thread which previously call <code>wait()</code> and took one of our precious concurrency tokens has decided it&#8217;s not going to ever return it.</p>
<p>But don&#8217;t despair! Do you remember the old schoolyard riddle? You&#8217;re silently passed a pen and notebook, on which you see the following:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-4972 colorbox-4958" src="https://neosmart.net/blog/wp-content/uploads/2022/09/Make-this-line-shorter.png" alt="" width="525" height="509" srcset="https://neosmart.net/blog/wp-content/uploads/2022/09/Make-this-line-shorter.png 525w, https://neosmart.net/blog/wp-content/uploads/2022/09/Make-this-line-shorter-309x300.png 309w" sizes="auto, (max-width: 525px) 100vw, 525px" /></p>
<p>Like with the riddle,<sup id="rf14-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn14-4958" title="Give up? Just draw another, longer line next to it!" rel="footnote">14</a></sup> we can implement scope guards to make it more likely that every call to <code>wait()</code> is matched by call to <code>release()</code> but we can&#8217;t actually stop the user from calling <code>std::mem::forget(sem.wait())</code> &#8211; and we don&#8217;t have to. Without trying to think of ways to cause a compiler error when a scope guard is leaked and not dropped, we can still make it, if not hard then at least <em>harder</em> for the user to leak a scope guard and throw our internal count off. How? Not by hiding the ability to forget a scope guard but by highlighting it front and center!</p>
<p>Let&#8217;s fast forward to our semaphore from above, but modified to return a scope guard instead to encourage returning concurrency tokens back to the semaphore when a thread has finished with a concurrency-limited operation:</p>
<pre><code class="language-rust">/// This concurrency token is returned from a call to `Semaphore::wait()`.
/// It's automatically returned to the semaphore upon drop, incrementing
/// the semaphore's internal available concurrency counter once more.
struct ConcurrencyToken&lt;'a&gt; {
    sem: &amp;'a Semaphore
}

impl Semaphore {
    pub fn wait&lt;'a&gt;(&amp;'a self) -&gt; ConcurrencyToken&lt;'a&gt; {
        include!("earlier definition of Semaphore::wait() here");

        // Now instead of returning () we return a ConcurrencyToken

        return ConcurrencyToken {
            sem: self,
        }
    }

    /// Directly increments the internal concurrency count without touching 
    /// `total_count` and without checking if it would exceed `max_count`.
    unsafe fn release_internal() {
        let prev_count = self.count.fetch_add(1, Ordering::Release);

        // We only need to wake a sleeping waiter if the previous count
        // was zero. In all other cases, no one will be asleep.
        if prev_count == 0 {
            self.event.set();
        }
    }
}

impl Drop for ConcurrencyToken&lt;'_&gt; {
    fn drop (&amp;mut self) {
        unsafe { self.sem.release_internal(); }
    }
}
</code></pre>
<p>This was our initial attempt at &#8220;strongly encouraging&#8221; calls to <code>Semaphore::wait()</code> to always be paired with calls to <code>Semaphore::internal_release()</code> (called by the <code>ConcurrencyToken</code> on drop), which decrements <code>count</code> without touching <code>total_count</code> so our logic in <code>Semaphore::try_release()</code> can continue to work.</p>
<p>As we said though, if one were to call <code>std::mem::forget(sem.wait())</code> the <code>ConcurrencyToken</code> would be forgotten without <code>internal_release()</code> ever being called, and the count we track in <code>total_count</code> would be off by one, preventing a free-standing call to <code>Semaphore::release()</code> that should have been allowed.</p>
<p>So what if we just directly add a new method to our concurrency token? A  <code>ConcurrencyToken::forget()</code> makes it <em>harder</em> to call <code>std::mem::forget()</code> on our concurrency token than it is to just call <code>Semaphore::wait().forget()</code> directly! (See, I really was going somewhere with that riddle!)</p>
<pre><code class="language-rust">impl ConcurrencyToken&lt;'_&gt; {
    /// It is a violation of this crate's contract to call `std::mem::forget()`
    /// on the result of `Semaphore::wait()`. To forget a `ConcurrencyToken`, 
    /// use this method instead.
    pub fn forget(self) {
        // We're keeping `count` permanently reduced, but we need to decrement
        // `total_count` to reflect this as well before forgetting ourselves.
        self.sem.total_count.fetch_sub(1, Ordering::Relaxed);
        std::mem::forget(self);
    }
}
</code></pre>
<p>And just like that, we now have something we can reasonably call a &#8220;safe&#8221; semaphore, worthy of rust!</p>
<h2>The price we pay for safety</h2>
<p>While I can&#8217;t say with complete confidence that this is the optimal implementation of a safe semaphore (exposing the same functionality), our journey above is still representative of the constant tug-of-war that takes place when trying to build an API as you juggle performance, the desire for zero-cost abstractions, and the imperative of surfacing a (within the boundaries of reasonable use) safe and misuse-resistant interface.</p>
<p>We started with something completely simple: two words for the count and a single byte auto-reset event we could use to impose strict ordering and optimized waiting/sleeping in cases of contention. Correctness (which, if you squint at it in a particular way, is just another kind of safety) mandated the use of atomics from the very start, preventing us from playing fast and loose with our integer math and imposing heavy penalties when it came to ensuring cache coherence and cross-core integrity. Then, just when we thought we had everything figured out, we needed to completely change our approach and even add a new struct member to boot (raising the size of the <code>Semaphore</code> struct by 33-45% depending on the integer width, which sounds really scary until you realize it&#8217;s still just a handful of bytes).</p>
<p>There are of course other possible solutions to the same problem, all of which potentially have their own drawbacks.<sup id="rf15-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn15-4958" title="I still need to sit down and experiment with packing count and max_count into one atomic double-width word and see how it works to decrement both count and max_count after each call to wait() instead of tracking with an additional total_count field, but even there we&rsquo;d have a price to pay. We can no longer use AtomicXXX::fetch_add() and we&rsquo;d have to use compare_exchange_weak() in a loop, after fetching the initial value, separating it into its component fields, incrementing/decrementing, then combining the fields into a single word again &ndash; although a quick godbolt attempt shows the compiler actually does a rather nice job." rel="footnote">15</a></sup> And even if there are cost-free solutions here, the general picture isn&#8217;t unique to semaphores or even concurrency primitives in general: it&#8217;s a story that&#8217;s played on repeat and comes up every time an interface needs to be added that has some caveats the caller needs to keep in mind. Writing correct code is hard, writing <em>safe</em> and correct code is harder. But, in my opinion, <em>this</em> is what actually makes rust special.</p>
<p>Rust&#8217;s concepts of ownership and exclusive RO/RW semantics play a huge role in making it such a popular language with low-level developers, but I would argue that it&#8217;s this attention that&#8217;s paid when writing libraries that deal with intricate or abstract concepts that can&#8217;t be reduced to simple <code>&amp;foo</code> vs <code>&amp;mut foo</code> semantics that make rust truly unique. As an old hat at C and C++ development, I&#8217;ve already worn my &#8220;low-level library developer&#8221; mantle thin, and it&#8217;s absolutely awesome to be able to write an abstraction that other developers can use without having to dive into syscalls and kernel headers as the only source of literature. But with rust, I&#8217;m experiencing a completely different kind of challenge in writing libraries and APIs: here there&#8217;s a bar even higher than just writing clean abstractions, and it&#8217;s being able to write these low-level abstractions in a way that not only can clever developers that have previously dealt with these system internals appreciate and use our libraries to their advantage, but that even others new to the scene can just read your documentation (or maybe not even that) and then let the shape of your API guide them to the rust, figuring out the <em>right</em> way of using it.</p>
<p>In any language, a savvy enough developer can usually figure their way around even a completely new library dealing with concepts and mechanisms completely alien to them. But in the process of figuring it out, they&#8217;re bound to make mistakes, bump into leaky abstractions (&#8220;but <em>why</em> shouldn&#8217;t I call <code>pthread_mutex_unlock</code> from another thread if I need to access the mutex and its currently locked? What is it there for, then?&#8221; &#8211; whether they&#8217;re asking it on SO or mulling it over quietly in their head as they figure out the black-box internals by poking and prodding away at the API surface), pull out what&#8217;s left of their hair, and bang their head against the wall some before arriving at a generally correct and workable solution.</p>
<p>But it doesn&#8217;t have to be that way, and the burden is on us as developers of these libraries and crates to give our fellow devs a better option. Runtime errors (like the ones the pthread API doesn&#8217;t even bother returning!) are good and other languages<sup id="rf16-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn16-4958" title="C# is a great example here, with extensive input and sanity checking for most APIs but almost all of it in the form of runtime exceptions &ndash; despite it being a strongly typed language with powerful and extensible compiler integration." rel="footnote">16</a></sup> have demonstrated how it can be used with non-zero but still fairly minimal overhead, but with the benefits of a strongly typed language, powerful type abstractions, and perhaps a smattering of generics and ZSTs, we can and should do better.</p>
<h2>The truly safe semaphore, for your benefit and review</h2>
<p>The semaphore we iteratively designed in this article is available for you to use, study, or review as part of the 0.2 release of the <a href="https://github.com/neosmart/rsevents-extra" rel="nofollow">rsevents-extra crate</a>. <a href="https://docs.rs/rsevents-extra/0.2.0/rsevents_extra/struct.Semaphore.html#method.try_release" rel="nofollow">This is the current API exposed by our <code>Semaphore</code></a> type (<a href="https://github.com/neosmart/rsevents-extra/blob/master/src/semaphore.rs" rel="nofollow">source code here</a>), incorporating some of the ideas discussed above.<sup id="rf17-4958"><a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/#fn17-4958" title="It currently still uses atomic unsigned integers in the implementation and so does not implement the wait-free, eventually consistent API to artificially reduce the currently available concurrency without making a wait() call,&nbsp;waiting for it to return, and then forgetting the result. At the time of its initial development, I had started off with signed integers then realized I didn&rsquo;t need them for the core functionality and switched to unsigned atomic values instead. I may need to revisit that decision in another release if it can give us either a wait-free reduce() corollary to release() instead of the Semaphore::wait().forget() or the current modify() method which allows wait-free direct manipulation of the internal count, but only with an &amp;mut Semaphore reference (to guarantee that it isn&rsquo;t in use, eschewing eventual consistency for correctness), but feedback on whether a wait-free reduce()&nbsp;at the cost of eventual consistency is a win or a draw/loss would be appreciated from anyone nerdy enough to read these footnotes!" rel="footnote">17</a></sup></p>
<p>The <code>Semaphore</code> type in <code>rsevents-extra</code> <a href="https://github.com/neosmart/rsevents-extra/blob/master/src/semaphore.rs" rel="nofollow">actually includes even more safety</a> than we&#8217;ve demonstrated above, but it&#8217;s of the bog-standard variety (checking for integer overflows, making sure we&#8217;re not decrementing past zero, etc) and not something unique to the challenges presented by semaphores in particular. The <a href="https://docs.rs/rsevents-extra/latest/rsevents_extra/struct.Semaphore.html#example" rel="nofollow"><code>Semaphore</code> docs example</a> shows a more fleshed-out version of the &#8220;listen for user events to artificially throttle/unthrottle the semaphore,&#8221; if you want to check it out.</p>
<p>If you have an itch to try your own hand at writing concurrency primitives, I cannot encourage you enough: it&#8217;s all kinds of challenging and rewarding, and really opens your mind to what goes on behind-the-scenes with synchronization types. The <code>rsevents</code> crate was written to make doing just that a breeze, and I recommend at least starting off with either <a href="https://docs.rs/rsevents/latest/rsevents/struct.ManualResetEvent.html" rel="nofollow">manual-</a> or <a href="https://docs.rs/rsevents/latest/rsevents/struct.AutoResetEvent.html" rel="nofollow">auto-reset events</a> to take care of the intricacies of sleeping and the correctness of waking one and only one past/future awaiter at a time. Rust generally uses channels and mutexes to take care of synchronization, but there&#8217;s always a time and place for lower level thread signalling constructs!</p>
<h2>Show some love and be the first to get new rust articles!</h2>
<p>Last but not least: a request for you, dear reader. I put a lot of effort into these rust writeups (and into the open source libraries and crates I author) for nothing but love. I&#8217;ve heard good things about Patreon, and have literally just now <a href="https://www.patreon.com/mqudsi" rel="follow">put up a page</a> to see if anyone would be interested in sponsoring my open source work. If you can&#8217;t spare some change to support me and my work on Patreon, please consider following me and my work <a href="https://twitter.com/mqudsi/" rel="follow">on twitter</a>, and starring <a href="https://github.com/neosmart/rsevents" rel="nofollow">rsevents</a> and <a href="https://github.com/neosmart/rsevents-extra" rel="nofollow">rsevents-extra</a> on GitHub.</p>
<p><strong>I&#8217;m currently looking for work opportunities as a senior engineer (rust is preferable, but I&#8217;m a polyglot). If you or your team is hiring, let me know!</strong></p>
<p>If you liked this writeup, please share it with others on your favorite nerdy social media platform. I also set up a small mailing list that only sends out an email when I post about rust here on this blog, you can sign up below to join (no spam, double opt-in required, one-click unsubscribe, I never share your email, etc. etc.):</p>
<blockquote class="twitter-tweet">
<p dir="ltr" lang="en">Just posted a ~longer writeup on what it takes to implement a truly safe Semaphore type in <a href="https://twitter.com/hashtag/rust?src=hash&amp;ref_src=twsrc%5Etfw" rel="follow">#rust</a>. Feedback welcome. <a href="https://t.co/QZdeCACpnH" rel="follow">https://t.co/QZdeCACpnH</a></p>
<p>— Mahmoud Al-Qudsi (@mqudsi) <a href="https://twitter.com/mqudsi/status/1577365674460151809?ref_src=twsrc%5Etfw" rel="follow">October 4, 2022</a></p></blockquote>
<p><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p>
<div class="sendy_widget" style='margin-bottom: 0.5em;'>
<p><em>If you would like to receive a notification the next time we release a rust library, publish a crate, or post some rust-related developer articles, you can subscribe below. Note that you'll only get notifications relevant to rust programming and development by NeoSmart Technologies. If you want to receive email updates for all NeoSmart Technologies posts and releases, please sign up in the sidebar to the right instead.</em></p>
<iframe tabIndex=-1 onfocus="sendy_no_focus" src="https://neosmart.net/sendy/subscription?f=BUopX8f2VyLSOb892VIx6W4IUNylMrro5AN6cExmwnoKFQPz9892VSk4Que8yv892RnQgL&title=Join+the+rust+mailing+list" style="height: 300px; width: 100%;"></iframe>
</div>
<script type="text/javascript">function sendy_no_focus(e) { e.preventDefault(); }</script>
<p><strong>Update (10/5/2022): </strong>The examples in this post have been updated to use <code>Ordering::Acquire</code> or <code>Ordering::Release</code> when reading/writing <code>self.count</code> in the <code>wait()</code> and <code>release()</code> family of functions to synchronize memory/cache coherence between threads and ensure correct instruction ordering. In the original version of this article, the examples all used <code>Ordering::Relaxed</code> and relied on <code>self.event</code> to take care of ordering, but as <code>self.event</code> is skipped as an optimization in cases where the semaphore has available concurrency, this was insufficient.</p>
<hr class="footnotes"><ol class="footnotes"><li id="fn1-4958"><p>In fact, in some languages/apis a &#8220;<a href="https://learn.microsoft.com/en-us/windows/win32/sync/critical-section-objects" rel="follow">critical section</a>&#8221; is another name for a mutex.&nbsp;<a href="#rf1-4958" class="backlink" title="Jump back to footnote 1 in the text.">&#8617;</a></p></li><li id="fn2-4958"><p>For a mutex, they are one and the same as it mandates exclusive access to a region.&nbsp;<a href="#rf2-4958" class="backlink" title="Jump back to footnote 2 in the text.">&#8617;</a></p></li><li id="fn3-4958"><p>Semaphores are generally non-reentrant so recursively obtaining a semaphore will count against its limit!&nbsp;<a href="#rf3-4958" class="backlink" title="Jump back to footnote 3 in the text.">&#8617;</a></p></li><li id="fn4-4958"><p>The empty parentheses/tuple <code>()</code> is rust-speak for <code>void</code>, for those of you not familiar with the language.&nbsp;<a href="#rf4-4958" class="backlink" title="Jump back to footnote 4 in the text.">&#8617;</a></p></li><li id="fn5-4958"><p>In rust, there&#8217;s actually (out of necessity) an entire class of  types or values that can be changed even through read-only references, a concept known as interior mutability and exposed via types like <code>AtomicXXX</code> and the various <code>std::cell Cell</code> types &#8212; but those are generally to be used on a fine-grained level and <em>in general</em> you wouldn&#8217;t be using them to make entire objects writeable via read-only references.&nbsp;<a href="#rf5-4958" class="backlink" title="Jump back to footnote 5 in the text.">&#8617;</a></p></li><li id="fn6-4958"><p>Actually, the <code>AutoResetEvent</code> implementation only takes <a href="https://github.com/neosmart/rsevents/blob/master/src/lib.rs" rel="nofollow">all of two bits</a>, but let&#8217;s just call it a byte to make everything nice and easy.&nbsp;<a href="#rf6-4958" class="backlink" title="Jump back to footnote 6 in the text.">&#8617;</a></p></li><li id="fn7-4958"><p>We would still have to keep <code>Semaphore::release()</code> around and make sure it can be publicly called so that a semaphore initialized with <code>{ count: m, max_count: n, ... }</code> with <em>m ≥ 0</em> and <em>n &gt; m</em> can be used.&nbsp;<a href="#rf7-4958" class="backlink" title="Jump back to footnote 7 in the text.">&#8617;</a></p></li><li id="fn8-4958"><p>You can see a sample application testing for <code>pthread_mutex_unlock()</code> safety <a href="https://gist.github.com/mqudsi/5aac71d8177866ba2762bda01a3ff21a" rel="follow">here</a> and try it out for yourself online <a href="https://onlinegdb.com/9hmNhBuEc" rel="follow">here</a>.&nbsp;<a href="#rf8-4958" class="backlink" title="Jump back to footnote 8 in the text.">&#8617;</a></p></li><li id="fn9-4958"><p>After all, if we use an <code>AtomicU8</code> to represent the max/initial count, they can be as small as three bytes each!&nbsp;<a href="#rf9-4958" class="backlink" title="Jump back to footnote 9 in the text.">&#8617;</a></p></li><li id="fn10-4958"><p>For the sake of this example, let&#8217;s assume <code>NUM_CPUS</code> is a sufficiently large number like 4 or 8, so that enough worker threads will try to enter the semaphore-protected region.&nbsp;<a href="#rf10-4958" class="backlink" title="Jump back to footnote 10 in the text.">&#8617;</a></p></li><li id="fn11-4958"><p><code>rsevent-extra</code>&#8216;s <code><a href="https://docs.rs/rsevents-extra/latest/rsevents_extra/struct.CountdownEvent.html">CountdownEvent</a></code> might just be the perfect tool for this job, btw!&nbsp;<a href="#rf11-4958" class="backlink" title="Jump back to footnote 11 in the text.">&#8617;</a></p></li><li id="fn12-4958"><p>Another way would be to pack <code>count</code> and <code>max_count</code> into a single double-width atomic (assuming such an atomic of this size exists) and to decrement <em>both</em> <code>count</code> and <code>max_count</code> when a call to <code>Semaphore::wait()</code> is made. This way, any calls to <code>Semaphore::release()</code> would compare the potential increase in <code>count</code> against a similarly decremented <code>max_count</code> and can catch any violations of our core precept. The issues described in the remainder of this article persist regardless of which method was chosen.&nbsp;<a href="#rf12-4958" class="backlink" title="Jump back to footnote 12 in the text.">&#8617;</a></p></li><li id="fn13-4958"><p>In rust parlance, memory leaks do not fall under the safety guarantees of the language and it&#8217;s perfectly &#8220;safe&#8221; if not exactly cromulent to write code that doesn&#8217;t <code>drop()</code> RAII resources.&nbsp;<a href="#rf13-4958" class="backlink" title="Jump back to footnote 13 in the text.">&#8617;</a></p></li><li id="fn14-4958"><p>Give up? Just draw another, longer line next to it!&nbsp;<a href="#rf14-4958" class="backlink" title="Jump back to footnote 14 in the text.">&#8617;</a></p></li><li id="fn15-4958"><p>I still need to sit down and experiment with packing <code>count</code> and <code>max_count</code> into one atomic double-width word and see how it works to decrement both <code>count</code> and <code>max_count</code> after each call to <code>wait()</code> instead of tracking with an additional <code>total_count</code> field, but even there we&#8217;d have a price to pay. We can no longer use <code>AtomicXXX::fetch_add()</code> and we&#8217;d have to use <code>compare_exchange_weak()</code> in a loop, after fetching the initial value, separating it into its component fields, incrementing/decrementing, then combining the fields into a single word again &#8211; although a quick godbolt attempt shows the compiler <a href="https://godbolt.org/z/4zba6js8j" rel="follow">actually does a rather nice job</a>.&nbsp;<a href="#rf15-4958" class="backlink" title="Jump back to footnote 15 in the text.">&#8617;</a></p></li><li id="fn16-4958"><p>C# is a great example here, with extensive input and sanity checking for most APIs but almost all of it in the form of runtime exceptions &#8211; despite it being a strongly typed language with powerful and extensible compiler integration.&nbsp;<a href="#rf16-4958" class="backlink" title="Jump back to footnote 16 in the text.">&#8617;</a></p></li><li id="fn17-4958"><p>It currently still uses atomic unsigned integers in the implementation and so does not implement the wait-free, eventually consistent API to artificially reduce the currently available concurrency without making a <code>wait()</code> call, <em>waiting for it to return</em>, and then forgetting the result. At the time of its initial development, I had started off with signed integers then realized I didn&#8217;t need them for the core functionality and switched to unsigned atomic values instead. I may need to revisit that decision in another release if it can give us either a wait-free <code>reduce()</code> corollary to <code>release()</code> instead of the <code>Semaphore::wait().forget()</code> or <a href="https://docs.rs/rsevents-extra/0.2.0/rsevents_extra/struct.Semaphore.html#method.modify" rel="nofollow">the current <code>modify()</code> method</a> which allows wait-free direct manipulation of the internal count, but only with an <code>&amp;mut Semaphore</code> reference (to guarantee that it isn&#8217;t in use, eschewing eventual consistency for correctness), but feedback on whether a wait-free <code>reduce()</code> <em>at the cost of eventual consistency</em> is a win or a draw/loss would be appreciated from anyone nerdy enough to read these footnotes!&nbsp;<a href="#rf17-4958" class="backlink" title="Jump back to footnote 17 in the text.">&#8617;</a></p></li></ol>The post <a href="https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/">Implementing truly safe semaphores in rust</a> first appeared on <a href="https://neosmart.net/blog">The NeoSmart Files</a>.]]></content:encoded>
					
					<wfw:commentRss>https://neosmart.net/blog/implementing-truly-safe-semaphores-in-rust/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4958</post-id>	</item>
	</channel>
</rss>