Shahathir (•◡•)

24 · Batu Caves, Selangor, Malaysia · 🇲🇾

professionally distracted

My Career Journey

  1. Financial Risk Group logo

    Financial Risk Group

    1yrs 3mos

    Assistant Software Developer

    Jun 2025 – Present

  2. Estee Lauder Companies logo

    Estee Lauder Companies

    6mos

    Software Engineer Intern

    Sep 2024 – Mar 2025

Tools & Platforms

TypeScript
JavaScript
Java
Python
PHP
Go
HTML5
CSS3
React
Next.js
Vite
Angular
Redux
React Router
Tailwind CSS
shadcn/ui
Material UI
Sass
Bootstrap
React Native
Node.js
TypeScript
JavaScript
Java
Python
PHP
Go
HTML5
CSS3
React
Next.js
Vite
Angular
Redux
React Router
Tailwind CSS
shadcn/ui
Material UI
Sass
Bootstrap
React Native
Node.js
Bun
Django
FastAPI
Spring Boot
Java Servlets
Express
PostgreSQL
MySQL
MS SQL Server
SQLite
Appwrite
Docker
AWS
Cloudflare
Nginx
Vercel
Netlify
DigitalOcean
Git
DBeaver
Postman
Bun
Django
FastAPI
Spring Boot
Java Servlets
Express
PostgreSQL
MySQL
MS SQL Server
SQLite
Appwrite
Docker
AWS
Cloudflare
Nginx
Vercel
Netlify
DigitalOcean
Git
DBeaver
Postman

Words I Live By

Shahathir is currently not listening to anything
Shahathir is currently not listening to anything

2026 © shahathir.me

Changelogs · Old site

  • AboutAbout
  • ThoughtsThoughts
  • TILTIL
  • BookmarksBookmarks
  • ExperienceExperience
  • ProjectsProjects
  • AccoladesAccolades
  • PhotographyPhotography
  • SongsSongs
  • StatsStats
  • UsesUses
  • ChatChat
  • Resume BuilderResume Builder
  • AboutAbout
  • ThoughtsThoughts
  • TILTIL
  • BookmarksBookmarks
  • ExperienceExperience
  • ProjectsProjects
  • AccoladesAccolades
  • PhotographyPhotography
  • SongsSongs
  • StatsStats
  • UsesUses
  • ChatChat
  • Resume BuilderResume Builder
July 2, 2026

asyncio will garbage-collect your fire-and-forget task

Background chat-title generation kept vanishing with no error — the cause: asyncio only keeps a weak reference to a bare create_task, so nothing held it alive.

I wanted chat sessions to name themselves. When a conversation starts, kick off a cheap, separate model call built from the first prompt, let it run in the background, and drop the title into the sidebar the moment it's ready — so it shows up seconds in and lands even if the main turn later pauses or the client disconnects. Classic fire-and-forget: create_task, don't await.

It worked. Mostly. Every so often a conversation just never got a title, with no error and no pattern I could pin down.

The cause is a genuine asyncio footgun, and it's in the docs if you go looking: the event loop keeps only a weak reference to a task. If nothing in your code holds a reference to it, the garbage collector is free to collect it mid-flight — and your background work just quietly stops. await and gather hold references, which is why you never notice this until the first time you truly fire-and-forget.

The fix is three lines: keep a strong reference, and drop it when the task finishes.

_background: set[asyncio.Task] = set()   # strong refs; the loop only keeps weak ones
 
def spawn(coro) -> None:
    task = asyncio.create_task(coro)
    _background.add(task)                 # <- this is what keeps it alive
    task.add_done_callback(_background.discard)

Two more things I got wrong on the way, both about "cosmetic work must never break the real work":

  • The task must never raise. A missing title is cosmetic; it must not bubble up as a failure on the stream the user is actually watching. So it swallows everything except CancelledError (which it re-raises, so cooperative cancellation still works) and falls back to the first line of the prompt.
  • Generate from the prompt alone. My first version built the title from the prompt and the finished answer, so it couldn't run until the turn completed — and a turn that paused for approval never got one. Decoupling it from the assistant's reply is what let it run early and survive.

The reframe I keep: "fire-and-forget" in asyncio is really "fire, hold a reference, and forget." The garbage collector has no idea your background job matters unless something is still pointing at it.