Project: Gluster (Runtime / Cloud Native Storage)
File: datastructure-iobuf.md

Short for IO Buffer. It is one allocatable unit for the consumers of the IOBUF API, each unit hosts @page_size(defined in arena structure) bytes of memory. As initial step of processing a fop, the IO buffer passed onto GlusterFS by the other applications (FUSE VFS/ Applications using gfapi) is copied into GlusterFS space i.e. iobufs. Hence Iobufs are mostly allocated/deallocated in Fuse, gfapi, protocol xlators, and also in performance xlators to cache the IO buffers etc. ``` struct iobuf { union { struct list_head list; struct { struct iobuf *next; struct iobuf *prev; }; }; struct iobufarena *iobufarena; gflockt lock; / for ->ptr and ->ref / int ref; / 0 == passive, >0 == active / void ptr; / usable memory region by the consumer */ void free_ptr; / in case of stdalloc, this is the one to be freed not the ptr / }; ``` There may be need of multiple iobufs for a single fop, like in vectored read/write. Hence multiple iobufs(default 16) are encapsulated under one iobref. ``` struct iobref { gflockt lock; int ref; struct iobuf iobrefs; / list of iobufs / int alloced; / 16 by default, grows as required / int used; / number of iobufs added to this iobref / }; ``` One region of memory MMAPed from the operating system. Each region MMAPs @arenasize bytes of memory, and hosts @arenasize / @page_size IOBUFs. The same sized iobufs are grouped into one arena, for sanity of access. ``` struct iobuf_arena { union { struct list_head list; struct { struct iobuf_arena *next; struct iobuf_arena *prev; }; }; sizet pagesize; / size of all iobufs in this arena / sizet arenasize; /* this is equal to (iobufpool->arenasize / page_size) page_size / sizet pagecount; struct iobufpool *iobufpool; void *mem_base; struct iobuf iobufs; / allocated iobufs list */ int active_cnt; struct listhead passivelist; / list of passive iobufs / struct listhead activelist; / list of active iobufs / int passive_cnt; (unused by itself) */ uint64t alloccnt; / total allocs in this pool / int max_active; / max active buffers at a given time / }; ``` Pool of Iobufs. As there may be many Io buffers required by the filesystem, a pool of iobufs are preallocated and kept, if these preallocated ones are exhausted only then the standard malloc/free is called, thus improving the performance. Iobuf pool is generally one per process, allocated during glusterfsctxt init (glusterfsctxdefaults_init), currently the preallocated iobuf pool memory is freed on process exit. Iobuf pool is globally accessible across GlusterFs, hence iobufs allocated by any xlator can be accessed by any other xlators(unless iobuf is not

``` struct iobuf_pool { pthreadmutext mutex; sizet arenasize; /* size of memory region in arena */ sizet defaultpage_size; / default size of iobuf / int arena_cnt; struct listhead arenas[GFVARIABLEIOBUFCOUNT]; /* array of arenas. Each element of the array is a list of arenas holding iobufs of particular page_size */ struct listhead filled[GFVARIABLEIOBUFCOUNT]; / array of arenas without free iobufs / struct listhead purge[GFVARIABLEIOBUFCOUNT]; / array of of arenas which can be purged / uint64t requestmisses; /* mostly the requests for higher value of iobufs */ }; ``` ~~~ The default size of the iobuf_pool(as of yet): 1024 iobufs of 128Bytes = 128KB 512 iobufs of 512Bytes = 256KB 512 iobufs of 2KB = 1MB 128 iobufs of 8KB = 1MB 64 iobufs of 32KB = 2MB 32 iobufs of 128KB = 4MB 8 iobufs of 256KB = 2MB 2 iobufs of 1MB = 2MB Total ~13MB ~~~ As seen in the datastructure iobuf_pool has 3 arena lists. arenas: The arenas allocated during iobuf_pool create, are part of this list. This list also contains arenas that are partially filled i.e. contain few active and few passive iobufs (passivecnt !=0, activecnt!=0 except for initially allocated arenas). There will be by default 8 arenas of the sizes mentioned above. filled: If all the iobufs in the arena are filled(passive_cnt = 0), the arena is moved to the filled list. If any of the iobufs from the filled arena is iobuf_put, then the arena moves back to the 'arenas' list. purge: If there are no active iobufs in the arena(active_cnt = 0), the arena is moved to purge list. iobuf_put() triggers destruction of the arenas in this list. The arenas in the purge list are destroyed only if there is atleast one arena in 'arenas' list, that way there won't be spurious mmap/unmap of buffers. (e.g: If there is an arena (page_size=128KB, count=32) in purge list, this arena is destroyed(munmap) only if there is an arena in 'arenas' list with page_size=128KB). ``` struct iobuf iobuf_get (struct iobuf_pool iobuf_pool); ``` Creates a new iobuf of the default page size(128KB hard coded as of yet). Also takes a reference(increments ref count), hence no need of doing it explicitly after getting iobuf. ``` struct iobuf iobuf_get2 (struct iobuf_pool iobufpool, sizet page_size); ``` Creates a new iobuf of a specified page size, if page_size=0 default page size is considered. ``` if (requested iobuf size > Max iobuf size in the pool(1MB as of yet)) { Perform standard allocation(CALLOC) of the requested size and add it to the list iobufpool->arenas[IOBUFARENAMAXINDEX]. } else { -Round the page size to match the stndard sizes in iobuf pool. (eg: if 3KB is requested, it is rounded to 8KB). -Select the arena list corresponding to the rounded size (eg: select 8KB arena) If the selected arena has passive count > 0, then return the iobuf from this arena, set the counters(passive/active/etc.)

else the arena is full, allocate new arena with rounded size and standard page numbers and add to the arena list (eg: 128 iobufs of 8KB is allocated). } ``` Also takes a reference(increments ref count), hence no need of doing it explicitly after getting iobuf. ``` struct iobuf iobuf_ref (struct iobuf iobuf); ``` Take a reference on the iobuf. If using an iobuf allocated by some other xlator/function/, its a good practice to take a reference so that iobuf is not deleted by the allocator. ``` void iobuf_unref (struct iobuf *iobuf); ``` Unreference the iobuf, if the ref count is zero iobuf is considered free. ``` -Delete the iobuf, if allocated from standard alloc and return. -set the active/passive count appropriately. -if passive count > 0 then add the arena to 'arena' list. -if active count = 0 then add the arena to 'purge' list. ``` Every iobufref should have a corresponding iobufunref, and also every iobufget/2 should have a correspondning iobufunref. ``` struct iobref *iobref_new (); ``` Creates a new iobref structure and returns its pointer. ``` struct iobref iobref_ref (struct iobref iobref); ``` Take a reference on the iobref. ``` void iobref_unref (struct iobref *iobref); ``` Decrements the reference count of the iobref. If the ref count is 0, then unref all the iobufs(iobuf_unref) in the iobref, and destroy the iobref. ``` int iobref_add (struct iobref iobref, struct iobuf iobuf); ``` Adds the given iobuf into the iobref, it takes a ref on the iobuf before adding it, hence explicit iobuf_ref is not required if adding to the iobref. ``` int iobref_merge (struct iobref to, struct iobref from); ``` Adds all the iobufs in the 'from' iobref to the 'to' iobref. Merge will not cause the delete of the 'from' iobref, therefore it will result in another ref on all the iobufs added to the 'to' iobref. Hence iobref_unref should be performed both on 'from' and 'to' iobrefs (performing iobref_unref only on 'to' will not free the iobufs and may result in leak). ``` void iobref_clear (struct iobref *iobref); ``` Unreference all the iobufs in the iobref, and also unref the iobref. If all iobufrefs/iobufnew do not have correspondning iobuf_unref, then the iobufs are not freed and recurring execution of such code path may lead to huge memory leaks. The easiest way to identify if a memory leak is caused by iobufs is to take a statedump. If the statedump shows a lot of filled arenas then it is a sure sign of leak. Refer doc/debugging/statedump.md for more details. If iobufs are leaking, the next step is to find where the iobuf_unref went missing. There is no standard/easy way of debugging this, code reading and logs are the only ways. If there is a liberty to reproduce the memory leak at will, then logs(gfcallinginfo) in iobufref/unref might help. TODO: A easier way to debug iobuf leaks.