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
SeaCat tutorial - Chapter 2: Simple Post (iOS)
The goal of this article is to create a simple iOS client which generates a simple POST Request which will be read in host written in Node.js and the output generated in the console. The whole comunication will be handled by SeaCat which help us to establish fast and secure connection among our key components.
Published on September 09, 2014
Situations Where Mobile App Security Best Practices is Necessary
The use of mobile app security best practices has become a necessity as app development and mobile usage continue to grow. These practices are needed to improve consumer protection, trust, and regulatory compliance.
Published on March 24, 2015
Entangled ways of product development in the area of cybersecurity #1 - Asynchronous or parallel?
I started working at TeskaLabs at the beginning of autumn 2017 as a student at the Faculty of Information Technology of CTU. In the job advertisement, I was particularly interested in the fact that it is a small, product-based company that does not focus on just one technology or one programming language.
Published on November 15, 2022