<?xml version="1.0" encoding="utf-8"?><rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:media="http://search.yahoo.com/mrss/"
    xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
    xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
>
<channel>
  <title>Srijan Choudhary, all posts tagged: systemd</title>
  <link>https://srijan.ch/feed/all/tag:systemd</link>
  <lastBuildDate>Thu, 08 Jun 2023 19:20:00 +0000</lastBuildDate>
  <image>
    <url>https://srijan.ch/assets/favicon/favicon-32x32.png</url>
    <title>Srijan Choudhary, all posts tagged: systemd</title>
    <link>https://srijan.ch/feed/all/tag:systemd</link>
  </image>
  <sy:updatePeriod>daily</sy:updatePeriod>
  <sy:updateFrequency>1</sy:updateFrequency>
  <generator>Kirby</generator>
  <atom:link href="https://srijan.ch/feed/all.xml/tag:systemd" rel="self" type="application/rss+xml" />
  <description>Srijan Choudhary&#039;s Articles and Notes Feed for tag: systemd</description>
  <item>
    <title>Exploring conflicting oneshot services in systemd</title>
    <description><![CDATA[Exploring ways to make two systemd services using a shared resource work with each other]]></description>
    <link>https://srijan.ch/exploring-conflicting-oneshot-services-in-systemd</link>
    <guid isPermaLink="false">64807b30f6b0810001fa0d01</guid>
    <category><![CDATA[linux]]></category>
    <category><![CDATA[devops]]></category>
    <category><![CDATA[emacs]]></category>
    <category><![CDATA[systemd]]></category>
    <dc:creator>Srijan Choudhary</dc:creator>
    <pubDate>Thu, 08 Jun 2023 19:20:00 +0000</pubDate>
    <media:content url="https://srijan.ch/media/pages/blog/exploring-conflicting-oneshot-services-in-systemd/0c15993753-1699621096/systemd-conflicts-01.png" medium="image" />
    <content:encoded><![CDATA[<figure data-ratio="auto">
    <img src="https://srijan.ch/media/pages/blog/exploring-conflicting-oneshot-services-in-systemd/0c15993753-1699621096/systemd-conflicts-01.png" alt="Exploring conflicting oneshot services in systemd">
  
    <figcaption class="text-center">
    Midjourney: two systemd services fighting over who will start first  </figcaption>
  </figure>
<h2>Background</h2>
<p>I use <a href="https://isync.sourceforge.io/mbsync.html" rel="noreferrer">mbsync</a> to sync my mailbox from my online provider (<a href="https://ref.fm/u12054901" rel="noreferrer">FastMail</a> - referer link) to my local system to eventually use with <a href="https://djcbsoftware.nl/code/mu/mu4e.html" rel="noreferrer">mu4e</a> (on Emacs).</p> <p>For periodic sync, I have a systemd service file called <code>mbsync.service</code> defining a oneshot service and a timer file called <code>mbsync.timer</code> that runs this service periodically. I can also activate the same service using a keybinding from inside mu4e.</p><figure>
  <pre><code class="language-ini">[Unit]
Description=Mailbox synchronization service
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/bin/mbsync fastmail-all
ExecStartPost=bash -c &quot;emacsclient -s srijan -n -e &#039;(mu4e-update-index)&#039; || mu index&quot;

[Install]
WantedBy=default.target</code></pre>
    <figcaption class="text-center">mbsync.service</figcaption>
  </figure>
<figure>
  <pre><code class="language-ini">[Unit]
Description=Mailbox synchronization timer
BindsTo=graphical-session.target
After=graphical-session.target

[Timer]
OnBootSec=2m
OnUnitActiveSec=5m
Unit=mbsync.service

[Install]
WantedBy=graphical-session.target</code></pre>
    <figcaption class="text-center">mbsync.timer</figcaption>
  </figure>
<p>Also, for instant download of new mail, I have another service called <a href="https://gitlab.com/shackra/goimapnotify" rel="noreferrer">goimapnotify</a> configured that listens for new/updated/deleted messages on the remote mailbox using IMAP IDLE, and calls the above <code>mbsync.service</code> when there are changes.</p><p>This has worked well for me for several years.</p><h2>The Problem</h2>
<p>I
 recently split my (huge) archive folder into yearly archives so that I 
can keep/sync only the recent years on my phone. [ Aside: <a href="https://fedi.srijan.dev/notice/AVGV5TuD1cOEWQ8iQa" rel="noreferrer">yearly refile in mu4e snippet</a>
 ]. This lead to an increase in the number of folders that mbsync has to
 sync, and this increased the time taken to sync because it syncs the 
folders one by one.</p> <p>It does have the feature to sync a subset of folders, so I created a second systemd service called <code>mbsync-quick.service</code>
 and only synced my Inbox from this service. Then I updated the 
goimapnotify config to trigger this quick service instead of the full 
service when it detects changes.</p> <p>But, this caused a problem: these
 two services can run at the same time, and hence can cause corruption 
or sync conflicts in the mail files. So, I wanted a way to make sure 
that these two services don't run at the same time.</p> <p>Ideally,
 whenever any of these services are triggered and the other service is 
already running, then it should wait for the other service to stop 
before starting, essentially forming a queue.</p><h2>Solution 1: Using systemd features</h2>
<p>Systemd has a <a href="https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Conflicts=" rel="noreferrer">way to specify conflicts</a> in the unit section. From the docs:</p><blockquote>
  If a unit has a<code>Conflicts=</code>setting on another unit, starting the former will stop the latter and vice versa.<br>[...] to ensure that the conflicting unit is stopped before the other unit is started, an<code>After=</code>or<code>Before=</code>dependency must be declared.  </blockquote>
<p>This
 is different from our requirement that the conflicting service should 
be allowed to finish before the triggered service starts, but maybe a 
good enough way to at least prevent both running at the same time.</p> <p>To test this, I added <code>Conflicts=</code>
 in both the services with the other service as the conflicting service,
 and it works. The only problem is that when a service is triggered, the
 other service is <code>SIGTERM</code>ed. This itself might not cause a 
corruption issue, but if this happens with the mbsync-quick service, 
then there might be a delay getting the mail.</p> <p>This is the best way
 I found that uses built-in systemd features without any workarounds or 
hacks. Other solutions below involve some workarounds.</p><h2>Solution 2: Conflict + stop after sync complete</h2>
<p>This
 is a variation on solution 1 - add a wrapper script to trap the SIGTERM
 and only exit when the sync is complete. This also worked.</p> <p>But, 
the drawback with this method is that anyone calling stop on these 
services (like the system shutting down) will have to wait for this to 
finish (or till timeout of 90s). This can cause slowdowns in system 
shutdown that are hard to debug. So, I don't prefer this solution.</p><h2>Solution 3: Delay start until the other service is finished</h2>
<p>This is also a hacky solution - use <code>ExecStartPre</code> to check if the other service is running, and busywait for it to stop before starting ourselves.</p><figure>
  <pre><code class="language-ini">[Unit]
Description=Mailbox synchronization service (quick)
After=network-online.target

[Service]
Type=oneshot
ExecStartPre=/bin/sh -c &#039;while systemctl --user is-active mbsync.service | grep -q activating; do sleep 0.5; done&#039;
ExecStart=/usr/bin/mbsync fastmail-inbox
ExecStartPost=bash -c &quot;emacsclient -s srijan -n -e &#039;(mu4e-update-index)&#039; || mu index&quot;</code></pre>
    <figcaption class="text-center">mbsync-quick.service</figcaption>
  </figure>
<p>Here, we use <code>systemctl is-active</code> to query the status of the other service, and wait until the other service is not in <code>activating</code> state anymore. The state is called <code>activating</code> instead of <code>active</code> because these are oneshot services that go from <code>inactive</code> to <code>activating</code> to <code>inactive</code> without ever reaching <code>active</code>.</p><p>To not make this an actual busywait on the CPU, I added a sleep of 0.5s.</p><p>This worked the best for my use case. When one of the services is triggered, it checks if the other service is running and waits for it to stop before running itself. It also does not have the drawback of solution 2 of trapping exits and delaying a stop command.</p><p>But, after using it for a day, I found there is a race condition (!) that can cause a deadlock between these two services and none of them are able to start.</p><p>The reason for the race condition was:</p><ul><li>A service is marked as <code>activating</code> when it's <code>ExecStartPre</code> command starts</li><li>I added a sleep of 0.5 seconds</li></ul><p>So, if the other service is triggered again in between those 0.5 seconds, both services will be marked as <code>activating</code> and they will indefinitely wait for each other to complete. This is what I get for using workarounds.</p><h2>Solution 4: One-way conflict, other way delay</h2>
<p>So,
 the final good-enough solution I came up with was to break this cyclic 
dependency by doing a hybrid of Solution 1 and Solution 3. I was okay 
with the <code>mbsync.service</code> being stopped for the (higher priority) <code>mbsync-quick.service</code>.</p> <p>So, I added <code>mbsync.service</code> in Conflicts section of <code>mbsync-quick.service</code>, and used the <code>ExecStartPre</code> method in <code>mbsync.service</code>.</p> <p>💡Let me know if you know a better way to achieve this.</p><h2>References</h2>
<ul><li><a href="https://unix.stackexchange.com/questions/503719/how-to-set-a-conflict-in-systemd-in-one-direction-only" rel="noreferrer">https://unix.stackexchange.com/questions/503719/how-to-set-a-conflict-in-systemd-in-one-direction-only</a></li><li><a href="https://unix.stackexchange.com/questions/465794/is-it-possible-to-make-a-systemd-unit-wait-until-all-its-conflicts-are-stopped/562959" rel="noreferrer">https://unix.stackexchange.com/questions/465794/is-it-possible-to-make-a-systemd-unit-wait-until-all-its-conflicts-are-stopped/562959</a></li></ul>]]></content:encoded>
    <comments>https://srijan.ch/exploring-conflicting-oneshot-services-in-systemd#comments</comments>
    <slash:comments>0</slash:comments>
  </item></channel>
</rss>
