<?xml version="1.0" encoding="UTF-8"?>
<rss 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:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" version="2.0">
  <channel>
    <title>misi.me</title>
    <link>https://misi.me/</link>
    <atom:link href="https://misi.me/feed.xml" rel="self" type="application/rss+xml"/>
    <description>Mihály Tóth-Tarsoly's personal website</description>
    <lastBuildDate>Thu, 05 Nov 2026 12:45:24 GMT</lastBuildDate>
    <language>en</language>
    <generator>Lume 3.3.3</generator>
    <author>
      <name>Mihály Tóth-Tarsoly</name>
    </author>
    <item>
      <title>Perl CGI in 2026</title>
      <link>https://misi.me/blog/cgi-scripts/</link>
      <guid isPermaLink="false">https://misi.me/blog/cgi-scripts/</guid>
      <description>Why I use CGI scripts for the backend functionality of this website.</description>
      <content:encoded>
        <![CDATA[<p>This website is built using an SSG<sup><a href="https://misi.me/blog/cgi-scripts/#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref aria-describedby="footnote-label">1</a></sup>: all the templates
are filled in with their content, and are then saved as static HTML code.
SSGs have the massive advantage that they don’t require a heavy server, or any additional
network round-trips to fetch the content for the page that my visitors are viewing.
Instead of the page downloading a posts list separately, it’s baked into the HTML so it’s
already there when the page loads. However, this has the downside that you are now limited
to showing the same content on every reload, until you rebuild the website.</p>
<p>As I was putting together this website and getting it ready for publication, there were a
couple of things I wanted to add that were inherently dynamic: a hit counter, and a
contact form. Today, a lot of people faced with this situation would reach for a web
framework like NextJS, perhaps combined with ‘serverless’ functions.<sup><a href="https://misi.me/blog/cgi-scripts/#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref aria-describedby="footnote-label">2</a></sup> However, as my
site was already nearing completion and I didn’t want to re-write it, I went looking for
alternatives, and I found one: Perl CGI scripts.</p>
<p><a href="https://en.wikipedia.org/wiki/Common_Gateway_Interface">CGI (Common Gateway Interface)</a>
is a web standard first developed in the 1990s that lets web servers (like the one hosting
the static site you’re now viewing) call into external programs to process web requests.
When a web request comes in for the CGI script, the web server calls the script, giving it
information about the request like the parameters, and then responds to the website viewer
with whatever output the script produced. Because this protocol is so simple, writing CGI
scripts is very straightforward.</p>
<p>Instead of needing a complicated web framework or serverless functions runtime that I’d
then be locked into, I can write simple one-file scripts that I can fully understand, with
no special execution environment needed. It meant that I could keep the existing static
site that I’d already built exactly the same, and add just those few bits of reactivity
that I wanted. For example, the visitor counter is implemented using a bit of Perl that
increments the counter in the database, and responds with an image rendering the count:</p>
<pre><code class="language-perl">my $dbh = DBI-&gt;connect("dbi:SQLite:dbname=$db", '', '', {
    # connection args omitted...
});
my ($count) = $dbh-&gt;selectrow_array(
	'UPDATE hits SET n = n + 1 WHERE id = 1 RETURNING n'
);

$cgi-&gt;set_response_type('image/png');
$cgi-&gt;add_response_header('Cache-Control' =&gt; 'no-store');
$cgi-&gt;render(data =&gt; SegmentedCounter::render($count));
</code></pre>
<p>That’s it! The code is maybe 50 lines in total. I did use a simple wrapper library around
common CGI tasks, <code>CGI::Tiny</code>, but it’s even possible to just <code>print</code> HTTP output to
<code>stdout</code> to use it as a response.</p>
<p>Of course, CGI scripts do have their own downsides, which is why they are
rarely used anymore for new projects. The main problem is latency: as the
web server has to spawn a new instance of the Perl interpreter every time a request comes in,
request responses are slower than with modern paradigms that use a prewarmed
worker pool to handle requests.</p>
<p>One of the biggest timesinks here can be Perl needing to load
all the included modules over and over again, so I would recommend keeping your scripts as
simple as possible and avoiding heavy frameworks. For example, I cut startup time from
94ms to 21ms by pruning the modules used. What we pay for in raw performance though, we
get back in readability and simplicity. Highly recommended!</p>
<section data-footnotes class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes</h2>
<ol>
<li id="user-content-fn-1">
<p>Static site generator. <a href="https://misi.me/blog/cgi-scripts/#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p>AWS Lambdas, Cloudflare Workers, Fly Machines, etc. <a href="https://misi.me/blog/cgi-scripts/#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]>
      </content:encoded>
      <pubDate>Tue, 03 Nov 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Light/dark mode on SVGs</title>
      <link>https://misi.me/blog/svg-colours/</link>
      <guid isPermaLink="false">https://misi.me/blog/svg-colours/</guid>
      <description>How to make SVG colours follow the site theme.</description>
      <content:encoded>
        <![CDATA[<p>Add a <code>fill="currentColor"</code> property to the root <code>&lt;svg&gt;</code> element.
Remove any <code>fill</code> properties on the paths inside.</p>
<p>On this site, I use a custom <code>Figure</code> component, that sets the <code>inline</code> property
on svgs. At build time, Lume’s <code>inline</code> plugin replaces the <code>&lt;img&gt;</code> element
with the SVG’s code.</p>
<p>In your CSS, you can add a rule for the svg:</p>
<pre><code class="language-css">.figure {
  color: var(--my-amazing-colour);
}
</code></pre>
<p>and you can have <code>--my-amazing-colour</code> change depending on
the theme.</p>
<p>Enjoy your newly flashbang-proof diagrams!</p>]]>
      </content:encoded>
      <pubDate>Fri, 30 Oct 2026 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>What's behind a ‘run tests’ button</title>
      <link>https://misi.me/blog/avon/</link>
      <guid isPermaLink="false">https://misi.me/blog/avon/</guid>
      <description>
        My reflections on designing and building a platform that can run lecturer-provided tests on Git repos. I go into the constraints involved, some designs that were considered, then elaborate on the final design that was chosen.
      </description>
      <content:encoded>
        <![CDATA[<figure class="epigraph"><blockquote class="flow"><p>And so, does the destination matter? Or is it the path we take? I declare that no accomplishment has substance nearly as great as the road used to achieve it. We are not creatures of destinations. It is the journey that shapes us. Our callused feet, our backs strong from carrying the weight of our travels, our eyes open with the fresh delight of experiences lived.</p></blockquote><figcaption><span aria-hidden="true">—</span> <span class="epigraph__author">Brandon Sanderson</span>, <cite>The Way of Kings</cite></figcaption></figure>
<p>Building a custom learning platform for my university sounded fairly straightforward at first.
The problem was that the client <em>also</em> wanted to be able to reliably run testing for hundreds of untrusted student code submissions in configurable environments.</p>
<div class="notice notice--warning" role="note"><img class="notice__icon notice__icon--light" src="https://misi.me/assets/pixel/notices/warning.png" alt="" width="64" height="64" draggable="false"><img class="notice__icon notice__icon--dark" src="https://misi.me/assets/pixel/notices/dark/warning.png" alt="" width="68" height="68" draggable="false"><div class="notice__body flow"><p class="notice__title">Not production-ready</p><p>The sandboxing setup that I describe in this post was built under time constraints for an MVP, and is
not suitable for running untrusted code in production. I talk about the tradeoffs further on.</p></div></div>
<h2>Client brief</h2>
<p><a href="https://github.com/toth-tarsoly/avon">Avon</a> was a project we built as part of Software Engineering Project, a year-long unit where our team was tasked with working with a real client to design a software system that would solve their problem.
Our client was the School of Computer Science at the University of Bristol, and the pitch was as follows:</p>
<figure class="blockquote"><blockquote class="flow"><p>A custom VLE<sup><a href="https://misi.me/blog/avon/#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref aria-describedby="footnote-label">1</a></sup> that can integrate with GitLab, automatically provision Git repos for students, and run tests on code submitted for coursework.</p></blockquote></figure>
<p>The rough sketch that we came up with was that lecturers would create courseworks using the UI, providing a template repository.<sup><a href="https://misi.me/blog/avon/#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref aria-describedby="footnote-label">2</a></sup>
Then, our system would provision repositories on a Git forge (GitLab in our case) for each student, using that template.
Lecturers would be able to select students and start a <em>test batch</em>, and they would be able to see the results of running the test script on each student’s repository.
Simple enough, right?</p>
<h2>The suffering begins</h2>
<p>Initially, we thought that this would be fairly simple to implement. This is because there are already tools designed explicitly for running user-defined tasks on code stored in a Git forge: CI systems. Sadly, most of these tools require you to configure them within the repository itself, which wouldn’t have worked when students have access to that.<sup><a href="https://misi.me/blog/avon/#user-content-fn-3" id="user-content-fnref-3" data-footnote-ref aria-describedby="footnote-label">3</a></sup> Also, the tools that we evaluated didn’t work with our security model that the code in the repository could be overtly malicious, and this is problematic in our case as the CI runner would need to have credentials to be able to report results back to Avon.
No such luck.</p>
<p>Because I suspected that designing a system that could satisfy these constraints would be a <em>nightmare</em>, I did quite a bit of research to find existing platforms that might be more aligned with our use case.<sup><a href="https://misi.me/blog/avon/#user-content-fn-4" id="user-content-fnref-4" data-footnote-ref aria-describedby="footnote-label">4</a></sup>
One promising candidate was a Kubernetes-based platform called <a href="https://argoproj.github.io/workflows/">Argo Workflows</a>, which caught my eye because it would have handled things like cloning the student’s repository into the container automatically, so we would need less custom logic.
Unfortunately, we weren’t able to use it, primarily because it was quite heavy and complicated to use, in addition to having an opinionated API that makes sense for some use cases, but not for ours. We also couldn’t come up with a way to connect it to our backend and have it update the state of the test run asynchronously, rather than needing polling.</p>
<p>I also considered just having a simple service that accepts jobs, then locally clones the repo and runs the test command inside it, collecting the results. But because these submissions will come from students, we need some isolation to ensure that enterprising individuals can’t run a Bitcoin miner on our testing system, or gain access to data they shouldn’t. This began to point me towards using Docker.</p>
<p>Docker was also appealing because it would give us a way to run test jobs in parallel easily; some of the test suites that we were working with are quite heavy, and often containers took about thirty to forty seconds to start up, prepare the environment, run the tests, and report the results. With Docker, we can leverage AWS ECS to handle as many jobs as we need, and it was convenient (mostly) to hook up to the rest of our infrastructure on AWS.</p>
<p>Something else that we had to consider was that each coursework is different, and requires different dependencies, testing and language tools, <em>et cetera</em>. This means that lecturers need a way to be able to define the environment that their tests will run in, as well as the specific commands that will be run to perform the tests. An initial design involved giving lecturers access to the Dockerfile that would be used, but after some time thinking about it, I came to the conclusion that it wouldn’t be feasible. This is because we need to inject instructions to clone the repo, collect the results, and report them, and this wouldn’t be possible with a lecturer-controlled file. The design we ended up with was one where there are multiple pre-set environment options that a lecturer can choose from.<sup><a href="https://misi.me/blog/avon/#user-content-fn-5" id="user-content-fnref-5" data-footnote-ref aria-describedby="footnote-label">5</a></sup></p>
<h2>Saying goodbye to our majestic monolith</h2>
<p>At the point when a team member and I began working on the testing engine, the architecture of our platform looked roughly like this:</p>
<figure class="figure figure--wide"><img class="figure__image" src="https://misi.me/assets/images/initial_architecture.svg" alt="Diagram of Avon's initial monolithic architecture" loading="lazy" decoding="async" inline role="img" aria-label="Diagram of Avon's initial monolithic architecture"><figcaption><span class="figure__caption">Our initial architecture. Simple, isn't it? How naïve we were.</span></figcaption></figure>
<p>After the implementation of the testing engine, the architecture of the backend looked like this:</p>
<figure class="figure figure--wide"><img class="figure__image" src="https://misi.me/assets/images/testing_architecture.svg" alt="Diagram of Avon's final queue-centred testing architecture" loading="lazy" decoding="async" inline role="img" aria-label="Diagram of Avon's final queue-centred testing architecture"><figcaption><span class="figure__caption">The final testing architecture.</span></figcaption></figure>
<p>A queue-centric architecture emerged naturally here, because it elegantly solved several problems that I anticipated.
If the backend ever died, any results would simply wait in the queue until it came back online to process any results.
In addition, multiple backend copies could run at the same time, and it would be safe for any of them to process results and persist them to the same database. This is because even if two backends try to process the same result, or one dies halfway through processing one, we can just discard duplicates.</p>
<p>Unfortunately, even though this architecture is elegant and decoupled, it introduced significant challenges when debugging.
Now, in addition to all the <em>usual</em> places where our code can fail, we introduced ten more!<sup><a href="https://misi.me/blog/avon/#user-content-fn-6" id="user-content-fnref-6" data-footnote-ref aria-describedby="footnote-label">6</a></sup></p>
<h3>What actually happens when you click ‘run tests’?</h3>
<p>To help understand why debugging this system was so difficult, it might help if I explain what the ‘run tests’ button in the UI kicks off. Here goes:</p>
<ol>
<li><strong>Lecturer starts the test batch</strong>. This is the part that you see - you select student groups in the UI to run tests for, and you click ‘run’.</li>
<li><strong>Backend starts up jobs</strong>. The backend (FastAPI) connects to AWS and makes an API call to start up the testing jobs in the testing cluster. We start up one job for every repo in the test batch; they all run in parallel. The test jobs are now stored as ‘started’ in the database, and we attach the ARNs that we receive from the AWS API.</li>
<li><strong>Containers start</strong>. At this point, the FastAPI backend has already moved on. In the background, the containers have all started running. We have some code written that logs into GitLab using a Service Account, and clones the student repo into the container so the tests can access it. Then, we run the test command that the lecturer provided in a subprocess with the parent process’ environment variables removed.<sup><a href="https://misi.me/blog/avon/#user-content-fn-7" id="user-content-fnref-7" data-footnote-ref aria-describedby="footnote-label">7</a></sup></li>
<li><strong>Tests execute</strong>. As the tests run, we collect any logs that are produced and store them to a file.</li>
<li><strong>Tests finish</strong>. When the tests finish, we take the log file and upload it to S3. The URI of the S3 resource and the exit code of the command that the lecturer provided is then sent in an SQS message.</li>
<li><strong>Backend persists results</strong>. A worker consumes the SQS queue. It receives the message and persists the results to the PostgreSQL database, with the appropriate status and log file URI. This status will then appear in the UI, which will poll for updates while viewing the test run.<sup><a href="https://misi.me/blog/avon/#user-content-fn-8" id="user-content-fnref-8" data-footnote-ref aria-describedby="footnote-label">8</a></sup> The worker is designed to be idempotent, and discard any duplicates that may be received.</li>
</ol>
<p>Sadly, none of this project (except perhaps the initial CRUD routes) could be said to have been plain sailing, and that’s what I’ll talk about next.</p>
<h3>Nothing ever works</h3>
<p>As you can probably imagine from that workflow, there are lots of moving parts here, and each one can fail in unique and fun ways that are difficult to observe. The hardest part of debugging a distributed system like this is that failures become distributed too. When you run something locally, or as part of a monolith, problems are (usually) visible immediately, and in one place.
With something like this, often the most you’ll get is just <em>nothing</em>, and we just have to work from there: hunting through AWS container logs, and hoping that we had the foresight to instrument things.</p>
<p>For example, when testing an early version of the system, we clicked go, but it just got stuck and nothing happened. I checked the AWS console, and the container did start, and even exited as well. In the logs, the tests completed, and it looked like everything was okay. Eventually, I worked out that the problem was that the container didn’t have permission to upload to S3, or send results to the queue, because I had incorrectly configured the IAM role’s permissions. This particular issue was especially difficult to debug because every change required the registration of a new Task Definition Revision. To avoid this kind of issue in the future, I added a step to the container startup that tested the AWS connection before doing the tests, with helpful log output.</p>
<p>This was the first exposure that I (and indeed the rest of the team) had to queue-driven systems like this, and it was a great introduction to how with distributed systems, it’s less about getting it working, and more so about reasoning through the often complex ways in which it can fail.</p>
<h2>Conclusion</h2>
<p>In the end, I did manage to get it working. We were able to demonstrate starting test runs, looking at test run logs in the UI, and seeing it automatically refresh when the run completes. It shipped, and the client was happy. Even though this project was only a proof-of-concept, and it might never be used, I’m still really glad I picked it at the start of the year. In the end, the user interface still just has a ‘run tests’ button, but the machinery underneath it turned out to be far more complicated than I had expected.</p>
<p>Despite being frustrating at times, this project taught me better than any other I’ve built how to ruthlessly separate ‘nice-to-have’ and ‘need-to-have’. In the beginning, we had a lot of ideas and features that we wanted to implement properly, like sandboxing and real-time log streaming, but when deadlines started approaching, it became necessary for us to sit down with the client, understand what it is that they <em>really</em> want, and ship something that works, even if it isn’t perfect. This is something that has changed how I approach planning projects today, and I’m grateful for it.</p>
<section data-footnotes class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes</h2>
<ol>
<li id="user-content-fn-1">
<p>Virtual learning environment. <a href="https://misi.me/blog/avon/#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p>This would contain, for example, the skeleton code for a programming coursework. <a href="https://misi.me/blog/avon/#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-3">
<p>It may have been possible to get around this by injecting the CI config automatically when tests are run, and keeping it out of the repo otherwise, but that would be <em>really</em> complicated. <a href="https://misi.me/blog/avon/#user-content-fnref-3" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-4">
<p>Some systems that we explored included E2B, Deno Sandboxes, and several others. Unfortunately, none of these platforms were really what we were looking for, as they seemed more focussed on different use-cases, such as providing a workspace for AI agents. <a href="https://misi.me/blog/avon/#user-content-fnref-4" data-footnote-backref="" aria-label="Back to reference 4" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-5">
<p>These correspond to individual Dockerfiles that we set up, that each installed a different set of dependencies. <a href="https://misi.me/blog/avon/#user-content-fnref-5" data-footnote-backref="" aria-label="Back to reference 5" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-6">
<p><em>Much</em> time was spent debugging this. Especially AWS. This was our first time using it, and ‘into the deep end’ is an understatement. It is an engineering marvel, and exceptionally powerful, but I think we had to re-configure everything at least four times before it worked. <a href="https://misi.me/blog/avon/#user-content-fnref-6" data-footnote-backref="" aria-label="Back to reference 6" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-7">
<p>I did attempt some level of hardening here. Using <code>tini</code>, we handle signal forwarding, and prevent leftover zombie processes. Unfortunately, due to deadline constraints, I wasn’t able to explore proper sandbox hardening as much as I would have liked. In particular, something like <a href="https://firecracker-microvm.github.io/">Firecracker</a> would be a much more secure sandboxing primitive to use here, and it was something that I did consider, but it is far more complicated to get started with, and due to time constraints it wasn’t possible. <a href="https://misi.me/blog/avon/#user-content-fnref-7" data-footnote-backref="" aria-label="Back to reference 7" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-8">
<p>This was another compromise: we decided that for this project, polling would certainly be acceptable. We considered WebSockets or SSE but it would have been very complicated for little benefit. <a href="https://misi.me/blog/avon/#user-content-fnref-8" data-footnote-backref="" aria-label="Back to reference 8" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]>
      </content:encoded>
      <pubDate>Thu, 21 May 2026 00:00:00 GMT</pubDate>
    </item>
  </channel>
</rss>