xsi_message_queue & xsi_semaphore: Use condition variables to wait.

This removes a lot of custom logic for managing waiting threads,
which was not even correct in all cases (and the code actually
acknowledged this with a big TODO about it, which weinhold
added all the way back in 2008!)
This commit is contained in:
Augustin Cavalier
2023-04-26 17:16:07 -04:00
parent 9747721a43
commit 6acd708e97
2 changed files with 76 additions and 183 deletions
+36 -90
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2008-2011, Haiku, Inc. All rights reserved.
* Copyright 2008-2023, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
@@ -36,23 +36,6 @@
namespace {
// Queue for holding blocked threads
struct queued_thread : DoublyLinkedListLinkImpl<queued_thread> {
queued_thread(Thread *_thread, int32 _message_length)
:
thread(_thread),
message_length(_message_length),
queued(false)
{
}
Thread *thread;
int32 message_length;
bool queued;
};
typedef DoublyLinkedList<queued_thread> ThreadQueue;
struct queued_message : DoublyLinkedListLinkImpl<queued_message> {
queued_message(const void *_message, ssize_t _length)
@@ -106,11 +89,12 @@ class XsiMessageQueue {
public:
XsiMessageQueue(int flags)
:
fBytesInQueue(0),
fThreadsWaitingToReceive(0),
fThreadsWaitingToSend(0)
fBytesInQueue(0)
{
mutex_init(&fLock, "XsiMessageQueue private mutex");
fWaitingToReceive.Init(this, "XsiMessageQueue");
fWaitingToSend.Init(this, "XsiMessageQueue");
SetIpcKey((key_t)-1);
SetPermissions(flags);
// Initialize all fields to zero
@@ -122,18 +106,11 @@ public:
// Implemented after sXsiMessageCount is declared
~XsiMessageQueue();
status_t BlockAndUnlock(Thread *thread, MutexLocker *queueLocker)
status_t BlockAndUnlock(ConditionVariableEntry *queueEntry, MutexLocker *queueLocker)
{
thread_prepare_to_block(thread, B_CAN_INTERRUPT,
THREAD_BLOCK_TYPE_OTHER, (void*)"xsi message queue");
// Unlock the queue before blocking
queueLocker->Unlock();
// TODO: We've got a serious race condition: If BlockAndUnlock() returned due to
// interruption, we will still be queued. A WakeUpThread() at this point will
// call thread_unblock() and might thus screw with our trying to re-lock the
// mutex.
return thread_block();
return queueEntry->Wait(B_CAN_INTERRUPT);
}
void DoIpcSet(struct msqid_ds *result)
@@ -146,29 +123,18 @@ public:
fMessageQueue.msg_ctime = (time_t)real_time_clock();
}
void Deque(queued_thread *queueEntry, bool waitForMessage)
void Dequeue(ConditionVariableEntry *queueEntry, bool waitForMessage)
{
if (queueEntry->queued) {
if (waitForMessage) {
fWaitingToReceive.Remove(queueEntry);
fThreadsWaitingToReceive--;
} else {
fWaitingToSend.Remove(queueEntry);
fThreadsWaitingToSend--;
}
}
queueEntry->Wait(B_RELATIVE_TIMEOUT, 0);
}
void Enqueue(queued_thread *queueEntry, bool waitForMessage)
void Enqueue(ConditionVariableEntry *queueEntry, bool waitForMessage)
{
if (waitForMessage) {
fWaitingToReceive.Add(queueEntry);
fThreadsWaitingToReceive++;
} else {
fWaitingToSend.Add(queueEntry);
fThreadsWaitingToSend++;
}
queueEntry->queued = true;
}
struct msqid_ds &GetMessageQueue()
@@ -252,18 +218,10 @@ public:
// Wake up all waiting thread for a message
// TODO: this can cause starvation for any
// very-unlucky-and-slow thread
while (queued_thread *entry = fWaitingToReceive.RemoveHead()) {
entry->queued = false;
fThreadsWaitingToReceive--;
thread_unblock(entry->thread, 0);
}
fWaitingToReceive.NotifyAll();
} else {
// Wake up only one thread waiting to send
if (queued_thread *entry = fWaitingToSend.RemoveHead()) {
entry->queued = false;
fThreadsWaitingToSend--;
thread_unblock(entry->thread, 0);
}
fWaitingToSend.NotifyOne();
}
}
@@ -279,11 +237,9 @@ private:
MessageQueue fMessage;
struct msqid_ds fMessageQueue;
uint32 fSequenceNumber;
uint32 fThreadsWaitingToReceive;
uint32 fThreadsWaitingToSend;
ThreadQueue fWaitingToReceive;
ThreadQueue fWaitingToSend;
ConditionVariable fWaitingToReceive;
ConditionVariable fWaitingToSend;
XsiMessageQueue* fLink;
};
@@ -402,16 +358,8 @@ XsiMessageQueue::~XsiMessageQueue()
mutex_destroy(&fLock);
// Wake up any threads still waiting
if (fThreadsWaitingToSend || fThreadsWaitingToReceive) {
while (queued_thread *entry = fWaitingToReceive.RemoveHead()) {
entry->queued = false;
thread_unblock(entry->thread, EIDRM);
}
while (queued_thread *entry = fWaitingToSend.RemoveHead()) {
entry->queued = false;
thread_unblock(entry->thread, EIDRM);
}
}
fWaitingToReceive.NotifyAll(EIDRM);
fWaitingToSend.NotifyAll(EIDRM);
// Free up any remaining messages
if (fMessageQueue.msg_qnum) {
@@ -447,8 +395,8 @@ XsiMessageQueue::Insert(queued_message *message)
fMessageQueue.msg_lspid = getpid();
fMessageQueue.msg_stime = real_time_clock();
fBytesInQueue += message->length;
if (fThreadsWaitingToReceive)
WakeUpThread(true /* WaitForMessage */);
WakeUpThread(true /* WaitForMessage */);
return false;
}
@@ -492,8 +440,8 @@ XsiMessageQueue::Remove(long typeRequested)
fMessageQueue.msg_rtime = real_time_clock();
fBytesInQueue -= message->length;
atomic_add(&sXsiMessageCount, -1);
if (fThreadsWaitingToSend)
WakeUpThread(false /* WaitForMessage */);
WakeUpThread(false /* WaitForMessage */);
return message;
}
@@ -763,30 +711,29 @@ _user_xsi_msgrcv(int messageQueueID, void *messagePointer,
if (message == NULL && !(messageFlags & IPC_NOWAIT)) {
// We are going to sleep
Thread *thread = thread_get_current_thread();
queued_thread queueEntry(thread, messageSize);
ConditionVariableEntry queueEntry;
messageQueue->Enqueue(&queueEntry, /* waitForMessage */ true);
uint32 sequenceNumber = messageQueue->SequenceNumber();
TRACE(("xsi_msgrcv: thread %d going to sleep\n", (int)thread->id));
TRACE(("xsi_msgrcv: thread %d going to sleep\n", (int)thread_get_current_thread_id()));
status_t result
= messageQueue->BlockAndUnlock(thread, &messageQueueLocker);
TRACE(("xsi_msgrcv: thread %d back to life\n", (int)thread->id));
= messageQueue->BlockAndUnlock(&queueEntry, &messageQueueLocker);
TRACE(("xsi_msgrcv: thread %d back to life\n", (int)thread_get_current_thread_id()));
messageQueueHashLocker.Lock();
messageQueue = sMessageQueueHashTable.Lookup(messageQueueID);
if (result == EIDRM || messageQueue == NULL || (messageQueue != NULL
&& sequenceNumber != messageQueue->SequenceNumber())) {
TRACE_ERROR(("xsi_msgrcv: message queue id %d (sequence = "
TRACE(("xsi_msgrcv: message queue id %d (sequence = "
"%" B_PRIu32 ") got destroyed\n", messageQueueID,
sequenceNumber));
return EIDRM;
} else if (result == B_INTERRUPTED) {
TRACE_ERROR(("xsi_msgrcv: thread %d got interrupted while "
"waiting on message queue %d\n",(int)thread->id,
TRACE(("xsi_msgrcv: thread %d got interrupted while "
"waiting on message queue %d\n", (int)thread_get_current_thread_id(),
messageQueueID));
messageQueue->Deque(&queueEntry, /* waitForMessage */ true);
messageQueue->Dequeue(&queueEntry, /* waitForMessage */ true);
return EINTR;
} else {
messageQueueLocker.Lock();
@@ -871,31 +818,30 @@ _user_xsi_msgsnd(int messageQueueID, const void *messagePointer,
if (goToSleep && !(messageFlags & IPC_NOWAIT)) {
// We are going to sleep
Thread *thread = thread_get_current_thread();
queued_thread queueEntry(thread, messageSize);
ConditionVariableEntry queueEntry;
messageQueue->Enqueue(&queueEntry, /* waitForMessage */ false);
uint32 sequenceNumber = messageQueue->SequenceNumber();
TRACE(("xsi_msgsnd: thread %d going to sleep\n", (int)thread->id));
result = messageQueue->BlockAndUnlock(thread, &messageQueueLocker);
TRACE(("xsi_msgsnd: thread %d back to life\n", (int)thread->id));
TRACE(("xsi_msgsnd: thread %d going to sleep\n", (int)thread_get_current_thread_id()));
result = messageQueue->BlockAndUnlock(&queueEntry, &messageQueueLocker);
TRACE(("xsi_msgsnd: thread %d back to life\n", (int)thread_get_current_thread_id()));
messageQueueHashLocker.Lock();
messageQueue = sMessageQueueHashTable.Lookup(messageQueueID);
if (result == EIDRM || messageQueue == NULL || (messageQueue != NULL
&& sequenceNumber != messageQueue->SequenceNumber())) {
TRACE_ERROR(("xsi_msgsnd: message queue id %d (sequence = "
TRACE(("xsi_msgsnd: message queue id %d (sequence = "
"%" B_PRIu32 ") got destroyed\n", messageQueueID,
sequenceNumber));
delete message;
notSent = false;
result = EIDRM;
} else if (result == B_INTERRUPTED) {
TRACE_ERROR(("xsi_msgsnd: thread %d got interrupted while "
"waiting on message queue %d\n",(int)thread->id,
TRACE(("xsi_msgsnd: thread %d got interrupted while "
"waiting on message queue %d\n", (int)thread_get_current_thread_id(),
messageQueueID));
messageQueue->Deque(&queueEntry, /* waitForMessage */ false);
messageQueue->Dequeue(&queueEntry, /* waitForMessage */ false);
delete message;
notSent = false;
result = EINTR;
+40 -93
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2008-2011, Haiku, Inc. All rights reserved.
* Copyright 2008-2023, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
@@ -37,23 +37,6 @@
namespace {
// Queue for holding blocked threads
struct queued_thread : DoublyLinkedListLinkImpl<queued_thread> {
queued_thread(Thread *thread, int32 count)
:
thread(thread),
count(count),
queued(false)
{
}
Thread *thread;
int32 count;
bool queued;
};
typedef DoublyLinkedList<queued_thread> ThreadQueue;
class XsiSemaphoreSet;
struct sem_undo : DoublyLinkedListLinkImpl<sem_undo> {
@@ -101,25 +84,21 @@ namespace {
class XsiSemaphore {
public:
XsiSemaphore()
: fLastPidOperation(0),
fThreadsWaitingToIncrease(0),
fThreadsWaitingToBeZero(0),
:
fLastPidOperation(0),
fValue(0)
{
fWaitingToIncrease.Init(this, "XsiSemaphore");
fWaitingToBeZero.Init(this, "XsiSemaphore");
}
~XsiSemaphore()
{
// For some reason the semaphore is getting destroyed.
// Wake up any remaing awaiting threads
while (queued_thread *entry = fWaitingToIncreaseQueue.RemoveHead()) {
entry->queued = false;
thread_unblock(entry->thread, EIDRM);
}
while (queued_thread *entry = fWaitingToBeZeroQueue.RemoveHead()) {
entry->queued = false;
thread_unblock(entry->thread, EIDRM);
}
fWaitingToIncrease.NotifyAll(EIDRM);
fWaitingToBeZero.NotifyAll(EIDRM);
// No need to remove any sem_undo request still
// hanging. When the process exit and doesn't found
// the semaphore set, it'll just ignore the sem_undo
@@ -138,51 +117,33 @@ public:
return true;
} else {
fValue += value;
if (fValue == 0 && fThreadsWaitingToBeZero > 0)
WakeUpThread(true);
else if (fValue > 0 && fThreadsWaitingToIncrease > 0)
WakeUpThread(false);
if (fValue == 0)
WakeUpThreads(true);
else if (fValue > 0)
WakeUpThreads(false);
return false;
}
}
status_t BlockAndUnlock(Thread *thread, MutexLocker *setLocker)
status_t BlockAndUnlock(ConditionVariableEntry *queueEntry, MutexLocker *setLocker)
{
thread_prepare_to_block(thread, B_CAN_INTERRUPT,
THREAD_BLOCK_TYPE_OTHER, (void*)"xsi semaphore");
// Unlock the set before blocking
setLocker->Unlock();
// TODO: We've got a serious race condition: If BlockAndUnlock() returned due to
// interruption, we will still be queued. A WakeUpThread() at this point will
// call thread_unblock() and might thus screw with our trying to re-lock the
// mutex.
return thread_block();
return queueEntry->Wait(B_CAN_INTERRUPT);
}
void Deque(queued_thread *queueEntry, bool waitForZero)
void Dequeue(ConditionVariableEntry *queueEntry, bool waitForZero)
{
if (queueEntry->queued) {
if (waitForZero) {
fWaitingToBeZeroQueue.Remove(queueEntry);
fThreadsWaitingToBeZero--;
} else {
fWaitingToIncreaseQueue.Remove(queueEntry);
fThreadsWaitingToIncrease--;
}
}
queueEntry->Wait(B_RELATIVE_TIMEOUT, 0);
}
void Enqueue(queued_thread *queueEntry, bool waitForZero)
void Enqueue(ConditionVariableEntry *queueEntry, bool waitForZero)
{
if (waitForZero) {
fWaitingToBeZeroQueue.Add(queueEntry);
fThreadsWaitingToBeZero++;
fWaitingToBeZero.Add(queueEntry);
} else {
fWaitingToIncreaseQueue.Add(queueEntry);
fThreadsWaitingToIncrease++;
fWaitingToIncrease.Add(queueEntry);
}
queueEntry->queued = true;
}
pid_t LastPid() const
@@ -193,10 +154,10 @@ public:
void Revert(short value)
{
fValue -= value;
if (fValue == 0 && fThreadsWaitingToBeZero > 0)
WakeUpThread(true);
else if (fValue > 0 && fThreadsWaitingToIncrease > 0)
WakeUpThread(false);
if (fValue == 0)
WakeUpThreads(true);
else if (fValue > 0)
WakeUpThreads(false);
}
void SetPid(pid_t pid)
@@ -209,14 +170,14 @@ public:
fValue = value;
}
ushort ThreadsWaitingToIncrease() const
ushort ThreadsWaitingToIncrease()
{
return fThreadsWaitingToIncrease;
return fWaitingToIncrease.EntriesCount();
}
ushort ThreadsWaitingToBeZero() const
ushort ThreadsWaitingToBeZero()
{
return fThreadsWaitingToBeZero;
return fWaitingToBeZero.EntriesCount();
}
ushort Value() const
@@ -224,33 +185,21 @@ public:
return fValue;
}
void WakeUpThread(bool waitingForZero)
void WakeUpThreads(bool waitingForZero)
{
if (waitingForZero) {
// Wake up all threads waiting on zero
while (queued_thread *entry = fWaitingToBeZeroQueue.RemoveHead()) {
entry->queued = false;
fThreadsWaitingToBeZero--;
thread_unblock(entry->thread, 0);
}
fWaitingToBeZero.NotifyAll();
} else {
// Wake up all threads even though they might go back to sleep
while (queued_thread *entry = fWaitingToIncreaseQueue.RemoveHead()) {
entry->queued = false;
fThreadsWaitingToIncrease--;
thread_unblock(entry->thread, 0);
}
fWaitingToIncrease.NotifyAll();
}
}
private:
pid_t fLastPidOperation; // sempid
ushort fThreadsWaitingToIncrease; // semncnt
ushort fThreadsWaitingToBeZero; // semzcnt
ushort fValue; // semval
ThreadQueue fWaitingToIncreaseQueue;
ThreadQueue fWaitingToBeZeroQueue;
ConditionVariable fWaitingToIncrease;
ConditionVariable fWaitingToBeZero;
};
#define MAX_XSI_SEMS_PER_TEAM 128
@@ -1191,33 +1140,31 @@ _user_xsi_semop(int semaphoreID, struct sembuf *ops, size_t numOps)
if (operations[i].sem_op != 0)
waitOnZero = false;
Thread *thread = thread_get_current_thread();
queued_thread queueEntry(thread, (int32)operations[i].sem_op);
ConditionVariableEntry queueEntry;
semaphore->Enqueue(&queueEntry, waitOnZero);
uint32 sequenceNumber = semaphoreSet->SequenceNumber();
TRACE(("xsi_semop: thread %d going to sleep\n", (int)thread->id));
result = semaphore->BlockAndUnlock(thread, &setLocker);
result = semaphore->BlockAndUnlock(&queueEntry, &setLocker);
TRACE(("xsi_semop: thread %d back to life\n", (int)thread->id));
// We are back to life. Find out why!
// Make sure the set hasn't been deleted or worst yet
// replaced.
// Make sure the set hasn't been deleted or worst yet replaced.
setHashLocker.Lock();
semaphoreSet = sSemaphoreHashTable.Lookup(semaphoreID);
if (result == EIDRM || semaphoreSet == NULL || (semaphoreSet != NULL
&& sequenceNumber != semaphoreSet->SequenceNumber())) {
TRACE_ERROR(("xsi_semop: semaphore set id %d (sequence = "
&& sequenceNumber != semaphoreSet->SequenceNumber())) {
TRACE(("xsi_semop: semaphore set id %d (sequence = "
"%" B_PRIu32 ") got destroyed\n", semaphoreID,
sequenceNumber));
notDone = false;
result = EIDRM;
} else if (result == B_INTERRUPTED) {
TRACE_ERROR(("xsi_semop: thread %d got interrupted while "
"waiting on semaphore set id %d\n",(int)thread->id,
TRACE(("xsi_semop: thread %d got interrupted while "
"waiting on semaphore set id %d\n", (int)thread_get_current_thread_id(),
semaphoreID));
semaphore->Deque(&queueEntry, waitOnZero);
semaphore->Dequeue(&queueEntry, waitOnZero);
result = EINTR;
notDone = false;
} else {