Kernel: Fix deadlock in ext2 filesystem

If multiple threads were waiting for more block buffers without anyone
releasing them, they ended up in a deadlock.

Now we store 6 blocks for 8 threads. If a thread already has a block
buffer, it will not have to wait for a new one. Only if there are more
than 8 threads using blocks, will it block until there are free slots
for a thread available.
This commit is contained in:
Bananymous 2026-01-10 00:30:30 +02:00
parent 2961a49dc7
commit ed82a18e2a
2 changed files with 98 additions and 28 deletions

View File

@ -28,36 +28,28 @@ namespace Kernel
BAN_NON_COPYABLE(BlockBufferWrapper); BAN_NON_COPYABLE(BlockBufferWrapper);
public: public:
BlockBufferWrapper(BAN::Span<uint8_t> buffer, bool* used, Mutex* mutex, ThreadBlocker* blocker) BlockBufferWrapper(BAN::Span<uint8_t> buffer, void (*callback)(void*, const uint8_t*), void* argument)
: m_buffer(buffer) : m_buffer(buffer)
, m_used(used) , m_callback(callback)
, m_mutex(mutex) , m_argument(argument)
, m_blocker(blocker) { }
{
ASSERT(m_used && *m_used);
}
BlockBufferWrapper(BlockBufferWrapper&& other) { *this = BAN::move(other); } BlockBufferWrapper(BlockBufferWrapper&& other) { *this = BAN::move(other); }
~BlockBufferWrapper() ~BlockBufferWrapper()
{ {
if (m_used == nullptr) if (m_callback == nullptr)
return; return;
m_mutex->lock(); m_callback(m_argument, m_buffer.data());
*m_used = false;
m_blocker->unblock();
m_mutex->unlock();
} }
BlockBufferWrapper& operator=(BlockBufferWrapper&& other) BlockBufferWrapper& operator=(BlockBufferWrapper&& other)
{ {
this->m_buffer = other.m_buffer; this->m_buffer = other.m_buffer;
this->m_used = other.m_used; this->m_callback = other.m_callback;
this->m_mutex = other.m_mutex; this->m_argument = other.m_argument;
this->m_blocker = other.m_blocker;
other.m_buffer = {}; other.m_buffer = {};
other.m_used = nullptr; other.m_callback = nullptr;
other.m_mutex = nullptr; other.m_argument = nullptr;
other.m_blocker = nullptr;
return *this; return *this;
} }
@ -75,9 +67,8 @@ namespace Kernel
private: private:
BAN::Span<uint8_t> m_buffer; BAN::Span<uint8_t> m_buffer;
bool* m_used; void (*m_callback)(void*, const uint8_t*);
Mutex* m_mutex; void* m_argument;
ThreadBlocker* m_blocker;
}; };
public: public:
@ -130,6 +121,9 @@ namespace Kernel
BAN::ErrorOr<void> initialize(size_t block_size); BAN::ErrorOr<void> initialize(size_t block_size);
private:
void destroy_callback(const uint8_t* buffer_ptr);
private: private:
struct BlockBuffer struct BlockBuffer
{ {
@ -137,10 +131,20 @@ namespace Kernel
bool used { false }; bool used { false };
}; };
struct ThreadInfo
{
pid_t tid { 0 };
size_t buffers { 0 };
};
private: private:
static constexpr size_t max_threads = 8;
static constexpr size_t max_buffers_per_thread = 6;
Mutex m_buffer_mutex; Mutex m_buffer_mutex;
ThreadBlocker m_buffer_blocker; ThreadBlocker m_buffer_blocker;
BAN::Array<BlockBuffer, 16> m_buffers; BAN::Array<BlockBuffer, max_threads * max_buffers_per_thread> m_buffers;
BAN::Array<ThreadInfo, max_threads> m_thread_infos;
}; };
private: private:

View File

@ -532,22 +532,88 @@ namespace Kernel
}; };
} }
BAN::ErrorOr<Ext2FS::BlockBufferWrapper> Ext2FS::BlockBufferManager::get_buffer() void Ext2FS::BlockBufferManager::destroy_callback(const uint8_t* buffer_ptr)
{ {
const auto tid = Thread::current_tid();
LockGuard _(m_buffer_mutex); LockGuard _(m_buffer_mutex);
for (;;) for (auto& buffer : m_buffers)
{ {
for (auto& buffer : m_buffers) if (buffer.buffer.data() != buffer_ptr)
continue;
ASSERT(buffer.used);
buffer.used = false;
break;
}
for (auto& info : m_thread_infos)
{
if (info.tid != tid)
continue;
ASSERT(info.buffers > 0);
info.buffers--;
if (info.buffers != 0)
break;
info.tid = 0;
m_buffer_blocker.unblock();
break;
}
}
BAN::ErrorOr<Ext2FS::BlockBufferWrapper> Ext2FS::BlockBufferManager::get_buffer()
{
const auto tid = Thread::current_tid();
ASSERT(tid);
LockGuard _(m_buffer_mutex);
ThreadInfo* thread_info = nullptr;
for (auto& info : m_thread_infos)
{
if (info.tid != tid)
continue;
thread_info = &info;
break;
}
while (thread_info == nullptr)
{
for (auto& info : m_thread_infos)
{ {
if (buffer.used) if (info.tid != 0)
continue; continue;
buffer.used = true; thread_info = &info;
return Ext2FS::BlockBufferWrapper(buffer.buffer.span(), &buffer.used, &m_buffer_mutex, &m_buffer_blocker); break;
}
if (thread_info)
{
thread_info->tid = tid;
thread_info->buffers = 0;
break;
} }
TRY(Thread::current().block_or_eintr_indefinite(m_buffer_blocker, &m_buffer_mutex)); TRY(Thread::current().block_or_eintr_indefinite(m_buffer_blocker, &m_buffer_mutex));
} }
ASSERT(thread_info->buffers < max_buffers_per_thread);
thread_info->buffers++;
for (auto& buffer : m_buffers)
{
if (buffer.used)
continue;
buffer.used = true;
return Ext2FS::BlockBufferWrapper {
buffer.buffer.span(),
[](void* self, const uint8_t* buffer) { static_cast<BlockBufferManager*>(self)->destroy_callback(buffer); },
this
};
}
ASSERT_NOT_REACHED();
} }
BAN::ErrorOr<void> Ext2FS::BlockBufferManager::initialize(size_t block_size) BAN::ErrorOr<void> Ext2FS::BlockBufferManager::initialize(size_t block_size)