25 Memory reclaimer proposal
Object lifetime issues
We have been having issues with using a ThreadBlockerWhileUsed to manage the lifetime of objects. Perhaps the issue relates to this: A surprise with mutexes and reference counts by Jonathan Corbet December 4, 2013.
Even if C++11 spec guarantees a ThreadBlockerWhileUsed can be used, maybe it's a bad idea because an implementation of std::mutex might be broken.
Consider that we go with essentially the current design which uses a ThreadBlockerWhileUsed, except the actual deletion of the object is delayed, by calling ScheduleWithDelay
struct X
{
X() : count_(2)
{
Async(DoWork());
}
void Dec()
{
std::lock_guard<std::mutex> lock(mutex_);
if (--count_ == 0)
{
cv_.notify_all();
}
}
void Close()
{
Dec();
{
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock,[this]{return count_ == 0;});
}
delete this;
}
void DoWork()
{
...
Dec();
}
std::mutex mutex_;
std::condition_variable cv_;
int count_;
};
void main()
{
X* x = new X;
x->Close();
}
//////////// X.h
struct X;
X* Open();
void Close();
//////////// X.cpp
struct X
{
X() : blocker(2)
{
}
void Open()
{
Async(DoWork());
}
void Close()
{
--blocker;
blocker.Wait();
}
void DoWork()
{
...
--blocker;
}
ThreadBlockerWhileUsed blocker;
};
X* Open()
{
X* x = new X();
x->Open();
return x;
}
void Close(X* x)
{
x->Close();
// x will be deleted, but will take at least 100 msec!
ScheduleWithDelay( [] { delete x; }, 100 );
};
class Scheduler
{
public:
// Not copyable
Scheduler& operator=(const Scheduler& rhs) = delete;
Scheduler(const Scheduler& rhs) = delete;
// Not movable
Scheduler& operator=(Schedular&& rhs) = delete;
Scheduler(Schedular&& rhs) = delete;
void Close()
{
stop_ = true;
thread_.join();
for (auto& t : newTasks_)
{
t();
}
newTasks_.clear();
}
void Add(Task t)
{
std::lock_guard lock(mutex_);
newTasks_.push_back(t);
}
void Run()
{
xvector<Task> pendingTasks;
while(1)
{
Sleep(100);
// Execute the pending tasks
for (auto& t : pendingTasks)
{
t();
}
pendingTasks.clear();
if (stop_) break;
{
std::lock_guard lock(mutex_);
std::swap(newTasks_, pendingTasks);
}
}
}
private:
std::atomic<bool> stop_;
std::thread thread_;
std::mutex mutex_;
xvector<Task> newTasks_;
};
Proposal
We want a facility for providing
- a system wide thread pool
- supports boost asio for I/O services
- supports posting of tasks to be executed in the future.
This facility must be explicitly started and stopped.
It solves all the following:
- cxMessage requirements for creating TCP servers, clients and connected sockets It elimninates the need to create Iocp?
- LSS scheduled jobs:
- lazy check pointer
- lazy cleaner
- lazy flusher
- lazy writer
- Postponed memory reclamation
- Async DGS
This needs to be done in a way that's compatible with web assembly. Also it needs to use a small number of threads, so a ceda process doesn't waste too much memory for stack space.
IDEA for delayed tasks: We want to be able to post a task with a delay. Unfortunately this raises questions about how to abort it. So maybe it falls under the control of a TaskExecuter? That way we can close the task executer to quickly close all the delayed tasks it owns?
Reclaimer
The purpose is to reclaim memory with a delay. It's a thread-safe singleton. We don't care which thread does the deleting. It can be justified for two reasons:
- Memory deletion is actually expensive, best to offload it so GUI threads etc are more responsive
- Avoid assumptions about mutexes, such as when use ThreadBlockerWhileUsed to delete itself.
public API:
void StartTheMemoryReclaimer()
void StopTheMemoryReclaimer()
void AsyncReclaimMemory(const std::function<void()>& reclaimFn);