beegfs/client_module/source/toolkit/NoAllocBufferStore.c
2025-08-10 01:34:16 +02:00

310 lines
7.4 KiB
C

#include <toolkit/NoAllocBufferStore.h>
#include <common/toolkit/tree/PointerRBTree.h>
#include <common/toolkit/tree/PointerRBTreeIter.h>
#ifdef BEEGFS_DEBUG
static int __NoAllocBufferStore_PointerComparator(const void* ptr1, const void* ptr2);
static void __NoAllocBufferStore_debugAddTask(NoAllocBufferStore* this);
static void __NoAllocBufferStore_debugRemoveTask(NoAllocBufferStore* this);
static void __NoAllocBufferStore_debugCheckTask(NoAllocBufferStore* this);
#endif
/**
* This BufferStore does not use any kind of memory allocation during its normal
* operation. However, it does (de)allocate the requested number of buffers
* during (de)initialization.
*/
static bool __NoAllocBufferStore_initBuffers(NoAllocBufferStore* this);
struct NoAllocBufferStore
{
char** bufArray;
size_t numBufs;
size_t bufSize;
size_t numAvailable; // number of currently available buffers in the store
Mutex mutex;
Condition newBufCond;
#ifdef BEEGFS_DEBUG
RBTree pidDebugTree; // store tasks that have taken a buffer
#endif
};
#ifdef BEEGFS_DEBUG
/**
* Return suitable values for a tree
*/
static int __NoAllocBufferStore_PointerComparator(const void* ptr1, const void* ptr2)
{
if (ptr1 < ptr2)
return -1;
else if (ptr1 == ptr2)
return 0;
return 1;
}
#endif // BEEGFS_DEBUG
/**
* @param numBufs number of buffers to be allocated, may be 0
* @param bufSize size of each allocated buffer
*/
bool NoAllocBufferStore_init(NoAllocBufferStore* this, size_t numBufs, size_t bufSize)
{
this->numAvailable = 0;
this->numBufs = numBufs;
this->bufSize = bufSize;
Mutex_init(&this->mutex);
Condition_init(&this->newBufCond);
#ifdef BEEGFS_DEBUG
PointerRBTree_init(&this->pidDebugTree, __NoAllocBufferStore_PointerComparator);
#endif
if(!__NoAllocBufferStore_initBuffers(this) )
{
Mutex_uninit(&this->mutex);
return false;
}
return true;
}
#ifdef BEEGFS_DEBUG
/**
* Add the buffer allocator into the debug tree, including the call trace. That way we can easily
* figure out two (forbidden) calls from the same thread. For debug builds only!
*/
static void __NoAllocBufferStore_debugAddTask(NoAllocBufferStore* this)
{
void * trace = os_saveStackTrace();
PointerRBTree_insert(&this->pidDebugTree, (void *)(long)current->pid, trace);
}
#else
#define __NoAllocBufferStore_debugAddTask(this)
#endif // BEEGFS_DEBUG
#ifdef BEEGFS_DEBUG
/**
* Tasks frees the buffer, so also remove it from the debug tree. For debug builds only!
*/
static void __NoAllocBufferStore_debugRemoveTask(NoAllocBufferStore* this)
{
void * currentPID = (void *)(long)current->pid;
RBTreeIter iter = PointerRBTree_find(&this->pidDebugTree, currentPID);
void *trace;
if (PointerRBTreeIter_end(&iter) == true)
return; // Buffer was likely/hopefully added with NoAllocBufferStore_instantBuf()
trace = PointerRBTreeIter_value(&iter);
os_freeStackTrace(trace);
PointerRBTree_erase(&this->pidDebugTree, currentPID);
}
#else
#define __NoAllocBufferStore_debugRemoveTask(this)
#endif
#ifdef BEEGFS_DEBUG
/**
* Check if the debug tree already knows about the current pid and if so, print a debug message
* to syslog to warn about the possible deadlock. NOTE: For debug builds only!
*/
static void __NoAllocBufferStore_debugCheckTask(NoAllocBufferStore* this)
{
RBTreeIter iter = PointerRBTree_find(&this->pidDebugTree, (void *)(long)current->pid);
if (unlikely(PointerRBTreeIter_end(&iter) == false) )
{
void *trace = PointerRBTreeIter_value(&iter);
printk_fhgfs(KERN_WARNING, "Thread called BufferStore two times - might deadlock!\n");
dump_stack();
printk_fhgfs(KERN_WARNING, "Call trace of the previous allocation:\n");
os_printStackTrace(trace, 4);
}
}
#else
#define __NoAllocBufferStore_debugCheckTask(this)
#endif // BEEGFS_DEBUG
/**
* @param numBufs number of buffers to be allocated
* @param bufSize size of each allocated buffer
*/
struct NoAllocBufferStore* NoAllocBufferStore_construct(size_t numBufs, size_t bufSize)
{
struct NoAllocBufferStore* this = kmalloc(sizeof(*this), GFP_NOFS);
if(!this ||
!NoAllocBufferStore_init(this, numBufs, bufSize) )
{
kfree(this);
return NULL;
}
return this;
}
void NoAllocBufferStore_uninit(NoAllocBufferStore* this)
{
// delete buffers
size_t i;
for(i=0; i < this->numAvailable; i++)
vfree(this->bufArray[i]);
// normal clean-up
Mutex_uninit(&this->mutex);
SAFE_KFREE(this->bufArray);
#ifdef BEEGFS_DEBUG
PointerRBTree_uninit(&this->pidDebugTree);
#endif
}
void NoAllocBufferStore_destruct(NoAllocBufferStore* this)
{
NoAllocBufferStore_uninit(this);
kfree(this);
}
/**
* Gets a buffer from the store.
* Waits if no buffer is immediately available.
*
* @return a valid buffer pointer
*/
char* NoAllocBufferStore_waitForBuf(NoAllocBufferStore* this)
{
char* buf;
Mutex_lock(&this->mutex);
__NoAllocBufferStore_debugCheckTask(this);
while(!this->numAvailable)
Condition_wait(&this->newBufCond, &this->mutex);
buf = (this->bufArray)[this->numAvailable-1];
(this->numAvailable)--;
__NoAllocBufferStore_debugAddTask(this);
Mutex_unlock(&this->mutex);
return buf;
}
/**
* Gets a buffer from the store.
* Fails if no buffer is immediately available.
*
* @return buffer pointer if a buffer was immediately available, NULL otherwise
*/
char* NoAllocBufferStore_instantBuf(NoAllocBufferStore* this)
{
char* buf;
Mutex_lock(&this->mutex);
if(!this->numAvailable)
buf = NULL;
else
{
buf = (this->bufArray)[this->numAvailable-1];
(this->numAvailable)--;
/* note: no _debugAddTask() here, because _instantBuf() is specifically intended to avoid
deadlocks (so we would generate only a lot of false alarms with this). */
}
Mutex_unlock(&this->mutex);
return buf;
}
/**
* Re-add a buffer to the pool
*/
void NoAllocBufferStore_addBuf(NoAllocBufferStore* this, char* buf)
{
if (unlikely(buf == NULL) )
{
BEEGFS_BUG_ON(buf == NULL, "NULL buffer detected!");
return; // If the caller had a real buffer, it will leak now!
}
Mutex_lock(&this->mutex);
Condition_signal(&this->newBufCond);
(this->bufArray)[this->numAvailable] = buf;
(this->numAvailable)++;
__NoAllocBufferStore_debugRemoveTask(this);
Mutex_unlock(&this->mutex);
}
bool __NoAllocBufferStore_initBuffers(NoAllocBufferStore* this)
{
size_t i;
this->bufArray = this->numBufs ? (char**)os_kzalloc(this->numBufs * sizeof(char*) ) : NULL;
if (!this->bufArray && (this->numBufs != 0) ) // numBufs is 0 for pageIO buffers in buffered mode
return false;
for(i = 0; i < this->numBufs; i++)
{
char* buf = vmalloc(this->bufSize);
if(!buf)
{
printk_fhgfs(KERN_WARNING, "NoAllocBufferStore_initBuffers: vmalloc failed to alloc "
"%lld bytes for buffer number %lld\n", (long long)this->bufSize, (long long)i);
goto error;
}
(this->bufArray)[i] = buf;
(this->numAvailable)++;
}
return true;
error:
for(i = 0; i < this->numBufs; i++)
vfree(this->bufArray[i]);
return false;
}
size_t NoAllocBufferStore_getNumAvailable(NoAllocBufferStore* this)
{
size_t numAvailable;
Mutex_lock(&this->mutex);
numAvailable = this->numAvailable;
Mutex_unlock(&this->mutex);
return numAvailable;
}
size_t NoAllocBufferStore_getBufSize(NoAllocBufferStore* this)
{
return this->bufSize;
}