Hey ![]()
I suspect, that I’ve stumbled upon a bug in Agent++, but I may be wrong, and I’m simply using the Agent++ API incorrectly.
First of all, the POSIX_THREADS macro is defined.
Thus, a pthread_mutex_t monitor exists as a member in Synchronized.
Furthermore, the NO_FAST_MUTEXES macro is defined.
Thus, the LockRequest and LockQueue classes are used - especially in RequestList::lock_request and RequestList::unlock_request.
Note, that RequestList::receive calls RequestList::lock_request.
Note, that Mib::process_request calls RequestList::unlock_request.
Therefore, RequestList::receive and Mib::process_request lock/unlock mutexes of the Requests via the LockQueue thread (LockQueue::run).
This seems to be intended.
If RequestList::receive returns a Request, that Request is locked via the LockQueue thread, and becomes an element of the RequestList.
This also seems to be intended.
If Mib::process_request is called, that Request is unlocked via the LockQueue thread, and removed from the RequestList.
This also seems to be intended.
Consider a code snippet, that looks something like the following (obviously in a very stripped-down version):
while (!stop) {
snmpRequest = snmpRequestList->receive(2);
if (snmpRequest && processSnmpRequests)
snmpMib->process_request(snmpRequest);
else
snmpMib->cleanup();
}
delete snmpRequestList;
Note, that if processSnmpRequests is set to false, we receive but don’t process the Requests.
When stop is set to true, delete snmpRequestList calls ~RequestList.
Assume, that snmpRequestList contains at least one unprocessed Request, when ~RequestList is called.
Since Mib::process_request wasn’t called, RequestList::unlock_request wasn’t called, thus the LockQueue thread never unlocks the mutexes of the Requests.
Via ~RequestList we get to ~Request (for each Request in the RequestList), and thus to ~Synchronized.
The relevant part of that destructor:
Synchronized::~Synchronized()
{
#ifdef POSIX_THREADS
int result;
result = pthread_cond_destroy(&cond);
if (result) {
LOG_BEGIN(loggerModuleName, ERROR_LOG | 2);
LOG("Synchronized cond_destroy failed with (result)(ptr)");
LOG(result);
LOG((unsigned long)this);
LOG_END;
}
result = pthread_mutex_destroy(&monitor);
#ifdef NO_FAST_MUTEXES
if( result == EBUSY ) {
// wait for other threads ...
if( EBUSY == pthread_mutex_trylock(&monitor) )
pthread_mutex_lock(&monitor); // another thread owns the mutex, let's wait ...
Note, that ~Synchronized is NOT called in the LockQueue thread, but in the thread, which calls ~RequestList, thus we get a deadlock in the line with the “another thread owns the mutex, let’s wait” comment.
This is not just a theoretical problem - we ran into exactly this, and I discovered it via inspecting a core dump and reading the source code of Agent++.
If this is actually a bug in Agent++, and you plan to fix it, what can we do in the meantime, as a sensible workaround on our side?
And if this is not a bug in Agent++, but intended behavior instead, what are we doing wrong?
If you need more information, please let me know.
I’m not a regular user of Agent++, in fact I’ve never used it before.
I was provided a deadlock scenario, and this is what I found.
Thanks in advance ![]()