Author: mmlr Date: 2011-06-21 02:56:20 +0200 (Tue, 21 Jun 2011) New Revision: 42272 Changeset: https://dev.haiku-os.org/changeset/42272 Modified: haiku/trunk/src/system/kernel/port.cpp Log: * Growing the port heap by adding new areas was broken in various ways. For one the acquired quota in sTotalSpaceInUse wasn't released in all cases leading to it eventually reaching the limit (after a _very_ long time though, so this is more theoretical than anything else). The sAllocatingArea flag wasn't reset in the case that an area was already added in the meantime, resulting in no further growing being possible. Then there were race conditions between waiting for space to become available and the situations which made that space available (freeing port_messages and adding new areas). * Fix these race conditions by using a mutex (sPortQuotaLock) to protect the various quota and allocation related variables. Instead removed the atomic_* operations that were previously used. * Had to move some static functions around. Should make port heap growing more robust, even though in normal use you'll likely never encounter it... Modified: haiku/trunk/src/system/kernel/port.cpp =================================================================== --- haiku/trunk/src/system/kernel/port.cpp 2011-06-20 22:24:36 UTC (rev 42271) +++ haiku/trunk/src/system/kernel/port.cpp 2011-06-21 00:56:20 UTC (rev 42272) @@ -49,6 +49,11 @@ // Port::owner. // * Port::lock: Protects all Port members save team_link, hash_link, and lock. // id is immutable. +// * sPortQuotaLock: Protects sTotalSpaceInUse, sAreaChangeCounter, +// sWaitingForSpace and the critical section of creating/adding areas for the +// port heap in the grow case. It also has to be held when reading +// sWaitingForSpace to determine whether or not to notify the +// sNoSpaceCondition condition variable. // // The locking order is sPortsLock -> Port::lock. A port must be looked up // in sPorts and locked with sPortsLock held. Afterwards sPortsLock can be @@ -338,12 +343,13 @@ static PortHashTable sPorts; static heap_allocator* sPortAllocator; static ConditionVariable sNoSpaceCondition; -static vint32 sTotalSpaceInUse; -static vint32 sAreaChangeCounter; -static vint32 sAllocatingArea; +static int32 sTotalSpaceInUse; +static int32 sAreaChangeCounter; +static int32 sWaitingForSpace; static port_id sNextPortID = 1; static bool sPortsActive = false; static mutex sPortsLock = MUTEX_INITIALIZER("ports list"); +static mutex sPortQuotaLock = MUTEX_INITIALIZER("port quota"); static PortNotificationService sNotificationService; @@ -492,59 +498,91 @@ } +static Port* +get_locked_port(port_id id) +{ + MutexLocker portsLocker(sPortsLock); + + Port* port = sPorts.Lookup(id); + if (port != NULL) + mutex_lock(&port->lock); + return port; +} + + +/*! You need to own the port's lock when calling this function */ +static inline bool +is_port_closed(Port* port) +{ + return port->capacity == 0; +} + + static void put_port_message(port_message* message) { size_t size = sizeof(port_message) + message->size; heap_free(sPortAllocator, message); - atomic_add(&sTotalSpaceInUse, -size); - sNoSpaceCondition.NotifyAll(); + MutexLocker quotaLocker(sPortQuotaLock); + sTotalSpaceInUse -= size; + if (sWaitingForSpace > 0) + sNoSpaceCondition.NotifyAll(); } static status_t get_port_message(int32 code, size_t bufferSize, uint32 flags, bigtime_t timeout, - port_message** _message) + port_message** _message, Port& port) { size_t size = sizeof(port_message) + bufferSize; - bool limitReached = false; + bool needToWait = false; + MutexLocker quotaLocker(sPortQuotaLock); + while (true) { - if (atomic_add(&sTotalSpaceInUse, size) - > int32(kTotalSpaceLimit - size)) { + while (sTotalSpaceInUse + size > kTotalSpaceLimit || needToWait) { // TODO: add per team limit // We are not allowed to create another heap area, as our // space limit has been reached - just wait until we get // some free space again. - limitReached = true; - wait: - MutexLocker locker(sPortsLock); - - atomic_add(&sTotalSpaceInUse, -size); - // TODO: we don't want to wait - but does that also mean we // shouldn't wait for the area creation? - if (limitReached && (flags & B_RELATIVE_TIMEOUT) != 0 - && timeout <= 0) + if ((flags & B_RELATIVE_TIMEOUT) != 0 && timeout <= 0) return B_WOULD_BLOCK; ConditionVariableEntry entry; sNoSpaceCondition.Add(&entry); - locker.Unlock(); + sWaitingForSpace++; + quotaLocker.Unlock(); + port_id portID = port.id; + mutex_unlock(&port.lock); + status_t status = entry.Wait(flags, timeout); + + // re-lock the port and the quota + Port* newPort = get_locked_port(portID); + quotaLocker.Lock(); + sWaitingForSpace--; + + if (newPort != &port || is_port_closed(&port)) { + // the port is no longer usable + return B_BAD_PORT_ID; + } + if (status == B_TIMED_OUT) return B_TIMED_OUT; - // just try again - limitReached = false; + needToWait = false; continue; } - int32 areaChangeCounter = atomic_get(&sAreaChangeCounter); + int32 areaChangeCounter = sAreaChangeCounter; + sTotalSpaceInUse += size; + quotaLocker.Unlock(); // Quota is fulfilled, try to allocate the buffer @@ -558,13 +596,16 @@ return B_OK; } - if (atomic_or(&sAllocatingArea, 1) != 0) { - // Just wait for someone else to create an area for us - goto wait; - } + quotaLocker.Lock(); - if (areaChangeCounter != atomic_get(&sAreaChangeCounter)) { - atomic_add(&sTotalSpaceInUse, -size); + // We weren't able to allocate and we'll start over, including + // re-acquireing the quota, so we remove our size from the in-use + // counter again. + sTotalSpaceInUse -= size; + + if (areaChangeCounter != sAreaChangeCounter) { + // There was already an area added since we tried allocating, + // start over. continue; } @@ -575,28 +616,22 @@ B_ANY_KERNEL_ADDRESS, kBufferGrowRate, B_NO_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA); if (area < 0) { - // it's time to let the userland feel our pain - sNoSpaceCondition.NotifyAll(); - return B_NO_MEMORY; + // We'll have to get by with what we have, so wait for someone + // to free a message instead. We enforce waiting so that we don't + // try to create a new area over and over. + needToWait = true; + continue; } heap_add_area(sPortAllocator, area, base, kBufferGrowRate); - atomic_add(&sAreaChangeCounter, 1); - sNoSpaceCondition.NotifyAll(); - atomic_and(&sAllocatingArea, 0); + sAreaChangeCounter++; + if (sWaitingForSpace > 0) + sNoSpaceCondition.NotifyAll(); } } -/*! You need to own the port's lock when calling this function */ -static inline bool -is_port_closed(Port* port) -{ - return port->capacity == 0; -} - - /*! Fills the port_info structure with information from the specified port. The port's lock must be held when called. @@ -653,18 +688,6 @@ } -static Port* -get_locked_port(port_id id) -{ - MutexLocker portsLocker(sPortsLock); - - Port* port = sPorts.Lookup(id); - if (port != NULL) - mutex_lock(&port->lock); - return port; -} - - // #pragma mark - private kernel API @@ -1394,9 +1417,16 @@ port->write_count--; status = get_port_message(msgCode, bufferSize, flags, timeout, - &message); - if (status != B_OK) + &message, *port); + if (status != B_OK) { + if (status == B_BAD_PORT_ID) { + // the port had to be unlocked and is now no longer there + T(Write(id, 0, 0, 0, 0, B_BAD_PORT_ID)); + return B_BAD_PORT_ID; + } + goto error; + } // sender credentials message->sender = geteuid();