Image by brgfx on Freepik
Inotify in ASAB Library
🇨🇿 Česká verze je níže / Czech version is below.
At TeskaLabs, we develop an open-source framework, ASAB, that helps us make writing microservices easy as breathing. One of its components allows the microservices to read from something we call a Library. It is rather a concept than a codebase. The Library contains common files dynamically read by a microservice. Thus, all microservices in a product can share the same content.
The subscribe functionality of ASAB Library enables microservices to be notified of any new changes. Library items live among a few destinations like a local file system, remote git repository, Azure, or ZooKeeper. In this blog post, I would like to share what I've learned when implementing subscribe feature for files on the file system.
Inotify
Inotify is a Linux kernel subsystem that allows applications to monitor filesystem events such as file modifications, creations, and deletions in real time. I started by looking for Python libraries implementing inotify. Asyncinotify and inotify-simple caught my eye, but they were excessive for my purpose and seemed a bit clumsy to me. Since ASAB is a framework our products rely on, we are cautious about introducing new dependencies. I had to connect with inotify myself.
Creating a Custom Inotify Implementation
Naturally, I started robbing the libraries mentioned above but soon got into trouble. Inotify uses file descriptors to propagate notifications. The application receives a unique file descriptor to monitor specific files or directories. When an event occurs, the kernel writes an event notification to the file descriptor. The application then reads this data to take appropriate action.
All I needed was to open the file descriptor and wait for an event. Unfortunately, this is a blocking operation being introduced into the fully asynchronous world of ASAB. After consulting Chat GPT for advice, I received a well-designed code suggestion.
async def inotify_events():
# initialize inotify
fd = inotify_init()
# add watch for directory
watch_descriptor = inotify_add_watch(
fd,
b'/path/to/directory',
IN_ALL_EVENTS
)
# create async file descriptor
reader = asyncio.open_file(fd, mode='rb')
while True:
await reader.read()
buffer = os.read(fd, 4096)
for event in buffer:
print(event)
Amazing! Isn't it? I was amazed. It was perfect.
Except asyncio.open_file()
method doesn't exist.
Understanding Ctypes and Masks
Playing with inotify, I met ctypes
library for the first time, interacting with the C library. Exciting. I stole most of it from inotify-simple
, though.
I also learned about bitmasks, which specify the event types that should be monitored. At first, I thought those were just some flags, but I was amazed when bitmask operations were shown to me. Being a biologist in IT brings me many "light bulb moments" when I learn something that is probably taught in the first year of Computer Science studies. For example, IN_CREATE
mask is represented by 00000100 byte. An event talking about new file creation should carry this mask. Using AND operation with these masks, I could tell whether a file was created.
Overcoming the Blocking Read Challenge
I kept trying to read from the file descriptor - maybe I could check the file descriptor from time to time or try a second thread. It was ugly, and I was desperate. Fortunately, asyncio
library in Python provides a miraculous tool to work with the file descriptors. The asyncio loop, the backbone of the ASAB Application can operate on file descriptors asynchronously. This allowed me to effectively integrate inotify into the ASAB framework. (And gave me the lesson again: Read the documentation.)
Happy Ending
My inotify implementation might not cover all of inotify's functionality, but it is sufficient for our use case and lightweight, which is very important when contributing to a framework codebase. By overcoming the blocking read challenge and learning about ctypes and bitmasks, I created a lean and efficient solution that enables the ASAB framework to react to changes in the Library in real time.
🇨🇿 Inotify a ASAB
Ve společnosti TeskaLabs vyvíjíme open-source framework ASAB, který nám pomáhá psát mikroslužby. Jedna z jeho součástí umožňuje mikroslužbám číst ze souborů, kterým dohromady říkáme knihovna. Knihovna obsahuje běžné soubory, které mikroslužba dynamicky čte. Všechny mikroslužby v produktu tak mohou sdílet stejný obsah.
ASAB umožňuje, aby byly mikroslužby informovány o všech nových změnách. Položky knihovny žijí na několika místech - místní souborový systém, vzdálený repozitář v git, Azure nebo ZooKeeper. V tomto blogovém příspěvku bych se chtěla podělit o to, co jsem se naučila při detekci změn v souborech na lokálním disku.
Inotify
Inotify je subsystém linuxového jádra, který umožňuje aplikacím sledovat události v systému souborů, jako jsou změny, vytváření a mazání souborů v reálném čase. Začala jsem hledáním knihoven Pythonu implementujících inotify. Do oka mi padly Asyncinotify a inotify-simple, ale pro můj účel byly zbytečně složité a připadaly mi trochu neohrabané. Vzhledem k tomu, že ASAB je framework, na který se naše produkty spoléhají, jsme při zavádění nových závislostí opatrní. Nezbylo tedy než se spojit s inotify přímo.
Vytvoření vlastní implementace Inotify
Samozřejmě jsem začala vykrádáním výše zmíněných knihoven, ale brzy jsem se dostal do problémů. Inotify používá k šíření oznámení dsile descriptors. Aplikace dostává jedinečný "deskriptor", který monitoruje konkrétní soubory nebo adresáře. Když dojde k události, jádro zapíše do tohoto deskriptoru oznámení o události. Aplikace pak tato data přečte a provede příslušnou akci.
Stačilo otevřít přidělený deskriptor a počkat na událost. Jenže se jedná o blokující operaci, která se vůbec nehodí do plně asynchronního světa ASABu. Teprve jsem se seznamovala s Chat GPT a jeho rady mi připadaly zázračné. Dostala jsem z něj následující návrh kódu:
async def inotify_events():
# initialize inotify
fd = inotify_init()
# add watch for directory
watch_descriptor = inotify_add_watch(
fd,
b'/path/to/directory',
IN_ALL_EVENTS
)
# create async file descriptor
reader = asyncio.open_file(fd, mode='rb')
while True:
await reader.read()
buffer = os.read(fd, 4096)
for event in buffer:
print(event)
Úžasné! Že ano? Byla jsem ohromena. Bylo to dokonalé.
Až na to, že metoda asyncio.open_file()
neexistuje.
Porozumění typům ze světa C a maskám
Při hraní s inotify jsem se poprvé setkala s pythoní knihovnou ctypes
, která komunikuje se základní knihovnou jazyka C. Vzrušující. (Většinu kódu jsem si ale půjčila z inotify-simple
.)
Dozvěděl jsem se také o maskách, které určují typy událostí, jež mají být sledovány. Nejdřív jsem si myslel, že to jsou jen nějaké příznaky, kódy, ale pak jsem byla poučena, že s těmito maskami mohu rpovádět binární operace. Být bioložkou v IT mi přináší mnoho momentů náhlého prozření, kdy se dozvím něco, co se pravděpodobně učí v prvním ročníku studia informatiky. Například masku IN_CREATE
reprezentuje bajt 00000100. Událost hovořící o vytvoření nového souboru by měla nést tuto masku. Pomocí operace AND s těmito maskami mohu zjistit, zda byl vytvořen nový soubor.
Zkrocení blokujícího čtení deskriptoru
Stále jsem se snažila číst z deskriptoru - možná bych ho mohla čas od času zkontrolovat nebo zkusit druhé vlákno? Kód nevypadal nijak hezky a já byla nešťastná. Naštěstí knihovna asyncio
poskytuje zázračný nástroj pro práci s file descriptors. Vnitřní asnychronní smyčka, páteř aplikace ASAB, dokáže pracovat s deskriptory asynchronně. To mi umožnilo efektivně integrovat inotify do frameworku ASAB. (A opět mě to naučilo číst dokumentaci raději dřív než se pustím do zoufalého kódování.)
Happy Ending
Moje implementace inotify možná nepokrývá všechny mořné funkce, ale pro náš případ je dostačující, jednoduchá a snadno udržitelná, což je při psaní frameworku velmi důležité. Poprala jsem se s blokující operací, objevila jsem ctypes
a masky a při tom jsem vytvořila efektivní řešení, které umožňuje frameworku ASAB reagovat na změny v knihovně v reálném čase.
Most Recent Articles
- A beginner-friendly intro to the Correlator for effective cybersecurity detection
- From State Machine to Stateless Microservice
- Entangled ways of product development in the area of cybersecurity #3 - LogMan.io
- Entangled ways of product development in the area of cybersecurity #2 - BitSwan
- Entangled ways of product development in the area of cybersecurity #1 - Asynchronous or parallel?
You Might Be Interested in Reading These Articles
Entangled ways of product development in the area of cybersecurity #2 - BitSwan
After successfully completing my engineering degree, I finally started working full-time at TeskaLabs just as I initially promised. In addition to data from the world of telecommunications, we started to learn data from the world of logistics in BitSwan, which of course required being able to calculate the cost of transporting some cargo from point A to point B.
Published on December 15, 2022
Software architect's point of view: Why use SeaCat
I've recently received an interesting question from one software architect: Why should he consider embedding SeaCat in his intended mobile application? This turned into a detailed discussion and I realised that not every benefit of SeaCat technology is apparent at first glance. Let me discuss the most common challenges of a software developer in the area of secure mobile communication and the way SeaCat helps to resolve them. The initial impulse for building SeaCat was actually out of frustration of repeating development challenges linked with implementation of secure mobile application communication. So let's talk about the most common challenges and how SeaCat address them.
Published on April 16, 2014
Why Developers Are Boosting Up Their Mobile Application Security?
Mobile application security is a significant issue for developers. Most try their best to make mobile apps secure and safe for their users. Here are some of the other reasons why developers are boosting up their mobile application security.
Published on April 14, 2015