<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Jan Hensel</title>
    <link rel="self" type="application/atom+xml" href="https://hensel.dev/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://hensel.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-06-11T00:00:00+00:00</updated>
    <id>https://hensel.dev/atom.xml</id>
    <entry xml:lang="en">
        <title>Mountain peak with clouds from Innsbruck</title>
        <published>2026-06-11T00:00:00+00:00</published>
        <updated>2026-06-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/mountain-peak-clouds-innsbruck-bw/"/>
        <id>https://hensel.dev/photography/mountain-peak-clouds-innsbruck-bw/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/mountain-peak-clouds-innsbruck-bw/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Sparrow (passer domesticus) ready to pounce</title>
        <published>2026-06-08T00:00:00+00:00</published>
        <updated>2026-06-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/sparrow-ready-to-pounce/"/>
        <id>https://hensel.dev/photography/sparrow-ready-to-pounce/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/sparrow-ready-to-pounce/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Yellow-legged gull (larus michahellis) in flight</title>
        <published>2026-06-06T00:00:00+00:00</published>
        <updated>2026-06-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/yellow-legged-gull-in-flight/"/>
        <id>https://hensel.dev/photography/yellow-legged-gull-in-flight/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/yellow-legged-gull-in-flight/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Tower cranes in sunset at Munich central station</title>
        <published>2026-06-05T00:00:00+00:00</published>
        <updated>2026-06-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/tower-cranes-sunset-munic-hbf/"/>
        <id>https://hensel.dev/photography/tower-cranes-sunset-munic-hbf/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/tower-cranes-sunset-munic-hbf/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Carrion crow (corvus corone) landing in windy conditions</title>
        <published>2026-05-31T00:00:00+00:00</published>
        <updated>2026-05-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/carrion-crow-stormy-landing/"/>
        <id>https://hensel.dev/photography/carrion-crow-stormy-landing/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/carrion-crow-stormy-landing/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Rose and corrosion</title>
        <published>2026-05-31T00:00:00+00:00</published>
        <updated>2026-05-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/rose-and-corrosion/"/>
        <id>https://hensel.dev/photography/rose-and-corrosion/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/rose-and-corrosion/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Morning meadow impression</title>
        <published>2026-05-29T00:00:00+00:00</published>
        <updated>2026-05-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/morning-meadow-impression/"/>
        <id>https://hensel.dev/photography/morning-meadow-impression/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/morning-meadow-impression/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Common gull (larus canus) over the Weser at sunset</title>
        <published>2026-05-24T00:00:00+00:00</published>
        <updated>2026-05-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/common-gull-weser-sunset/"/>
        <id>https://hensel.dev/photography/common-gull-weser-sunset/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/common-gull-weser-sunset/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Canada geese (branta canadensis) over clouds in Blockland</title>
        <published>2026-05-20T00:00:00+00:00</published>
        <updated>2026-05-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/canada-geese-cloud-blockland/"/>
        <id>https://hensel.dev/photography/canada-geese-cloud-blockland/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/canada-geese-cloud-blockland/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Sedge warbler (acrocephalus schoenobaenus) on a diagonal reed at Kuhgrabenweg</title>
        <published>2026-05-20T00:00:00+00:00</published>
        <updated>2026-05-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/sedge-warbler-diagonal-reed-kuhgrabenweg/"/>
        <id>https://hensel.dev/photography/sedge-warbler-diagonal-reed-kuhgrabenweg/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/sedge-warbler-diagonal-reed-kuhgrabenweg/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Unisee dock album cover</title>
        <published>2026-05-20T00:00:00+00:00</published>
        <updated>2026-05-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/unisee-dock-album-cover/"/>
        <id>https://hensel.dev/photography/unisee-dock-album-cover/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/unisee-dock-album-cover/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Carrion crow (corvus corone) with loot</title>
        <published>2026-05-10T00:00:00+00:00</published>
        <updated>2026-05-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/carrion-crow-loot/"/>
        <id>https://hensel.dev/photography/carrion-crow-loot/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/carrion-crow-loot/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>European robin (erithacus rubecula) in song towards the setting sun</title>
        <published>2026-05-10T00:00:00+00:00</published>
        <updated>2026-05-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/robin-in-song-towards-sunset/"/>
        <id>https://hensel.dev/photography/robin-in-song-towards-sunset/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/robin-in-song-towards-sunset/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Blue tit (cyanistes caeruleus) by the Unisee</title>
        <published>2026-05-09T00:00:00+00:00</published>
        <updated>2026-05-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/blue-tit-unisee/"/>
        <id>https://hensel.dev/photography/blue-tit-unisee/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/blue-tit-unisee/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Young roe deer (capreolus capreolus) buck in a Bürgerpark meadow</title>
        <published>2026-05-09T00:00:00+00:00</published>
        <updated>2026-05-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/young-roe-deer-buck-bpark-meadow/"/>
        <id>https://hensel.dev/photography/young-roe-deer-buck-bpark-meadow/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/young-roe-deer-buck-bpark-meadow/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Software Bills of Materials: Motivation, Formats, Tools, and Challenges</title>
        <published>2024-07-01T00:00:00+00:00</published>
        <updated>2024-07-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            Jan Hensel
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/papers/sbom-motivations-and-challenges/"/>
        <id>https://hensel.dev/papers/sbom-motivations-and-challenges/</id>
        
        <content type="html" xml:base="https://hensel.dev/papers/sbom-motivations-and-challenges/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Macaw peeking out from behind a leaf</title>
        <published>2024-04-03T00:00:00+00:00</published>
        <updated>2024-04-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/photography/macaw-peeking-out-from-behind-leaf/"/>
        <id>https://hensel.dev/photography/macaw-peeking-out-from-behind-leaf/</id>
        
        <content type="html" xml:base="https://hensel.dev/photography/macaw-peeking-out-from-behind-leaf/"></content>
        
    </entry>
    <entry xml:lang="en">
        <title>Survey of Automated Agents and Spam on GitHub</title>
        <published>2024-03-15T00:00:00+00:00</published>
        <updated>2024-03-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            Jan Hensel
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/papers/github-sbots-analysis-2024/"/>
        <id>https://hensel.dev/papers/github-sbots-analysis-2024/</id>
        
        <content type="html" xml:base="https://hensel.dev/papers/github-sbots-analysis-2024/">&lt;h2 id=&quot;some-context-for-the-paper&quot;&gt;Some Context for the Paper&lt;&#x2F;h2&gt;
&lt;p&gt;While studying computer science I also took some courses outside of that realm.
One such course was in the field of social media and communications studies, a field somewhat closely related to sociology, on the topic of social bots, ie. automated agents acting in social networks.&lt;&#x2F;p&gt;
&lt;p&gt;While I do not use many social platforms, one sneakily social platform that I &lt;em&gt;do&lt;&#x2F;em&gt; use a lot is GitHub.
Looking into it, I found that people had already analyzed and codified GitHub as a &quot;social platform&quot; years prior, but I could not find any paper discussing its social automation.&lt;&#x2F;p&gt;
&lt;p&gt;Of course, especially malicious social automation is ever-present and I quickly found a spam campaign to analyze;
as luck would have it, just a few days into data gathering I actually found a second campaign which seems to be related to the first, as its methodology was strikingly similar.&lt;&#x2F;p&gt;
&lt;p&gt;In this paper, which tries to fit in with the social sciences context for which it is written, I therefore relate GitHub as a social platform with its types of automation to the terminology of the study of automated social agents (social bots) and analyze those spam campaigns in some detail.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;data&quot;&gt;Data?&lt;&#x2F;h3&gt;
&lt;p&gt;If you are a researcher or otherwise interested in the data, I might be able to help you;
Feel free to reach out.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;methodological-sidenote&quot;&gt;Methodological sidenote&lt;&#x2F;h3&gt;
&lt;p&gt;My methodology for data gathering was a bit haphazard (in my honest opinion) and I wrote a &lt;a href=&quot;&#x2F;blog&#x2F;doing-way-too-much-in-bash&quot;&gt;brief blog post&lt;&#x2F;a&gt; one some of the complications of it, however it is intended to be a fun little primer on concurrency.&lt;&#x2F;p&gt;
&lt;p&gt;If you happen to have any questions about how to get data from GitHub at some (moderate) scale and would like some advice, feel free to reach out as well.
I&#x27;m hardly an expert, but I do understand that especially for non-programmers it might be daunting to try and get data from a foreign platform.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Clinging Way Harder to Bash than Anyone Ever Should</title>
        <published>2024-02-25T00:00:00+00:00</published>
        <updated>2024-02-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Jan Hensel
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hensel.dev/blog/doing-way-too-much-in-bash/"/>
        <id>https://hensel.dev/blog/doing-way-too-much-in-bash/</id>
        
        <content type="html" xml:base="https://hensel.dev/blog/doing-way-too-much-in-bash/">&lt;h3 id=&quot;how-it-started&quot;&gt;How it started&lt;&#x2F;h3&gt;
&lt;p&gt;I&#x27;ve been working on &lt;a href=&quot;&#x2F;papers&#x2F;github-sbots-analysis-2024&quot;&gt;a paper about spam on GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;When it came to gathering some data I figured, as you do in academia, it doesn&#x27;t have to be pretty, I&#x27;ll just bang something together in Bash with &lt;code&gt;curl&lt;&#x2F;code&gt; and &lt;code&gt;jq&lt;&#x2F;code&gt; and all the assorted Unix tooling and I know I&#x27;ll get my data out of it just fine.&lt;&#x2F;p&gt;
&lt;p&gt;Normally when I have a thought like this, it turns out, uh oh, things are way more complicated than they seem and, uh oh, the complexity demons invade my peaceful existence.
But this is not actually what happened this time!
This time I had it right, gathering the data was kind of a breeze and all it really took was gluing some scripts together and playing around with the gathered data a bit and hitting the 5K-request rate limit a few times (predictably).&lt;&#x2F;p&gt;
&lt;p&gt;But when the complexity demons keep their distance it is not because they have lost, but because they know they have already won the battle in your mind, long ago.&lt;&#x2F;p&gt;
&lt;p&gt;Perhaps because of SWE conditioning, perhaps banal perfectionism and an irresistible urge to tinker with any problem I&#x27;m working on until it breaks, I felt the need to make my code more seamless (less seamful?), more integrated.
I tell myself that I already did 90% of the work by getting it to work and the last few steps of integration would be disproportionately valuable when having to re-use this in the future.
Valuable to who and in what future will they use my hacky scripts to make GitHub REST API calls? 🤷 Oh well...&lt;&#x2F;p&gt;
&lt;p&gt;So I set out to make it all work &lt;em&gt;smoothly&lt;&#x2F;em&gt;.
But other demons have invaded my mind as well, and they tell me that I cannot justify rewriting my GitHub API scraping project in a proper language™ like Go.
While they are sometimes defeated by the &quot;it would be so much easier to rewrite this from scratch&quot; demons, this time they persevered and so I set out to just try to gently mould the existing state of my scripts into something that I can just set off and forget and it will reliably get me the data from the API without making GitHub ban my account for spamming them with a trillion requests or something.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;how-it-s-going&quot;&gt;How it&#x27;s going&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;GH_REQ_LOG&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;GH_REQ_LOG&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:-&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;false&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;GH_REQ_LOG_LOCK&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;tmp&#x2F;gh_req_log.lock&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;ghreqlog&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  if&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;GH_REQ_LOG&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;false&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; ]&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; then&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    return&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  fi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;  MSG&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; flock&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;GH_REQ_LOG_LOCK&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;    -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;-command&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;echo &lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;\$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;MSG&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &amp;gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;GH_REQ_LOG&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I think the compulsion to write a function like this in a Bash script is a pretty surefire way to know you should probably have switched to a proper language™ a while back.
Just to be clear, I don&#x27;t think this is some mighty-impressive Bash-wizardry, just a misguided attempt by a misguided young man to reconcile all his poor decisions.
And it worked!&lt;&#x2F;p&gt;
&lt;p&gt;So what&#x27;s happening here, exactly?
This is just a logging function in order to debug a bit, so you call it like &lt;code&gt;ghreqlog &quot;it is working&quot;&lt;&#x2F;code&gt; and that&#x27;s it, but it doesn&#x27;t log to STDOUT but instead to the specified log file &lt;code&gt;$GH_REQ_LOG&lt;&#x2F;code&gt;, assuming that one was specified (the &lt;code&gt;${FOO:-bar}&lt;&#x2F;code&gt; syntax means that if &lt;code&gt;$FOO&lt;&#x2F;code&gt; is not defined, use the literal &lt;code&gt;bar&lt;&#x2F;code&gt;, so we have the logging disabled by default).&lt;&#x2F;p&gt;
&lt;p&gt;But that&#x27;s not all, is it...? What&#x27;s this &lt;code&gt;flock&lt;&#x2F;code&gt; business?&lt;&#x2F;p&gt;
&lt;p&gt;Ah well, you see, that&#x27;s where my genius comes in.
If I made GitHub API calls &lt;em&gt;sequentially&lt;&#x2F;em&gt;, one after the other, each taking, say 0.2 seconds, it would take &lt;em&gt;minutes&lt;&#x2F;em&gt; to get 5000 API calls done, over sixteen minutes in fact!
Instead, it is much more efficient to spend at least an hour crafting the scripts such that they leverage the power of &lt;code&gt;parallel&lt;&#x2F;code&gt; and dive into the realm of concurrency (or is it parallelism?).
That way, my requests will be done in, well, also minutes, but fewer minutes most likely.
Plus it enables us to hit the secondary rate limit on the GitHub REST API, something normally reserved for DDoSers and those who wrote their code so poorly they accidentally seemed as such... well, that&#x27;s probably been me at some point in this endeavour.&lt;&#x2F;p&gt;
&lt;p&gt;So, of course, concurrency is where all the trouble begins, not necessarily with doing things at the same time, but with having to &lt;em&gt;synchronize&lt;&#x2F;em&gt; between them after all!
At first it always seems like you can set it all off and forget it but then there is that one pesky little edge case, always.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;parallel&lt;&#x2F;code&gt; is a neat tool, and one of its less practical but very cute features is &lt;code&gt;--bar&lt;&#x2F;code&gt; which simply displays a progress bar across the width of your terminal.
Realistically, my goal here was less some lofty pristine error free data-gathering framework that could be used for generations to come and more just to see the little bar make it smoothly all the way across my screen &lt;em&gt;without manual intervention&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;To make that happen, first let&#x27;s look at what would happen if we just let it run.
To be clear, my the central part of the scripts that ends up invoking requests is essentially this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;cat&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; $&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;list_of_usernames&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; parallel&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;-bar&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;-halt&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; soon,fail=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; get_userinfo_from_github&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;By default &lt;code&gt;parallel&lt;&#x2F;code&gt; actually just runs through all your data, even if some commands fail, but for talking to the API I felt it best to abort mission once something &lt;em&gt;truly&lt;&#x2F;em&gt; went wrong to avoid getting API-banned because I made the same request 10K times in 8 seconds or something.
So we &lt;code&gt;--halt soon,fail=1&lt;&#x2F;code&gt;, meaning we halt when 1 job fails but only &lt;code&gt;soon&lt;&#x2F;code&gt;, so letting any running jobs finish (to avoid half-writing response objects, I suppose).&lt;&#x2F;p&gt;
&lt;p&gt;So what can go wrong?
Of course, we could simply not have any connectivity to GitHub but let&#x27;s set that aside for a second and assume we do.
The user we are requesting could certainly have removed their account at this point; these are people targeted in spam attacks (that GitHub does woefully little to effectively prevent) so for that reason or any other they may just call it quits or get banned or something.
In that case we get a &lt;code&gt;404&lt;&#x2F;code&gt; back from the API.
Besides that, we could exceed a rate limit, in which case we get back a &lt;code&gt;403&lt;&#x2F;code&gt; (or, according to the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.github.com&#x2F;en&#x2F;rest&#x2F;using-the-rest-api&#x2F;rate-limits-for-the-rest-api?apiVersion=2022-11-28#exceeding-the-rate-limit&quot;&gt;docs&lt;&#x2F;a&gt;, a &lt;code&gt;429&lt;&#x2F;code&gt;, which I have never gotten).&lt;&#x2F;p&gt;
&lt;p&gt;Our interest, foolishly, is to catch these error cases correctly and appropriately handle them.
Of course, there is not much to do when a user removes their account, and a simple tolerance of this case with a warning back to us that there was a &lt;code&gt;404&lt;&#x2F;code&gt; is probably the best we can do.
But when we get rate-limited, we have a couple of competing interests.&lt;&#x2F;p&gt;
&lt;p&gt;If we lived in a sequential world (&lt;code&gt;while read user ; do get_info $user ; done&lt;&#x2F;code&gt;) we could just communicate the problem via the exit code and in the case of a rate-limit, take the necessary wait time here and retry later, hardly so bad.
But we are parallelized, and there&#x27;s really no way to communicate to parallel that &quot;if this command fails with this exit state wait with any future commands until this long, if it fails in this other way, wait for that long, if it fails like this, that&#x27;s not even a failure so just keep going, and if it fails any other way then abort&quot;.
(At least I think there isn&#x27;t any way to do this, but at this point nothing surprises me about these tools anymore.)&lt;&#x2F;p&gt;
&lt;p&gt;So what we ought to do is handle this gracefully in our request-making function.
On the level of an individual request there&#x27;s nothing much more to do than &lt;code&gt;if it_went_wrong_and_you_should_wait ; then wait ; fi&lt;&#x2F;code&gt;.
But the individual instance of the request-making function is not, in fact, unrelated to all the other instances of it, due to the rate limit.
So there is this pesky hidden fact that says we actually cannot consider these otherwise unrelated actions as unrelated.
If we tried to see them this way, then while one instance might hit the rate limit and realize to wait (just fine) and then the next one, started at the same time also hits it and waits (just fine), but eventually &lt;code&gt;parallel&lt;&#x2F;code&gt; starts a new instance for us making yet another request that is subject to the same rate limit yet has never been told that it should actually wait and not make yet another request that might get us banned.&lt;&#x2F;p&gt;
&lt;p&gt;So what is to be done?
Of course, to synchronize a bit between these requests, i.e., give those later requests access to the information that we&#x27;re currently on hold so as not to anger the API overlords.
We&#x27;ll want some sort of indicator that tells future requests to cool it until &lt;code&gt;$later&lt;&#x2F;code&gt;&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-later-1&quot;&gt;&lt;a href=&quot;#fn-later&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;How we tell ourselves this could just be a file, couldn&#x27;t it?
After all, even though we are a bunch of programs executing in parallel, we do share a file system.
This is true, and it would probably even work in practice (as far as I can see) because we wouldn&#x27;t expect the GitHub API to tell us significantly different reset-times shortly once after the other.
But it would also be oh so very wrong, because we are using no mechanism to synchronize.
Technically there&#x27;s plenty of mechanism that synchronize things when we use the file system on Linux, but not on the level of our program.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s say we get told by the API &quot;too fast, don&#x27;t talk to me again until 2PM&quot; and note this down in a file so the other programs know and we wait and then by 2PM we talk again and it works and so we tell all the other programs it&#x27;s all clear again (by removing the file) and it should be all just fine, right?
Wrong, of course, because if, after we we wake up and make our request but before we remove the file, the API tells another instance of our program to wait until 3PM, so that program stores this to the file, and then we immediately remove it!
Now we might spam another 20-30 API calls and get this much closer to being banned!&lt;&#x2F;p&gt;
&lt;p&gt;And if you say &quot;ah, but you just need to read the file to see if the value is still 2PM, so you know you&#x27;re removing the relevant warning only&quot;, the situation is actually no different at all when considering this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  A                    B                    C&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-------------------  ------------------  -----------------&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;make request ❌&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;hear 2PM&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;no warning exists&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;write warning 2PM&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sleep til 2PM&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;make request ✅&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;read warning         make request ❌&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                     hear 3PM&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;(stalls for          read warning&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; whatever reason)    warning says 2PM&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                     write 3PM warning&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;remove warning now&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;thinking it was 2PM&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                                         sees no warning&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                                         gets us banned T.T&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It&#x27;s textbook, and the textbook also has a solution: Atomicity!
We need to know that whatever we read has not been changed by the time we write.
If somebody tries to write after we read but before we wrote, we want them to be told to wait for a moment until we did our write.&lt;&#x2F;p&gt;
&lt;p&gt;And &lt;em&gt;that&#x27;s&lt;&#x2F;em&gt; what &lt;code&gt;flock&lt;&#x2F;code&gt; gives us!
With &lt;code&gt;flock&lt;&#x2F;code&gt;, we can specify a file in the file system as a &lt;em&gt;lock variable&lt;&#x2F;em&gt; and then do any operation and however long we take to do that operation, if anybody else tries to do something that also has to claim the lock, they will be made to wait until we are done with our thing.
Of course, this only works when you remember to actually lock the (right) variable in all the places where it matters, which is the reason that concurrency is so hard.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Author&#x27;s note&lt;&#x2F;em&gt;:&lt;&#x2F;p&gt;
&lt;p&gt;I might extend this post in the future, or write a follow-up; for now, I&#x27;m happy to get it out of my TODO-list.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-later&quot;&gt;
&lt;p&gt;With the GitHub API, when you hit the secondary rate limit you get a &lt;code&gt;retry-after&lt;&#x2F;code&gt; HTTP header which you should adhere to (I think it always says &lt;code&gt;60&lt;&#x2F;code&gt;, meaning sixty seconds), while for the primary rate limit you should listen to the &lt;code&gt;x-ratelimit-reset&lt;&#x2F;code&gt; header. &lt;a href=&quot;#fr-later-1&quot;&gt;↩&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;section&gt;
</content>
        
    </entry>
</feed>
