<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Kiran PK]]></title><description><![CDATA[Kiran PK]]></description><link>https://blog.kiranpk.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 12 Apr 2026 16:36:36 GMT</lastBuildDate><atom:link href="https://blog.kiranpk.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[File Signature: How Systems Actually Detect File Types]]></title><description><![CDATA[Most people assume the file extension tells the system what type of file it is. It's an intuitive assumption — .png means image, .pdf means document, .exe means executable. At first glance, this seems]]></description><link>https://blog.kiranpk.dev/file-signature-how-systems-actually-detect-file-types</link><guid isPermaLink="true">https://blog.kiranpk.dev/file-signature-how-systems-actually-detect-file-types</guid><category><![CDATA[files]]></category><category><![CDATA[file-signatures]]></category><dc:creator><![CDATA[KIRAN PK]]></dc:creator><pubDate>Wed, 11 Mar 2026 17:15:38 GMT</pubDate><content:encoded><![CDATA[<p>Most people assume the file extension tells the system what type of file it is. It's an intuitive assumption — <code>.png</code> means image, <code>.pdf</code> means document, <code>.exe</code> means executable. At first glance, this seems completely reasonable.</p>
<p>But here's the thing: file extensions are just part of the filename. They're a label, not a guarantee.</p>
<p>Consider this scenario:</p>
<pre><code class="language-plaintext">malware.exe  →  renamed  →  totally-safe-image.png
</code></pre>
<p>The filename changed. The bytes inside the file? Not a single one. If your system relied purely on the extension to decide how to handle that file, it just got fooled. This is a real technique used to sneak malicious files past unsuspecting users.</p>
<p>So how do operating systems and tools actually detect the real file type? They don't look at the name at all — they look inside the file itself.</p>
<hr />
<h2>Magic Numbers: The Fingerprints of Files</h2>
<p>Every common file format has a sequence of bytes baked into it that acts as its identity. These bytes — usually sitting right at the start of the file — are called <strong>file signatures</strong>, or <strong>magic numbers</strong>.</p>
<p>When a program wants to know what a file really is, it opens the file and reads those first few bytes, then compares them against a table of known signatures.</p>
<p>Take a PNG image. No matter what you name it, the first four bytes will always be:</p>
<pre><code class="language-plaintext">89 50 4E 47
</code></pre>
<p>The ASCII representation for that is <code>‰PNG</code>.</p>
<p>The same is true for PDFs (<code>%PDF-</code>), SQLite databases (literally <code>SQLite format 3</code>), and dozens of other formats.</p>
<p>In our Go detector, we represent each of these as a struct:</p>
<pre><code class="language-go">type FileMagicNumberMapping struct {
    Name        string
    Signature   []byte
    Offset      int
    CustomCheck func(filePath string) bool
}
</code></pre>
<p>And the known signatures are defined in a lookup table:</p>
<pre><code class="language-go">var FileType = []FileMagicNumberMapping{
    {"PNG Image",        []byte{0x89, 0x50, 0x4E, 0x47},             0, nil},
    {"PDF Document",     []byte{0x25, 0x50, 0x44, 0x46, 0x2D},       0, nil},
    {"SQLite Database",  []byte{0x53, 0x51, 0x4C, 0x69, 0x74, 0x65, 0x20, 0x66}, 0, nil},
    // ...
}
</code></pre>
<p>When we run the detector, we open the file and walk through this list, reading just enough bytes at the right position to check for a match:</p>
<pre><code class="language-go">for _, s := range FileType {
    header := make([]byte, len(s.Signature))
    _, err := file.ReadAt(header, int64(s.Offset))
    if err != nil {
        continue
    }

    if bytes.HasPrefix(header, s.Signature) {
        fmt.Printf("Match found: %s\n", s.Name)
        found = true
        break
    }
}
</code></pre>
<p>The key function here is <code>file.ReadAt</code> — it lets us read bytes from any position in the file. Now this is just a base case.</p>
<hr />
<h2>Not Every Signature Starts at Zero</h2>
<p>It would be convenient if every file format stored its magic bytes at the very beginning. Most do. But some don't.</p>
<p>ISO disk images are the classic example. Their identifying bytes (<code>43 44 30 30 31</code>, or <code>CD001</code>) sit at byte offset <strong>32769</strong> — deep into the file, not at the start. This is a deliberate part of the ISO 9660 spec; the first 32KB is reserved for boot code.</p>
<p>This is exactly why the struct has an <code>Offset</code> field, and why <code>ReadAt</code> is the right tool for the job:</p>
<p>go</p>
<pre><code class="language-go">{"ISO Image", []byte{0x43, 0x44, 0x30, 0x30, 0x31}, 32769, nil},
</code></pre>
<p>Instead of reading from position 0, we jump directly to byte 32769 and check for the signature there.</p>
<hr />
<h2>When Bytes Aren't Enough</h2>
<p>Here's where things get interesting. Not every file format has a reliable magic number. Scripts, config files, and plain text files have no standardized binary header — they're just... text.</p>
<p>Many scripts start with a <strong>shebang line</strong>: a special first line that tells the shell which interpreter to use.</p>
<pre><code class="language-plaintext">#!/usr/bin/python3
</code></pre>
<p>The <code>#!</code> bytes (<code>0x23 0x21</code>) do act as a kind of signature, but they're shared by every type of script — Python, Bash, Ruby, Perl. The bytes alone only tell you "this is <em>a</em> script." To know <em>which</em> script, you have to actually read the line.</p>
<p>This is called <strong>content sniffing</strong> — inspecting the file's actual content rather than relying on fixed byte patterns. In <code>scripts_check.go</code>, we handle this case:</p>
<pre><code class="language-go">func ScriptCheck(fileName string) bool {
    file, err := os.Open(fileName)
    if err != nil {
        return false
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    if scanner.Scan() {
        line := scanner.Text()
        if strings.HasPrefix(line, "#!") &amp;&amp; strings.Contains(line, "python") {
            fmt.Printf("Match found: Python Script\n")
            return true
        }
        // Fallback: look for common Python patterns
        if strings.HasPrefix(line, "import ") || strings.HasPrefix(line, "def ") || strings.Contains(line, "print(") {
            fmt.Printf("Match found: Python Script\n")
            return true
        }
    }
    return false
}
</code></pre>
<p>Notice the fallback: if there's no shebang, we look for patterns like <code>import</code>, <code>def</code>, or <code>print(</code> — things that strongly suggest Python code. This is a rougher heuristic, but it's how you handle formats that were never designed to be identified by a fixed header.</p>
<p>This is why the <code>CustomCheck</code> field exists in our struct. For file types where raw byte comparison isn't sufficient, we plug in a function that can do deeper inspection. The byte signature gives us a quick first filter (<code>#!</code>), and if that matches, the custom check takes over to figure out the specifics.</p>
<p>Your browser does exactly the same thing. When a server sends a file with the wrong MIME type — or no type at all — the browser reads the content and makes its best guess. That's content sniffing in the wild.</p>
<hr />
<h2>The Tricky Case: Container Formats</h2>
<p>There's one more scenario worth knowing about.</p>
<p>Many modern file formats — DOCX, XLSX, PPTX, JAR, APK — aren't really unique formats at all. They're ZIP archives with a specific internal structure. And they all start with the same signature:</p>
<pre><code class="language-plaintext">50 4B 03 04
</code></pre>
<p>That's the ZIP magic number. So if you only check the magic bytes, every Word document, every Excel spreadsheet, and every Android app looks identical — just "a ZIP file."</p>
<p>To tell them apart, you have to open the archive and inspect what's inside. A DOCX will contain <code>word/document.xml</code>. An XLSX will have <code>xl/workbook.xml</code>. The container tells you the encoding; the contents tell you the actual format.</p>
<p>This is a known limitation of pure signature-based detection, and it's a good example of where content sniffing and deeper inspection become necessary — the magic number is just the starting point.</p>
<hr />
<h2>The Limits of Magic Numbers</h2>
<p>Magic numbers are remarkably useful, but they're not a perfect solution.</p>
<p><strong>Some formats have no signature at all.</strong> Plain text files, CSVs, JSON, log files — none of these have defined magic bytes. You can't fingerprint a <code>.csv</code> the way you fingerprint a <code>.png</code>. Detection has to rely on content analysis, file extension as a hint, or context.</p>
<p><strong>Some formats share a signature.</strong> As we just saw with ZIP-based formats, a shared magic number means you need additional inspection to disambiguate. The signature gets you to the right neighborhood; you still have to find the right house.</p>
<p>The <code>file</code> command on Linux, and MIME detection in browsers, both combine these techniques — magic numbers, content sniffing, and context — to make their best determination.</p>
<hr />
<h2>Wrapping Up</h2>
<p>Next time you rename a file, remember: the extension is just a label. The truth is in the bytes.</p>
<p>File signatures are one of those concepts that seem small until you realize how much of your operating system, your browser, and your security tooling depends on them quietly doing their job. Understanding how they work — and where they fall short — is useful any time you're building something that needs to handle files reliably.</p>
<p>The full source for the detector used in this post is linked below. It's a small codebase but covers the major cases: fixed-offset signatures, content sniffing via custom checks, and the BOM-based detection used to identify Unicode text encoding variants.</p>
<hr />
<p>Github: <a href="https://github.com/kiranmurali93/learning/tree/main/file-signature">https://github.com/kiranmurali93/learning/tree/main/file-signature</a></p>
<p>Wikipedia: <a href="https://en.wikipedia.org/wiki/Magic_number_(programming)">https://en.wikipedia.org/wiki/Magic_number_(programming)</a></p>
]]></content:encoded></item><item><title><![CDATA[Concurrency in Practice - A Job Claiming System That Works]]></title><description><![CDATA[Concurrency issues don’t always show up during development—they usually surface when multiple users interact with your system simultaneously. For small-scale applications, this might not even be a concern. But if you’re a developer curious about how ...]]></description><link>https://blog.kiranpk.dev/concurrency-in-practice-a-job-claiming-system-that-works</link><guid isPermaLink="true">https://blog.kiranpk.dev/concurrency-in-practice-a-job-claiming-system-that-works</guid><category><![CDATA[concurrency]]></category><dc:creator><![CDATA[KIRAN PK]]></dc:creator><pubDate>Thu, 01 May 2025 09:30:18 GMT</pubDate><content:encoded><![CDATA[<p>Concurrency issues don’t always show up during development—they usually surface when multiple users interact with your system simultaneously. For small-scale applications, this might not even be a concern. But if you’re a developer curious about how to handle concurrency the right way, this post is for you.</p>
<hr />
<p>For the purpose of this blog, let’s consider a business use case: a job posting platform for freelance recruiters.</p>
<p>Companies post jobs on the platform, and recruiters can claim the jobs they want to work on. However, to avoid overwhelming companies with too many applications, a single job can only be claimed by a limited number of recruiters—say, four. This means we need to enforce a concurrency-safe mechanism to ensure that no more than the allowed number of claims are made per job.</p>
<p>Now, some of you might think: “That sounds simple—we just need to query the number of claims for a job, and if it’s greater than or equal to 4, return an error, right?”</p>
<p>And if I ask you about the code would it be like this?</p>
<pre><code>count := db.Query(<span class="hljs-string">`SELECT COUNT(*) FROM job_claims WHERE job_id = ?`</span>)
<span class="hljs-keyword">if</span> count &gt;= <span class="hljs-number">4</span> {
    <span class="hljs-keyword">return</span> error(<span class="hljs-string">"Job fully claimed"</span>)
}
<span class="hljs-comment">// proceed to insert claim</span>
</code></pre><p>If so then you’re almost right—but not completely.</p>
<blockquote>
<p>The problem with this approach lies in <strong>how web servers handle requests</strong> and <strong>how we <em>think</em> they do</strong>.</p>
<p>As developers, we often imagine our logic runs sequentially: a request comes in, it checks the count, inserts the claim, and we're done. But in reality, <strong>multiple requests can hit the server at the same time</strong>, and each of them can execute that same <code>SELECT COUNT(*)</code> query <em>before</em> any of them inserts their claim.</p>
<p>This means two or more recruiters could read the same count (say, 3), believe the job is still available, and go on to insert their own claim—resulting in more than four claims being stored. This is a classic <strong>race condition</strong>.</p>
</blockquote>
<p>Before we test this out, let’s set up a basic project. For this demo, I’ll be using <strong>Golang</strong> and <strong>PostgreSQL</strong>.</p>
<p>Here’s what the folder structure looks like:</p>
<pre><code>|- db/
   |- db.go
|- handlers/
   |- jobs.go
|-.env
|- main.go
|- test.go
</code></pre><p>Now let's break down what each file will do:</p>
<ul>
<li><code>db/db.go</code> : Handles postgresql connection setup.</li>
<li><code>handlers/jobs.go</code>: Contains the handler/business logic for claiming the job.</li>
<li><code>.env</code>: Stores the secrets which you dont want to expose in the code.</li>
<li><code>main.go</code>: The entry file, which starts the http server and sets up routes.</li>
<li><code>test.go</code>: Simulates multiple concurrent recruiters trying to claim a job.</li>
</ul>
<p>We also need to define the database schema and relationships.<br />Let’s start with the basic tables required for this system:</p>
<ul>
<li><strong><code>companies</code></strong>: Stores information about all the companies registered on the platform.</li>
<li><strong><code>jobs</code></strong>: Contains job postings created by companies.</li>
<li><strong><code>recruiters</code></strong>: Holds the list of all recruiters who have signed up.</li>
<li><strong><code>job_claims</code></strong>: Records when a recruiter claims a job, forming a many-to-many relationship between recruiters and jobs.</li>
</ul>
<p>Here’s a quick sql query for setting up the tables
<code>companies</code> table</p>
<pre><code>CREATE TABLE public.companies (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  name TEXT NOT NULL
);
</code></pre><p><code>jobs</code> table</p>
<pre><code>CREATE TABLE public.jobs (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  role TEXT,
  company_id BIGINT,
  CONSTRAINT jobs_company_id_fkey FOREIGN KEY (company_id) REFERENCES companies (id)
);
</code></pre><p><code>recruiter</code> table</p>
<pre><code>CREATE TABLE public.recruiter (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  name TEXT
);
</code></pre><p><code>job_claims</code> table</p>
<pre><code>CREATE TABLE public.job_claims (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  created_at TIMESTAMPTZ DEFAULT now(),
  job_id BIGINT NOT NULL,
  recruiter_id BIGINT NOT NULL,
  CONSTRAINT job_claims_job_id_recruiter_id_key UNIQUE (job_id, recruiter_id),
  CONSTRAINT job_claims_job_id_fkey FOREIGN KEY (job_id) REFERENCES jobs (id),
  CONSTRAINT job_claims_recruiter_id_fkey FOREIGN KEY (recruiter_id) REFERENCES recruiter (id)
);
</code></pre><p>Now since the setup is done let's move a head with implementation:</p>
<p>Let’s now implement the logic where we simply check how many claims a job already has, and only proceed if it’s less than 4.</p>
<p>This is how our <code>handler/jobs.go</code> will look like</p>
<pre><code>func ClaimJob(w http.ResponseWriter, r *http.Request) {
    <span class="hljs-attr">jobId</span> := r.URL.Query().Get(<span class="hljs-string">"jobId"</span>)
    <span class="hljs-attr">recruiterId</span> := r.URL.Query().Get(<span class="hljs-string">"recruiterId"</span>)

    <span class="hljs-keyword">if</span> jobId == <span class="hljs-string">""</span> || recruiterId == <span class="hljs-string">""</span> {
        http.Error(w, <span class="hljs-string">"Missing jobId or recruiterId"</span>, http.StatusBadRequest)
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-comment">// Count how many claims exist for this job</span>
    <span class="hljs-keyword">var</span> count int
    <span class="hljs-attr">err</span> := db.DB.QueryRow(<span class="hljs-string">`SELECT COUNT(*) FROM job_claims WHERE job_id = $1`</span>, jobId).Scan(&amp;count)
    <span class="hljs-keyword">if</span> err != nil {
        http.Error(w, <span class="hljs-string">"Could not fetch claim count"</span>, http.StatusInternalServerError)
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-keyword">if</span> count &gt;= <span class="hljs-number">4</span> {
        http.Error(w, <span class="hljs-string">"Job fully claimed"</span>, http.StatusForbidden)
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-comment">// Insert the claim</span>
    _, err = db.DB.Exec(<span class="hljs-string">`INSERT INTO job_claims (job_id, recruiter_id) VALUES ($1, $2)`</span>, jobId, recruiterId)
    <span class="hljs-keyword">if</span> err != nil {
        http.Error(w, <span class="hljs-string">"Failed to claim the job"</span>, http.StatusInternalServerError)
        <span class="hljs-keyword">return</span>
    }

    json.NewEncoder(w).Encode(map[string]string{
        <span class="hljs-string">"message"</span>: <span class="hljs-string">"Job claimed successfully"</span>,
    })
}
</code></pre><p>And to test this out we have a <code>test.go</code> file. Note: This file would remain the same during entire processes</p>
<pre><code>func main() {
    <span class="hljs-keyword">var</span> wg sync.WaitGroup

    <span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span>; i &lt;= <span class="hljs-number">5</span>; i++ {
        wg.Add(<span class="hljs-number">1</span>)
        go func(rid int) {
            defer wg.Done()
            <span class="hljs-attr">url</span> := fmt.Sprintf(<span class="hljs-string">"http://localhost:3000/claim-job?jobId=1&amp;recruiterId=%d"</span>, rid)
            resp, <span class="hljs-attr">err</span> := http.Get(url)
            <span class="hljs-keyword">if</span> err != nil {
                fmt.Printf(<span class="hljs-string">"Recruiter %d error: %v\n"</span>, rid, err)
                <span class="hljs-keyword">return</span>
            }
            defer resp.Body.Close()
            fmt.Printf(<span class="hljs-string">"Recruiter %d: %s\n"</span>, rid, resp.Status)
        }(i)
    }

    wg.Wait()
}
</code></pre><p>If you run this test, you will get something like this</p>
<pre><code>Recruiter <span class="hljs-number">4</span>: <span class="hljs-number">200</span> OK
Recruiter <span class="hljs-number">1</span>: <span class="hljs-number">200</span> OK
Recruiter <span class="hljs-number">2</span>: <span class="hljs-number">200</span> OK
Recruiter <span class="hljs-number">5</span>: <span class="hljs-number">200</span> OK
Recruiter <span class="hljs-number">3</span>: <span class="hljs-number">200</span> OK
</code></pre><p>Note: This can be in any order since we are running it concurrently.</p>
<p>If you notice the output you will be able to see there are a total of 5 claims rather than just 4. </p>
<p>So what went wrong?
Each request checked the count before inserting, but none of them had visibility into the others that were happening at the same time.</p>
<p>At this point, you might be thinking:<br />“Why not just wrap the whole thing in a transaction so that it's atomic?”</p>
<p>Let's try that out too.
The updated section of <code>handler/jobs.go</code> would look like this</p>
<pre><code>tx, <span class="hljs-attr">err</span> := db.DB.Begin()
<span class="hljs-keyword">if</span> err != nil {
    http.Error(w, <span class="hljs-string">"Could not start transaction"</span>, http.StatusInternalServerError)
    <span class="hljs-keyword">return</span>
}
defer tx.Rollback()

<span class="hljs-keyword">var</span> count int
err = tx.QueryRow(<span class="hljs-string">`SELECT COUNT(*) FROM job_claims WHERE job_id = $1`</span>, jobId).Scan(&amp;count)
<span class="hljs-keyword">if</span> err != nil {
    http.Error(w, <span class="hljs-string">"Could not fetch claim count"</span>, http.StatusInternalServerError)
    <span class="hljs-keyword">return</span>
}

<span class="hljs-keyword">if</span> count &gt;= <span class="hljs-number">4</span> {
    http.Error(w, <span class="hljs-string">"Job fully claimed"</span>, http.StatusForbidden)
    <span class="hljs-keyword">return</span>
}

_, err = tx.Exec(<span class="hljs-string">`INSERT INTO job_claims (job_id, recruiter_id) VALUES ($1, $2)`</span>, jobId, recruiterId)
<span class="hljs-keyword">if</span> err != nil {
    http.Error(w, <span class="hljs-string">"Failed to claim the job"</span>, http.StatusInternalServerError)
    <span class="hljs-keyword">return</span>
}

err = tx.Commit()
<span class="hljs-keyword">if</span> err != nil {
    http.Error(w, <span class="hljs-string">"Failed to commit transaction"</span>, http.StatusInternalServerError)
    <span class="hljs-keyword">return</span>
}
</code></pre><p>This feels like this would solve the problem. To check this out let's try running the <code>test.go</code> again and we would get something like this</p>
<pre><code>Recruiter <span class="hljs-number">3</span>: <span class="hljs-number">200</span> OK
Recruiter <span class="hljs-number">4</span>: <span class="hljs-number">200</span> OK
Recruiter <span class="hljs-number">2</span>: <span class="hljs-number">200</span> OK
Recruiter <span class="hljs-number">5</span>: <span class="hljs-number">200</span> OK
Recruiter <span class="hljs-number">1</span>: <span class="hljs-number">200</span> OK
</code></pre><p>Unfortunately, this too doesn’t seem to help.</p>
<p>If you ask why, here's the reason:
Even though the request is running a transaction, there is no locking happening. Each transaction still sees the same initial count before others have committed their transaction.</p>
<blockquote>
<p>Transactions can only isolates their own change. Without explicitly locking in rows. So multiple transactions can read the same state and make decision based on that, leading to same race condition.</p>
</blockquote>
<p>So now what would be the fix?
The solution to this problem is to <strong>lock the job row</strong> that recruiters are trying to claim. This ensures that only one transaction can access and modify the relevant data at a time.</p>
<p>In postgres we have a command "SELECT FOR UPDATE"
In our case it will be something like this</p>
<pre><code>SELECT * FROM jobs WHERE id = $<span class="hljs-number">1</span> FOR UPDATE;
</code></pre><p>What does it do?</p>
<ul>
<li>It locks on to the corresponding job.</li>
<li>Other transactions which tries to access the same row for update, will be blocked until the lock is released.</li>
</ul>
<p>Let's update the handler logic</p>
<pre><code>tx, <span class="hljs-attr">err</span> := db.DB.Begin()
<span class="hljs-keyword">if</span> err != nil {
    http.Error(w, <span class="hljs-string">"Could not start transaction"</span>, http.StatusInternalServerError)
    <span class="hljs-keyword">return</span>
}
defer tx.Rollback()

<span class="hljs-comment">// Lock the job row</span>
_, err = tx.Exec(<span class="hljs-string">`SELECT * FROM jobs WHERE id = $1 FOR UPDATE`</span>, jobId)
<span class="hljs-keyword">if</span> err != nil {
    http.Error(w, <span class="hljs-string">"Failed to lock job"</span>, http.StatusInternalServerError)
    <span class="hljs-keyword">return</span>
}

<span class="hljs-comment">// Check if this recruiter already claimed the job</span>
<span class="hljs-keyword">var</span> exists bool
err = tx.QueryRow(<span class="hljs-string">`SELECT EXISTS(SELECT 1 FROM job_claims WHERE job_id = $1 AND recruiter_id = $2)`</span>, jobId, recruiterId).Scan(&amp;exists)
<span class="hljs-keyword">if</span> err != nil {
    http.Error(w, <span class="hljs-string">"Could not check claim status"</span>, http.StatusInternalServerError)
    <span class="hljs-keyword">return</span>
}

<span class="hljs-keyword">if</span> exists {
    http.Error(w, <span class="hljs-string">"Recruiter already claimed this job"</span>, http.StatusConflict)
    <span class="hljs-keyword">return</span>
}

<span class="hljs-comment">// Count existing claims</span>
<span class="hljs-keyword">var</span> count int
err = tx.QueryRow(<span class="hljs-string">`SELECT COUNT(*) FROM job_claims WHERE job_id = $1`</span>, jobId).Scan(&amp;count)
<span class="hljs-keyword">if</span> err != nil {
    http.Error(w, <span class="hljs-string">"Could not fetch claim count"</span>, http.StatusInternalServerError)
    <span class="hljs-keyword">return</span>
}

<span class="hljs-keyword">if</span> count &gt;= <span class="hljs-number">4</span> {
    http.Error(w, <span class="hljs-string">"Job fully claimed"</span>, http.StatusForbidden)
    <span class="hljs-keyword">return</span>
}

<span class="hljs-comment">// Insert the claim</span>
_, err = tx.Exec(<span class="hljs-string">`INSERT INTO job_claims (job_id, recruiter_id) VALUES ($1, $2)`</span>, jobId, recruiterId)
<span class="hljs-keyword">if</span> err != nil {
    http.Error(w, <span class="hljs-string">"Failed to claim job"</span>, http.StatusInternalServerError)
    <span class="hljs-keyword">return</span>
}

err = tx.Commit()
<span class="hljs-keyword">if</span> err != nil {
    http.Error(w, <span class="hljs-string">"Failed to commit transaction"</span>, http.StatusInternalServerError)
    <span class="hljs-keyword">return</span>
}
</code></pre><p>Now when you run the <code>test.go</code> you will notice something like this</p>
<pre><code>Recruiter <span class="hljs-number">3</span>: <span class="hljs-number">200</span> OK
Recruiter <span class="hljs-number">5</span>: <span class="hljs-number">200</span> OK
Recruiter <span class="hljs-number">2</span>: <span class="hljs-number">200</span> OK
Recruiter <span class="hljs-number">4</span>: <span class="hljs-number">200</span> OK
Recruiter <span class="hljs-number">1</span>: <span class="hljs-number">403</span> Forbidden
</code></pre><p>As you can see only 4 claims are successful, the 5th one fails. This solves the race condition.</p>
<p>Now there might be some confusion that I would like to clear
You might notice that we're locking a row in the <code>jobs</code> table, but performing operations on the <code>job_claims</code> table. This is intentional and highlights an important concept in database locking:</p>
<p>When managing concurrency, you need to lock the "decision-making resource," not just the tables being modified. In our case:</p>
<ol>
<li>The job itself (in the <code>jobs</code> table) is the logical resource we're contending for</li>
<li>The claims (in the <code>job_claims</code> table) are just the implementation detail of how we track that resource allocation</li>
</ol>
<p>By locking the job row, we ensure that all decisions about whether a particular job can accept more claims are serialised—meaning they happen one after another, not simultaneously. This prevents the race condition where multiple transactions might all see "3 claims" at the same time and all decide they can proceed.</p>
<p>Another approach would be to create a separate "job_claims_count" table that tracks the current count per job, and lock rows in that table instead. This would achieve the same goal while potentially reducing lock contention on the main <code>jobs</code> table.</p>
<p>For checking the implementation code check out <a target="_blank" href="https://github.com/kiranmurali93/learning/tree/main/job-claim-concurrency-demo">github</a></p>
]]></content:encoded></item><item><title><![CDATA[A Guide to Handling Numeric Input in Go: Base Interpretation Mistakes to Avoid]]></title><description><![CDATA[The Problem: Unexpected Behaviour with Leading Zeros
Recently, while working on a Go program that reverses an integer input, I encountered an unexpected issue: input values with leading zeros produced incorrect results. For example, entering 0123 ret...]]></description><link>https://blog.kiranpk.dev/a-guide-to-handling-numeric-input-in-go-base-interpretation-mistakes-to-avoid</link><guid isPermaLink="true">https://blog.kiranpk.dev/a-guide-to-handling-numeric-input-in-go-base-interpretation-mistakes-to-avoid</guid><category><![CDATA[golang]]></category><dc:creator><![CDATA[KIRAN PK]]></dc:creator><pubDate>Mon, 11 Nov 2024 12:21:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731327580622/458ab9f7-6462-4856-a3a0-236cd5659d40.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h4 id="heading-the-problem-unexpected-behaviour-with-leading-zeros">The Problem: Unexpected Behaviour with Leading Zeros</h4>
<p>Recently, while working on a Go program that reverses an integer input, I encountered an unexpected issue: input values with leading zeros produced incorrect results. For example, entering <code>0123</code> returned <code>83</code> instead of <code>321</code>. After some debugging, I realised the cause was rooted in Go's interpretation of numeric literals.</p>
<p>Here’s a simplified version of the code:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span>  {
    <span class="hljs-keyword">var</span> inp <span class="hljs-keyword">int</span>
    fmt.Scan(&amp;inp)
    fmt.Println(inp)  <span class="hljs-comment">// Outputs unexpected values for leading-zero inputs</span>
}
</code></pre>
<p>Entering <code>0123</code> resulted in <code>83</code>, a behaviour that puzzled me at first. Why would Go interpret <code>0123</code> as <code>83</code>? Let’s dive into the mechanics behind Go’s input handling to understand this better.</p>
<h4 id="heading-insights-into-numeric-literals-in-go">Insights into Numeric Literals in Go</h4>
<p>In Go, numeric literals are interpreted based on their prefixes:</p>
<ul>
<li><p><strong>Decimal</strong>: Numbers without any prefix (e.g., <code>123</code>) are treated as decimal.</p>
</li>
<li><p><strong>Octal</strong>: Numbers starting with <code>0</code> (like <code>0123</code>) are interpreted as octal (base 8).</p>
</li>
<li><p><strong>Hexadecimal</strong>: Numbers prefixed with <code>0x</code> or <code>0X</code> (like <code>0x1F</code>) are hexadecimal (base 16).</p>
</li>
<li><p><strong>Binary</strong>: Numbers prefixed with <code>0b</code> or <code>0B</code> (like <code>0b1010</code>) are binary (base 2). This was introduced in Go 1.13.</p>
</li>
</ul>
<p>Because <code>0123</code> has a <code>0</code> prefix, Go treats it as an octal number, converting it to <code>83</code> in decimal, which explains the unexpected output.</p>
<h4 id="heading-avoiding-the-trap-solutions-for-consistent-input-interpretation">Avoiding the Trap: Solutions for Consistent Input Interpretation</h4>
<p>If you’re dealing with user input that might include leading zeros and you want decimal interpretation, consider reading the input as a string and then converting it explicitly to an integer. Here’s an improved version of the original code:</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"fmt"</span>
    <span class="hljs-string">"strconv"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> inp <span class="hljs-keyword">string</span>
    fmt.Scan(&amp;inp)

    num, err := strconv.Atoi(inp)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Println(<span class="hljs-string">"Invalid input"</span>)
        <span class="hljs-keyword">return</span>
    }

    fmt.Println(num)  <span class="hljs-comment">// Now it will interpret as decimal regardless of leading zeros</span>
}
</code></pre>
<h4 id="heading-other-situations-to-watch-for-in-go">Other Situations to Watch For in Go</h4>
<p>This behaviour isn't limited to <code>fmt.Scan</code>:</p>
<ul>
<li><p><strong>String parsing with</strong> <code>strconv.ParseInt</code>: Setting the base to <code>0</code> in functions like <code>strconv.ParseInt("0123", 0, 64)</code> will detect the base from prefixes. If consistent decimal interpretation is needed, specify <code>10</code> as the base instead.</p>
</li>
<li><p><strong>Hexadecimal and binary literals in code</strong>: These are helpful, but make sure they’re not used inadvertently, as <code>0x</code> and <code>0b</code> prefixes will trigger hexadecimal and binary parsing.</p>
</li>
</ul>
<h4 id="heading-key-takeaways">Key Takeaways</h4>
<p>This experience taught me the importance of understanding how Go interprets numeric literals. Here’s a summary of best practices when handling numeric input in Go:</p>
<ol>
<li><p><strong>Read input as strings</strong> when dealing with user data that could have ambiguous prefixes, and specify the desired base during conversion.</p>
</li>
<li><p><strong>Explicitly specify the base</strong> when parsing numeric strings with <code>strconv</code> functions to avoid automatic base detection.</p>
</li>
<li><p><strong>Watch out for leading zeros</strong> in integer input, especially in cases where the base might unintentionally switch to octal.</p>
</li>
</ol>
<p>Understanding these nuances can prevent bugs and ensure that your Go programs handle numeric inputs predictably.</p>
]]></content:encoded></item><item><title><![CDATA[How I build a whatsapp campaign internal tool]]></title><description><![CDATA[Introduction:
Campaign management is a critical aspect of any organisation's outreach strategy, and an effective scheduling and execution system can significantly enhance its success. Previously we used to run manual scripts using freshchat apis, whi...]]></description><link>https://blog.kiranpk.dev/whatsapp-campaign-tool</link><guid isPermaLink="true">https://blog.kiranpk.dev/whatsapp-campaign-tool</guid><category><![CDATA[whatsapp]]></category><category><![CDATA[AWS]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[architecture]]></category><dc:creator><![CDATA[KIRAN PK]]></dc:creator><pubDate>Fri, 05 Jan 2024 03:44:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/upBKRmHJrCI/upload/c2a99fd1838f0318deea7df292f005b7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Introduction:</strong></p>
<p>Campaign management is a critical aspect of any organisation's outreach strategy, and an effective scheduling and execution system can significantly enhance its success. Previously we used to run manual scripts using freshchat apis, which is an api wrapper around whatsapp api, which is an manual repetitive task for developers. So I thought of automating this process and make an internal tool around it.</p>
<p><strong>Architecture:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704423114871/80e69cc7-5f57-4762-abe5-5f685bbf3289.png" alt class="image--center mx-auto" /></p>
<p>When a user uploads a campaign, the scheduling API comes into play. This API's primary function is to create a campaign and schedule an event at the specified time. The event is associated with a campaign ID and time data, and the scheduler returns an event ID, which is then stored as a property of the campaign. Importantly, this scheduling mechanism allows for future modifications to the scheduled event, but only for campaigns that are yet to happen.</p>
<p>Note: The scheduling is done using AWS eventbridge rules.</p>
<p>At the scheduled time, a Lambda function is triggered to execute the campaign. This function, in turn, calls an internal API to initiate the campaign. The entire process ensures seamless and automated campaign execution.</p>
<p>The run campaign process involves parsing a CSV file to generate parameters for a Freshchat API call. The Freshchat API returns a response object with a response ID. These response IDs and their statuses are saved for future reference. Notably, when listing campaigns, any messages with a status code other than Sent, Delivered, or Failed trigger a Freshchat API status check to update the values accordingly.</p>
<p>Campaigns can be edited with the provided schedule ID, allowing modifications to scheduled events. If there is a change in the schedule time during editing, a new event is generated with the corresponding campaign ID.</p>
<p>When the run campaign API is called, it checks for the scheduled time. If there is a time difference of 5 minutes or more between the API request and the scheduled time, the campaign is ignored. This feature prevents unintended or premature campaign executions.</p>
<p><strong>Required APIs and Payloads:</strong></p>
<p>Create and Edit Campaign:</p>
<ul>
<li><p>Endpoint: <code>POST /ss/admin/campaigns</code> and <code>PATCH /ss/admin/campaigns</code></p>
</li>
<li><p>Payloads:</p>
<ul>
<li><p>For creating: <code>{ csv: file, templateId: string, numberOfPlaceholders: string, dateTime: Date, campaignName: string }</code></p>
</li>
<li><p>For editing: <code>{ id: number, csv: file, templateId: string, numberOfPlaceholders: string, dateTime: Date, campaignName: string }</code></p>
</li>
</ul>
</li>
</ul>
<p>List Campaigns:</p>
<ul>
<li><p>Endpoint: <code>GET /ss/admin/campaigns</code></p>
</li>
<li><p>Response Payload:</p>
<ul>
<li><code>{ id: number, campaignName: string, templateId: string, total: number, success: number, failed: number, createdAt: Date, scheduledAt: Date, status: string, csvUploaded: string }</code></li>
</ul>
</li>
</ul>
<p>Test Message:</p>
<ul>
<li><p>Endpoint: <code>POST /ss/admin/test-campaign</code></p>
</li>
<li><p>Payload: <code>{ phoneNumber: string, placeholders: string, templateId: string }</code></p>
</li>
</ul>
<p><strong>Database Tables:</strong></p>
<p>Campaign Table:</p>
<ul>
<li>Fields: <code>id, name, templateId, totalMessages, successMessages, failureMessages, inQueue, createdAt, scheduledAt, status, fileUrl</code></li>
</ul>
<p>Message Table:</p>
<ul>
<li>Fields: <code>id, messageId, campaignId, status, phoneNumber</code></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to Implement a Simple Cache Invalidation System in Vue.js]]></title><description><![CDATA[Before we delve into the mechanics of cache invalidation, let's clarify what caching is.
A cache, often referred to as a web cache, is a technology designed to store copies of web content, such as web pages, images, and other resources, closer to end...]]></description><link>https://blog.kiranpk.dev/simple-cache-invalidation-system-in-vuejs</link><guid isPermaLink="true">https://blog.kiranpk.dev/simple-cache-invalidation-system-in-vuejs</guid><category><![CDATA[Vue.js]]></category><category><![CDATA[cache]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[vite]]></category><dc:creator><![CDATA[KIRAN PK]]></dc:creator><pubDate>Tue, 19 Sep 2023 04:18:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1695096968604/42823de7-c16f-47ec-94d4-5609acf86aa5.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Before we delve into the mechanics of cache invalidation, let's clarify what caching is.</p>
<p>A cache, often referred to as a web cache, is a technology designed to store copies of web content, such as web pages, images, and other resources, closer to end-users. The primary goal of a web cache is to enhance the speed and efficiency of web page loading by reducing the time it takes to fetch web content from remote web servers. This caching mechanism significantly improves the user experience by minimizing latency and conserving bandwidth.</p>
<p>Given this definition, it's evident that utilizing caching can lead to better performance. So, when should we consider invalidating these caches? The necessity for cache invalidation varies depending on different use cases. In my experience, a particular challenge emerged when performing major releases. Some users experienced issues with the platform even after thorough testing before the release. At times, we had to instruct users to force-refresh their browsers to obtain up-to-date data. Clearly, this wasn't an ideal user experience, prompting us to find a solution.</p>
<p>Our approach was straightforward: we needed to automate the process of refreshing the client-side cache whenever a new release of the website occurred. To accomplish this, we explored several methods, but the one that proved effective was to maintain a version data comparison between the local version and the deployed version.</p>
<p><strong>Step 1: Create a</strong> <code>version.json</code> File</p>
<p>Begin by creating a <code>version.json</code> file in your Vue project's public folder. The file should contain a key named "version" with a value that can be used for comparison. For this example, let's use a timestamp as the value.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">""</span> <span class="hljs-comment">// This is where the timestamp comes in</span>
}
</code></pre>
<p><strong>Step 2: Implement the Version Comparison in a Composable</strong></p>
<p>Now, let's implement the version comparison logic in a composable. Begin by defining the base URL for your domain:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> baseUrl = <span class="hljs-keyword">import</span>.meta.env.VITE_WEB_URL;
</code></pre>
<p>This base URL should point to your website's domain. Next, retrieve the deployed version from your hosted domain:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> GetVersion {
  version: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRealVersion</span>(<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">GetVersion</span>&gt; </span>{
  <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.get&lt;GetVersion&gt;(<span class="hljs-string">`<span class="hljs-subst">${baseUrl}</span>/version.json`</span>);
  <span class="hljs-keyword">return</span> res.data;
}
</code></pre>
<p>Similarly, obtain the local version from the <code>version.json</code> file:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> localVersion <span class="hljs-keyword">from</span> <span class="hljs-string">"../../public/version.json"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getLocalVersion</span>(<span class="hljs-params"></span>): <span class="hljs-title">GetVersion</span> </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> version = <span class="hljs-built_in">JSON</span>.parse(<span class="hljs-built_in">JSON</span>.stringify(localVersion));
    <span class="hljs-keyword">return</span> version;
  } <span class="hljs-keyword">catch</span> {
    <span class="hljs-keyword">return</span> {
      version: <span class="hljs-string">""</span>,
    };
  }
}
</code></pre>
<p>Now that we have both versions, we can create a function to compare them:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { compareAsc, isValid } <span class="hljs-keyword">from</span> <span class="hljs-string">"date-fns"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">invalidCacheCheck</span>(<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">Boolean</span>&gt; </span>{
  <span class="hljs-keyword">const</span> deployedVersion = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>((<span class="hljs-keyword">await</span> getRealVersion()).version);
  <span class="hljs-keyword">const</span> localVersion = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(getLocalVersion().version);

  <span class="hljs-keyword">if</span> (!isValid(localVersion)) {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }

  <span class="hljs-keyword">const</span> newVersionAvailable = compareAsc(deployedVersion, localVersion);

  <span class="hljs-keyword">if</span> (newVersionAvailable === <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;

  <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
}
</code></pre>
<p><strong>Step 3: Implement Cache Invalidation on a Timer</strong></p>
<p>However, the problem with the above function is that it only checks once when called. To address this, we need to perform checks at regular intervals. Here's how you can achieve this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { minutesToMilliseconds } <span class="hljs-keyword">from</span> <span class="hljs-string">"date-fns"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cacheInvalidator</span>(<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">void</span>&gt; </span>{
  <span class="hljs-keyword">const</span> timeIntervalInMinutes = <span class="hljs-number">5</span>;
  <span class="hljs-keyword">const</span> timeIntervalInMillisecond = minutesToMilliseconds(
    timeIntervalInMinutes
  );

  <span class="hljs-built_in">setInterval</span>(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> invalidate = <span class="hljs-keyword">await</span> invalidCacheCheck();
    <span class="hljs-keyword">if</span> (invalidate) {
      <span class="hljs-built_in">window</span>.location.reload();
    }
  }, timeIntervalInMillisecond);
}
</code></pre>
<p>This function checks if a new version is available in intervals of 5 minutes and, if so, refreshes the client-side cache.</p>
<p><strong>Step 4: Implementation in App.vue</strong></p>
<p>To activate this cache invalidation mechanism, call the <code>cacheInvalidator()</code> function in your <code>App.vue</code> file.</p>
<p><strong>Step 5: Updating the Version with Each Deployment</strong></p>
<p>Lastly, you'll need to ensure that the version is updated with each deployment. You can achieve this by using a script that generates the <code>version.json</code> file. Here's an example script:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>

<span class="hljs-comment"># Get the current UTC time</span>
current_time=$(date -u +<span class="hljs-string">"%Y-%m-%dT%H:%M:%SZ"</span>)

<span class="hljs-comment"># Create a JSON object with the current time</span>
json_data=<span class="hljs-string">"{ \"version\": \"<span class="hljs-variable">$current_time</span>\" }"</span>

<span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$json_data</span>"</span> &gt; ./public/version.json

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Version has been updated"</span>
</code></pre>
<p>Include this script in your build process to automatically update the version in each deployment.</p>
<p>By implementing this cache invalidation system, you can ensure that your Vue.js application always serves the latest content to your users, enhancing their experience and avoiding the need for manual refreshes.</p>
]]></content:encoded></item><item><title><![CDATA[Navigating the JEST Memory Issue - My Workaround]]></title><description><![CDATA[Let's dive right into the issue our team was facing. We rely on one of the most popular JavaScript testing frameworks, JEST, for rigorously testing our backend APIs. Initially, everything was sailing smoothly - setup was a breeze, and our tests were ...]]></description><link>https://blog.kiranpk.dev/navigating-the-jest-memory-issue-my-workaround</link><guid isPermaLink="true">https://blog.kiranpk.dev/navigating-the-jest-memory-issue-my-workaround</guid><category><![CDATA[Testing]]></category><category><![CDATA[Jest]]></category><category><![CDATA[backend]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[KIRAN PK]]></dc:creator><pubDate>Sat, 09 Sep 2023 11:27:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1694259657798/c960b6da-679d-4673-aa0d-5217b94355a1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let's dive right into the issue our team was facing. We rely on one of the most popular JavaScript testing frameworks, JEST, for rigorously testing our backend APIs. Initially, everything was sailing smoothly - setup was a breeze, and our tests were smooth to run. We were okay with how things were going.</p>
<p>However, as our codebase grew and we added more APIs to the code, problems started cropping up. The test suite began taking longer to complete, and memory consumption started to skyrocket. Our initial solution was to simply bump up the heap memory using <code>--max-old-space-size</code>. It worked like a charm, at least for a while.</p>
<p>As time passed, we kept increasing the heap memory allocation until we reached a point where we realized this band-aid approach wasn't sustainable. We were no longer able to run tests on our local machines, and even in our pipeline, tests were failing due to mysterious memory issues.</p>
<p>That's when we decided it was time to tackle this head-on.</p>
<p>Before diving into this, I checked the Jest GitHub repository for similar issues. To my surprise, there were several reported, but none of the proposed solutions seemed to do the trick for us.</p>
<p>So, I embarked on a trial-and-error journey.</p>
<p>Uncertain if the issue lay with our code or with Jest itself, I decided to start by refactoring some code and tests. After rerunning the tests, I waited for about 12 minutes, only to see no improvement in memory usage. By the way, you can monitor memory usage in Jest using the <code>--logHeapUsage</code> argument.</p>
<p>Feeling a bit lost, I searched for more blogs and videos and came across the term "Memory Leak." With this newfound knowledge, I began searching for these leaks, feeling like a detective using Chrome DevTools to analyze heap memory usage heat maps. I zoomed in and out of those charts repeatedly, but ultimately, I was tired and still clueless.</p>
<p>Back to the GitHub issues, I went, reading comments one after another, still finding no definitive solution. It dawned on me that maybe it wasn't our problem; perhaps it was just how Jest operated. If it were a Jest issue, my initial reaction was, "Let's switch to another testing framework!" But the reality of rewriting the entire testing suite hit us like a ton of bricks, especially with a small team. We were only weeks away from rewriting our front end from React with Material-UI to Vue.js with Tailwind, and we had to fix bugs and implement new features. So, I decided to give it a few more tries before throwing in the towel.</p>
<p>This time, I ran individual tests one by one and then attempted to run everything together. What I noticed was that when I ran tests individually, they ran smoothly, and memory usage stayed within limits. However, when more files were included, memory consumption began to swell. Jest seemed to increase heap usage after each test. Although I didn't know the root cause, I had a workaround. Since I discovered this issue while running tests individually, I thought, "Why not do this every time I need to test something?"</p>
<p>So, I wrote a shell script that looked something like this:</p>
<pre><code class="lang-bash">base_dir=<span class="hljs-string">""</span>
integration_test_dir=<span class="hljs-variable">${base_dir}</span>tests/integrationTests
integration_directory_list=($(ls <span class="hljs-variable">${integration_test_dir}</span>))

<span class="hljs-keyword">for</span> dir <span class="hljs-keyword">in</span> <span class="hljs-string">"<span class="hljs-variable">${integration_directory_list[@]}</span>"</span>; <span class="hljs-keyword">do</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$dir</span>"</span>
    npm run <span class="hljs-built_in">test</span> <span class="hljs-variable">${integration_test_dir}</span>/<span class="hljs-variable">${dir}</span>
<span class="hljs-keyword">done</span>

unit_test_dir=<span class="hljs-variable">${base_dir}</span>tests/unitTests
unit_directory_list=$(ls <span class="hljs-variable">${unit_test_dir}</span>)

<span class="hljs-keyword">for</span> unit <span class="hljs-keyword">in</span> <span class="hljs-string">"<span class="hljs-variable">${unit_directory_list[@]}</span>"</span>; <span class="hljs-keyword">do</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"<span class="hljs-variable">$unit</span>"</span>
    npm run <span class="hljs-built_in">test</span> <span class="hljs-variable">${unit_test_dir}</span>/<span class="hljs-variable">${unit}</span>
<span class="hljs-keyword">done</span>

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Testing completed"</span>
</code></pre>
<p>I then modified the <code>package.json</code> to include:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"test"</span>: <span class="hljs-string">"NODE_ENV=test NODE_OPTIONS='--max-old-space-size=6144' jest --runInBand"</span>,
  <span class="hljs-attr">"test: local"</span>: <span class="hljs-string">"bash scripts/test-local.sh"</span>
}
</code></pre>
<p>Now to run the test use the command</p>
<pre><code class="lang-bash">npm run <span class="hljs-built_in">test</span>:<span class="hljs-built_in">local</span>
</code></pre>
<p>I gave this approach a shot, and voilà, it worked.</p>
<p>I realize this isn't a definitive solution, but rather a hack to keep things running until someone finds out how to fix this issue properly.</p>
]]></content:encoded></item></channel></rss>