<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>emre şahin's digital garden 🍃 - Path</title>
    <link>https://emresahin.net/tags/path/</link>
    <description>Posts in the Path tag</description>
    <language>en</language>
    <managingEditor>contact@emresahin.net (Emre Şahin)</managingEditor>
    <lastBuildDate>Tue, 15 Sep 2026 19:46:32 +0000</lastBuildDate>
    <atom:link href="https://emresahin.net/tags/path/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>Syncing Path Operations in Xvc</title>
      <published>2024-06-04T19:50:37+00:00</published>
      <updated>2024-06-04T19:50:37+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Tue, 04 Jun 2024 19:50:37 +0000</pubDate>
      <link>https://emresahin.net/syncing-path-operations-in-xvc/</link>
      <guid isPermaLink="true">https://emresahin.net/syncing-path-operations-in-xvc/</guid>
      <description>While writing a HOWTO post for the documentation, I found a bug where multiple carry-in commands were causing file system failures. When multiple threads were accessing the same cache directory, if one of them tried to set up the cache directory while another was still working on it, it caused a ...</description>
      <category>xvc</category>
      <category>Development</category>
      <category>Rust</category>
      <category>Concurrency</category>
      <category>File System</category>
      <category>Locking</category>
      <category>Path</category>
      <category>Multithreading</category>
      <category>Parallel</category>
      <category>OS</category>
      <content:encoded><![CDATA[<p>While writing a HOWTO post for the documentation, I found a bug where multiple
carry-in commands were causing file system failures. When multiple threads were
accessing the same cache directory, if one of them tried to set up the cache directory
while another was still working on it, it caused a permissions error.</p>
<p>Rust has <em>fearless concurrency</em> for memory access, but for the file system, there
seem to be no built-in locked access primitives.</p>
<p>I decided to write one. Parallel execution of file system operations is important
for Xvc. The error messages are annoying; having identical files in a repository is
common, and in those cases, these messages look like there is a problem. In
theory, when files are identical, having only one of them written to the cache
is not a problem, but there may be other issues preventing cache
access. We could just swallow the error and get away with it, but that’s not ideal.</p>
<p>Two options came to mind. One is modifying the list of cache files before
creating threads so that no two threads access the same cache file at the same
time. This is hard to implement and brings extra complexity to thread creation.
A one-in-a-thousand concern becomes an architectural burden.</p>
<p>The other solution is to lock paths while accessing them so two threads
working on the same cache path wait for each other. This is easier and requires
just dependency injection into the thread functions. It has the downside of
making file system operations slightly slower, as each path operation will now require
checking a mutex, but that seems of little concern for file system access, which
is already much slower than memory operations.</p>
<p>First, I tried to implement this with a <code>HashMap&lt;PathBuf, Mutex&lt;()&gt;&gt;</code>, but this
must also be passed to the function wrapped in <code>Arc&lt;Mutex&lt;HashMap&gt;&gt;</code>, which
made the “ceremony” of acquiring the lock for a single file much longer.</p>
<p>The issue is that you don’t want the <code>HashMap</code> itself to be a bottleneck. Multiple
threads shouldn’t wait for the <code>HashMap</code> to become available, as it’s not the
<code>HashMap</code> we want to lock, but the values inside it.</p>
<p>The solution is to return the lock value from a method of a struct. Something
like:</p>
<pre><code class="language-rust">pub struct PathSync {
    locks: Arc&lt;RwLock&lt;HashMap&lt;PathBuf, Arc&lt;Mutex&lt;()&gt;&gt;&gt;&gt;&gt;,
}</code></pre>
<p>Now the ceremony can be performed within the method, and threads working in
different directories won’t need to wait for the hash map to become available.</p>
<p>However, after implementing this, I realized I’d probably forget to lock a path
at some point. This is a general-purpose solution, and I should apply it to all
path operations when multiple threads are working. In most cases,
there are multiple paths to lock (cache_path, cache_dir, repository path), and if
I forget to lock one of them, a future user, some time, somewhere, will probably
see an error message.</p>
<p>So, I decided on a different approach: creating wrappers to run passed closures. This makes it
much more obvious that paths Xvc works on must be locked before operations.</p>
<pre><code class="language-rust">    pub fn with_sync_path(
        &amp;self,
        path: &amp;Path,
        mut f: impl FnMut(&amp;Path) -&gt; Result&lt;()&gt;,
    )</code></pre>
<p>This works by passing the path and a closure that operates on that path. It
first locks the path and then runs the closure. The locking mechanism allows
threads with different paths to run in parallel, but if they try to operate on the same path, they
will wait for each other.</p>
<p>The implementation is <a href="https://github.com/iesahin/xvc/blob/main/walker/src/sync.rs">here</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>Rust `ends_with` and `strip_prefix` behavior differences in `Path` and `str`</title>
      <published>2022-07-06T16:03:51+00:00</published>
      <updated>2022-07-06T16:03:51+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Wed, 06 Jul 2022 16:03:51 +0000</pubDate>
      <link>https://emresahin.net/Rust--ends_with--and--strip_prefix--behavior-differences-in--Path--and--str-/</link>
      <guid isPermaLink="true">https://emresahin.net/Rust--ends_with--and--strip_prefix--behavior-differences-in--Path--and--str-/</guid>
      <description>While writing an ignore library, I encountered subtle bugs caused by Rust’s Path behavior. ends_with in Path is different from ends_with in str : If you try to check whether a Path is a directory by its final character, you’ll find that path.ends_with("/") returns false . Path::strip_prefix also ...</description>
      <category>development</category>
      <category>rust</category>
      <category>programming</category>
      <category>rust</category>
      <category>Path</category>
      <category>str</category>
      <category>std</category>
      <category>debugging</category>
      <content:encoded><![CDATA[<p>While writing an ignore library, I encountered subtle bugs caused by Rust’s <code>Path</code> behavior.</p>
<ul>
<li>
<p><strong><code>ends_with</code> in <code>Path</code> is different from <code>ends_with</code> in <code>str</code></strong>:
If you try to check whether a <code>Path</code> is a directory by its final character, you’ll find that <code>path.ends_with("/")</code> returns <code>false</code>.</p>
</li>
<li>
<p><strong><code>Path::strip_prefix</code> also consumes the final slash</strong>:
If you have a directory marker at the end, such as <code>/Users/emre/mydir/</code>, and pass it to <code>strip_prefix("/Users/emre")</code>, you’ll get <code>mydir</code> instead of <code>mydir/</code>.</p>
</li>
</ul>]]></content:encoded>
    </item>
  </channel>
</rss>
