Darwin-Streaming-Server/CommonUtilitiesLib/Task.cpp
Darren VanBuren 849723c9cf Add even more of the source
This should be about everything needed to build so far?
2017-03-07 17:14:16 -08:00

415 lines
15 KiB
C++

/*
*
* @APPLE_LICENSE_HEADER_START@
*
* Copyright (c) 1999-2008 Apple Inc. All Rights Reserved.
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*
*/
/*
File: Task.cpp
Contains: implements Task class
*/
#include "Task.h"
#include "OS.h"
#include "OSMemory.h"
#include "atomic.h"
#include "OSMutexRW.h"
unsigned int Task::sShortTaskThreadPicker = 0;
unsigned int Task::sBlockingTaskThreadPicker = 0;
OSMutexRW TaskThreadPool::sMutexRW;
static char* sTaskStateStr="live_"; //Alive
Task::Task()
: fEvents(0), fUseThisThread(NULL),fDefaultThread(NULL), fWriteLock(false), fTimerHeapElem(), fTaskQueueElem(), pickerToUse(&Task::sShortTaskThreadPicker)
{
#if DEBUG
fInRunCount = 0;
#endif
this->SetTaskName("unknown");
fTaskQueueElem.SetEnclosingObject(this);
fTimerHeapElem.SetEnclosingObject(this);
}
void Task::SetTaskName(char* name)
{
if (name == NULL)
return;
::strncpy(fTaskName,sTaskStateStr,sizeof(fTaskName));
::strncat(fTaskName,name,sizeof(fTaskName));
fTaskName[sizeof(fTaskName) -1] = 0; //terminate in case it is longer than ftaskname.
}
Bool16 Task::Valid()
{
if ( (this->fTaskName == NULL)
|| (0 != ::strncmp(sTaskStateStr,this->fTaskName, 5))
)
{
if (TASK_DEBUG) qtss_printf(" Task::Valid Found invalid task = %p\n", (void *)this);
return false;
}
return true;
}
Task::EventFlags Task::GetEvents()
{
//Mask off every event currently in the mask except for the alive bit, of course,
//which should remain unaffected and unreported by this call.
EventFlags events = fEvents & kAliveOff;
(void)atomic_sub(&fEvents, events);
return events;
}
void Task::Signal(EventFlags events)
{
if (!this->Valid())
return;
//Fancy no mutex implementation. We atomically mask the new events into
//the event mask. Because atomic_or returns the old state of the mask,
//we only schedule this task once.
events |= kAlive;
EventFlags oldEvents = atomic_or(&fEvents, events);
if ((!(oldEvents & kAlive)) && (TaskThreadPool::sNumTaskThreads > 0))
{
if (fDefaultThread != NULL && fUseThisThread == NULL)
fUseThisThread = fDefaultThread;
if (fUseThisThread != NULL)
// Task needs to be placed on a particular thread.
{
if (TASK_DEBUG)
{
if (fTaskName[0] == 0) ::strcpy(fTaskName, " corrupt task");
qtss_printf("Task::Signal enque TaskName=%s fUseThisThread=%p q elem=%p enclosing=%p\n", fTaskName, (void *) fUseThisThread, (void *) &fTaskQueueElem, (void *) this);
if (TaskThreadPool::sTaskThreadArray[0] == fUseThisThread) qtss_printf("Task::Signal RTSP Thread running TaskName=%s \n", fTaskName);
}
fUseThisThread->fTaskQueue.EnQueue(&fTaskQueueElem);
}
else
{
//find a thread to put this task on
unsigned int theThreadIndex = atomic_add( (unsigned int *) pickerToUse, 1);
if (&Task::sShortTaskThreadPicker == pickerToUse)
{
theThreadIndex %= TaskThreadPool::sNumShortTaskThreads;
if (TASK_DEBUG) qtss_printf("Task::Signal enque TaskName=%s using Task::sShortTaskThreadPicker=%u numShortTaskThreads=%"_U32BITARG_" short task range=[0-%"_U32BITARG_"] thread index =%u \n",fTaskName, Task::sShortTaskThreadPicker, TaskThreadPool::sNumShortTaskThreads,TaskThreadPool::sNumShortTaskThreads -1, theThreadIndex);
}
else if (&Task::sBlockingTaskThreadPicker == pickerToUse)
{
theThreadIndex %= TaskThreadPool::sNumBlockingTaskThreads;
theThreadIndex += TaskThreadPool::sNumShortTaskThreads; //don't pick from lower non-blocking (short task) threads.
if (TASK_DEBUG) qtss_printf("Task::Signal enque TaskName=%s using Task::sBlockingTaskThreadPicker=%u numBlockingThreads=%"_U32BITARG_" blocking thread range=[%"_U32BITARG_"-%"_U32BITARG_"] thread index =%u \n",fTaskName, Task::sBlockingTaskThreadPicker, TaskThreadPool::sNumBlockingTaskThreads, TaskThreadPool::sNumShortTaskThreads, TaskThreadPool::sNumBlockingTaskThreads+TaskThreadPool::sNumShortTaskThreads-1, theThreadIndex);
}
else
{
if (TASK_DEBUG) if (fTaskName[0] == 0) ::strcpy(fTaskName, " corrupt task");
return;
}
if (TASK_DEBUG) if (fTaskName[0] == 0) ::strcpy(fTaskName, " corrupt task");
if (TASK_DEBUG) qtss_printf("Task::Signal enque TaskName=%s theThreadIndex=%u thread=%p q elem=%p enclosing=%p\n", fTaskName,theThreadIndex, (void *)TaskThreadPool::sTaskThreadArray[theThreadIndex],(void *) &fTaskQueueElem,(void *) this);
TaskThreadPool::sTaskThreadArray[theThreadIndex]->fTaskQueue.EnQueue(&fTaskQueueElem);
}
}
else
if (TASK_DEBUG) qtss_printf("Task::Signal sent to dead TaskName=%s q elem=%p enclosing=%p\n", fTaskName, (void *) &fTaskQueueElem, (void *) this);
}
void Task::GlobalUnlock()
{
if (this->fWriteLock)
{ this->fWriteLock = false;
TaskThreadPool::sMutexRW.Unlock();
}
}
void Task::SetThreadPicker(unsigned int* picker)
{
pickerToUse = picker;
Assert(pickerToUse != NULL);
if (TASK_DEBUG)
{
if (fTaskName[0] == 0) ::strcpy(fTaskName, " corrupt task");
if (&Task::sShortTaskThreadPicker == pickerToUse)
{
qtss_printf("Task::SetThreadPicker sShortTaskThreadPicker for task=%s\n", fTaskName);
}
else if (&Task::sBlockingTaskThreadPicker == pickerToUse)
{
qtss_printf("Task::SetThreadPicker sBlockingTaskThreadPicker for task=%s\n",fTaskName);
}
else
{ qtss_printf("Task::SetThreadPicker ERROR unknown picker for task=%s\n",fTaskName);
}
}
}
void TaskThread::Entry()
{
Task* theTask = NULL;
while (true)
{
theTask = this->WaitForTask();
//
// WaitForTask returns NULL when it is time to quit
if (theTask == NULL || false == theTask->Valid() )
return;
Bool16 doneProcessingEvent = false;
while (!doneProcessingEvent)
{
//If a task holds locks when it returns from its Run function,
//that would be catastrophic and certainly lead to a deadlock
#if DEBUG
Assert(this->GetNumLocksHeld() == 0);
Assert(theTask->fInRunCount == 0);
theTask->fInRunCount++;
#endif
theTask->fUseThisThread = NULL; // Each invocation of Run must independently
// request a specific thread.
SInt64 theTimeout = 0;
if (theTask->fWriteLock)
{
OSMutexWriteLocker mutexLocker(&TaskThreadPool::sMutexRW);
if (TASK_DEBUG) qtss_printf("TaskThread::Entry run global locked TaskName=%s CurMSec=%.3f thread=%p task=%p\n", theTask->fTaskName, OS::StartTimeMilli_Float() ,(void *) this,(void *) theTask);
theTimeout = theTask->Run();
theTask->fWriteLock = false;
}
else
{
OSMutexReadLocker mutexLocker(&TaskThreadPool::sMutexRW);
if (TASK_DEBUG) qtss_printf("TaskThread::Entry run TaskName=%s CurMSec=%.3f thread=%p task=%p\n", theTask->fTaskName, OS::StartTimeMilli_Float(), (void *) this,(void *) theTask);
theTimeout = theTask->Run();
}
#if DEBUG
Assert(this->GetNumLocksHeld() == 0);
theTask->fInRunCount--;
Assert(theTask->fInRunCount == 0);
#endif
if (theTimeout < 0)
{
if (TASK_DEBUG)
{
qtss_printf("TaskThread::Entry delete TaskName=%s CurMSec=%.3f thread=%p task=%p\n", theTask->fTaskName, OS::StartTimeMilli_Float(), (void *) this, (void *) theTask);
theTask->fUseThisThread = NULL;
if (NULL != fHeap.Remove(&theTask->fTimerHeapElem))
qtss_printf("TaskThread::Entry task still in heap before delete\n");
if (NULL != theTask->fTaskQueueElem.InQueue())
qtss_printf("TaskThread::Entry task still in queue before delete\n");
theTask->fTaskQueueElem.Remove();
if (theTask->fEvents &~ Task::kAlive)
qtss_printf ("TaskThread::Entry flags still set before delete\n");
(void)atomic_sub(&theTask->fEvents, 0);
::strncat (theTask->fTaskName, " deleted", sizeof(theTask->fTaskName) -1);
}
theTask->fTaskName[0] = 'D'; //mark as dead
delete theTask;
theTask = NULL;
doneProcessingEvent = true;
}
else if (theTimeout == 0)
{
//We want to make sure that 100% definitely the task's Run function WILL
//be invoked when another thread calls Signal. We also want to make sure
//that if an event sneaks in right as the task is returning from Run()
//(via Signal) that the Run function will be invoked again.
doneProcessingEvent = compare_and_store(Task::kAlive, 0, &theTask->fEvents);
if (doneProcessingEvent)
theTask = NULL;
}
else
{
//note that if we get here, we don't reset theTask, so it will get passed into
//WaitForTask
if (TASK_DEBUG) qtss_printf("TaskThread::Entry insert TaskName=%s in timer heap thread=%p elem=%p task=%p timeout=%.2f\n", theTask->fTaskName, (void *) this, (void *) &theTask->fTimerHeapElem,(void *) theTask, (float)theTimeout / (float) 1000);
theTask->fTimerHeapElem.SetValue(OS::Milliseconds() + theTimeout);
fHeap.Insert(&theTask->fTimerHeapElem);
(void)atomic_or(&theTask->fEvents, Task::kIdleEvent);
doneProcessingEvent = true;
}
#if TASK_DEBUG
SInt64 yieldStart = OS::Milliseconds();
#endif
this->ThreadYield();
#if TASK_DEBUG
SInt64 yieldDur = OS::Milliseconds() - yieldStart;
static SInt64 numZeroYields;
if ( yieldDur > 1 )
{
if (TASK_DEBUG) qtss_printf( "TaskThread::Entry time in Yield %qd, numZeroYields %qd \n", yieldDur, numZeroYields );
numZeroYields = 0;
}
else
numZeroYields++;
#endif
}
}
}
Task* TaskThread::WaitForTask()
{
while (true)
{
SInt64 theCurrentTime = OS::Milliseconds();
if ((fHeap.PeekMin() != NULL) && (fHeap.PeekMin()->GetValue() <= theCurrentTime))
{
if (TASK_DEBUG) qtss_printf("TaskThread::WaitForTask found timer-task=%s thread %p fHeap.CurrentHeapSize(%"_U32BITARG_") taskElem = %p enclose=%p\n",((Task*)fHeap.PeekMin()->GetEnclosingObject())->fTaskName, (void *) this, fHeap.CurrentHeapSize(), (void *) fHeap.PeekMin(), (void *) fHeap.PeekMin()->GetEnclosingObject());
return (Task*)fHeap.ExtractMin()->GetEnclosingObject();
}
//if there is an element waiting for a timeout, figure out how long we should wait.
SInt64 theTimeout = 0;
if (fHeap.PeekMin() != NULL)
theTimeout = fHeap.PeekMin()->GetValue() - theCurrentTime;
Assert(theTimeout >= 0);
//
// Make sure we can't go to sleep for some ridiculously short
// period of time
// Do not allow a timeout below 10 ms without first verifying reliable udp 1-2mbit live streams.
// Test with streamingserver.xml pref reliablUDP printfs enabled and look for packet loss and check client for buffer ahead recovery.
if (theTimeout < 10)
theTimeout = 10;
//wait...
OSQueueElem* theElem = fTaskQueue.DeQueueBlocking(this, (SInt32) theTimeout);
if (theElem != NULL)
{
if (TASK_DEBUG) qtss_printf("TaskThread::WaitForTask found signal-task=%s thread %p fTaskQueue.GetLength(%"_U32BITARG_") taskElem = %p enclose=%p\n", ((Task*)theElem->GetEnclosingObject())->fTaskName, (void *) this, fTaskQueue.GetQueue()->GetLength(), (void *) theElem, (void *)theElem->GetEnclosingObject() );
return (Task*)theElem->GetEnclosingObject();
}
//
// If we are supposed to stop, return NULL, which signals the caller to stop
if (OSThread::GetCurrent()->IsStopRequested())
return NULL;
}
}
TaskThread** TaskThreadPool::sTaskThreadArray = NULL;
UInt32 TaskThreadPool::sNumTaskThreads = 0;
UInt32 TaskThreadPool::sNumShortTaskThreads = 0;
UInt32 TaskThreadPool::sNumBlockingTaskThreads = 0;
Bool16 TaskThreadPool::AddThreads(UInt32 numToAdd)
{
Assert(sTaskThreadArray == NULL);
sTaskThreadArray = new TaskThread*[numToAdd];
for (UInt32 x = 0; x < numToAdd; x++)
{
sTaskThreadArray[x] = NEW TaskThread();
sTaskThreadArray[x]->Start();
if (TASK_DEBUG) qtss_printf("TaskThreadPool::AddThreads sTaskThreadArray[%"_U32BITARG_"]=%p\n",x, sTaskThreadArray[x]);
}
sNumTaskThreads = numToAdd;
if (0 == sNumShortTaskThreads)
sNumShortTaskThreads = numToAdd;
return true;
}
TaskThread* TaskThreadPool::GetThread(UInt32 index)
{
Assert(sTaskThreadArray != NULL);
if (index >= sNumTaskThreads)
return NULL;
return sTaskThreadArray[index];
}
void TaskThreadPool::RemoveThreads()
{
//Tell all the threads to stop
for (UInt32 x = 0; x < sNumTaskThreads; x++)
sTaskThreadArray[x]->SendStopRequest();
//Because any (or all) threads may be blocked on the queue, cycle through
//all the threads, signalling each one
for (UInt32 y = 0; y < sNumTaskThreads; y++)
sTaskThreadArray[y]->fTaskQueue.GetCond()->Signal();
//Ok, now wait for the selected threads to terminate, deleting them and removing
//them from the queue.
for (UInt32 z = 0; z < sNumTaskThreads; z++)
delete sTaskThreadArray[z];
sNumTaskThreads = 0;
}