[asterisk-scf-commits] asterisk-scf/integration/ice-util-cpp.git branch "workqueue" updated.
Commits to the Asterisk SCF project code repositories
asterisk-scf-commits at lists.digium.com
Mon Apr 25 16:14:23 CDT 2011
branch "workqueue" has been updated
via 2128806347779274465c5efcbc944d6befc02e8c (commit)
via 7df3ef4306ceb0f310257d8f7a5c576282441d0b (commit)
via eb8d3361f2f9d0f576460ff023f46154c84cdeca (commit)
via 6c933f0ee983a7d3c1637a84f0a4158a172b2bef (commit)
via b9a46f90817c1ce2966e6757b6482abb9aad37fe (commit)
via 352eb6b0b2b957e76fe28da311e10f8a67fa1db2 (commit)
via a70022e3d9b6778001fd3e9ae3b58182e6a269ab (commit)
via 6aba2bf84394577a59038d24016ea782596be054 (commit)
via 65ec9d71bfb5f42fae1f3cb7b3666431d635762f (commit)
via 59b0c3247b2faf3ed662d8d5792f077306593e1a (commit)
via c16907ace888a1ed1020e6f49e764c330ad40dd5 (commit)
via 4c66ace69ba6180b85f2869955a1efc1c078d8fb (commit)
from de35ff4fc373c9ff094d829edb5f49560a041a13 (commit)
Summary of changes:
ThreadPool/include/AsteriskSCF/ThreadPool.h | 15 ++-
ThreadPool/include/AsteriskSCF/WorkerThread.h | 12 +--
ThreadPool/src/CMakeLists.txt | 1 +
ThreadPool/src/ThreadPool.cpp | 130 +++++++++++++++++++------
ThreadPool/src/WorkerThread.cpp | 39 ++++----
ThreadPool/test/TestThreadPool.cpp | 99 ++++++++++++++++---
WorkQueue/src/CMakeLists.txt | 1 +
WorkQueue/src/DefaultQueueListener.cpp | 52 +++++-----
WorkQueue/src/SuspendableWorkQueue.cpp | 4 +-
WorkQueue/src/WorkQueue.cpp | 2 +-
WorkQueue/test/TestSuspendableWorkQueue.cpp | 96 ++++++++++--------
WorkQueue/test/TestWorkQueue.cpp | 85 ++++++++--------
12 files changed, 345 insertions(+), 191 deletions(-)
- Log -----------------------------------------------------------------
commit 2128806347779274465c5efcbc944d6befc02e8c
Author: Mark Michelson <mmichelson at digium.com>
Date: Mon Apr 25 16:12:32 2011 -0500
Remove ugliness from the SuspendableWorkQueueTest.
Specifically, get rid of global locks and condition variables since
they really are unnecessary. I also removed all calls to lock.unlock()
in favor of using scoped locks.
diff --git a/WorkQueue/test/TestSuspendableWorkQueue.cpp b/WorkQueue/test/TestSuspendableWorkQueue.cpp
index 36cc69f..47ab140 100644
--- a/WorkQueue/test/TestSuspendableWorkQueue.cpp
+++ b/WorkQueue/test/TestSuspendableWorkQueue.cpp
@@ -22,23 +22,6 @@
using namespace AsteriskSCF::System::WorkQueue::V1;
using namespace AsteriskSCF::WorkQueue;
-// A ComplexTask creates a thread to simulate
-// An asynchronous task. When asyncTaskCond is
-// notified, this simulates the completion of
-// the asynchronous task
-static boost::condition_variable asyncTaskCond;
-static bool asyncTaskComplete;
-
-// workResumableCond is used by the ComplexTask to
-// notify the testing thread that it has
-// finished notifying the SuspendableQueue
-// that work may be resumed.
-static boost::condition_variable workResumableCond;
-
-// Lock used in conjunction with the condition
-// variables.
-static boost::mutex globalLock;
-
class TestListener : public QueueListener
{
public:
@@ -61,15 +44,17 @@ public:
void workResumable()
{
- boost::unique_lock<boost::mutex> lock(globalLock);
+ boost::unique_lock<boost::mutex> lock(mLock);
resumableNotice = true;
- workResumableCond.notify_one();
+ mCond.notify_one();
}
bool addedNotice;
bool addedEmptyNotice;
bool emptyNotice;
bool resumableNotice;
+ boost::mutex mLock;
+ boost::condition_variable mCond;
};
typedef IceUtil::Handle<TestListener> TestListenerPtr;
@@ -91,14 +76,16 @@ typedef IceUtil::Handle<SimpleTask> SimpleTaskPtr;
class ComplexTask : public SuspendableWork
{
public:
- ComplexTask() : currentState(Initial), mThread(boost::bind(&ComplexTask::thread, this)) { }
+ ComplexTask() : mAsyncTaskComplete(false), currentState(Initial),
+ mThread(boost::bind(&ComplexTask::thread, this)) { }
~ComplexTask()
{
- boost::unique_lock<boost::mutex> lock(globalLock);
- asyncTaskComplete = true;
- asyncTaskCond.notify_one();
- lock.unlock();
+ {
+ boost::unique_lock<boost::mutex> lock(mLock);
+ mAsyncTaskComplete = true;
+ mCond.notify_one();
+ }
mThread.join();
}
@@ -122,14 +109,18 @@ public:
void thread()
{
- boost::unique_lock<boost::mutex> lock(globalLock);
- while (!asyncTaskComplete)
{
- asyncTaskCond.wait(lock);
+ boost::unique_lock<boost::mutex> lock(mLock);
+ while (!mAsyncTaskComplete)
+ {
+ mCond.wait(lock);
+ }
}
- lock.unlock();
mListener->workResumable();
}
+
+ bool mAsyncTaskComplete;
+
enum State
{
Initial,
@@ -137,6 +128,8 @@ public:
Task2Complete
} currentState;
+ boost::mutex mLock;
+ boost::condition_variable mCond;
boost::thread mThread;
SuspendableWorkListenerPtr mListener;
};
@@ -438,18 +431,20 @@ BOOST_AUTO_TEST_CASE(complexWork)
// We simulate the ComplexTask's asynchronous task here in
// the testing thread.
- boost::unique_lock<boost::mutex> lock(globalLock);
- asyncTaskComplete = true;
- asyncTaskCond.notify_one();
- lock.unlock();
+ {
+ boost::unique_lock<boost::mutex> lock(work->mLock);
+ work->mAsyncTaskComplete = true;
+ work->mCond.notify_one();
+ }
// Now we wait to be told work may be resumed.
- lock.lock();
- while (!listener->resumableNotice)
{
- workResumableCond.wait(lock);
+ boost::unique_lock<boost::mutex> lock(listener->mLock);
+ while (!listener->resumableNotice)
+ {
+ listener->mCond.wait(lock);
+ }
}
- lock.unlock();
// Yeah it's redundant, so sue me.
BOOST_CHECK(listener->resumableNotice == true);
commit 7df3ef4306ceb0f310257d8f7a5c576282441d0b
Author: Mark Michelson <mmichelson at digium.com>
Date: Mon Apr 25 15:54:43 2011 -0500
Change code to accommodate new exception guidelines for work cancellation.
Tests have been updated to properly expect and test exception times.
diff --git a/WorkQueue/src/SuspendableWorkQueue.cpp b/WorkQueue/src/SuspendableWorkQueue.cpp
index d7bc7cf..4dfd788 100644
--- a/WorkQueue/src/SuspendableWorkQueue.cpp
+++ b/WorkQueue/src/SuspendableWorkQueue.cpp
@@ -274,13 +274,13 @@ void SuspendableWorkQueue::cancelWork(const SuspendableWorkPtr& work)
if (work == mPriv->currentWork)
{
- throw WorkNotFound(__FILE__, __LINE__);
+ throw WorkInProgress(__FILE__, __LINE__);
}
std::deque<SuspendableWorkPtr>::iterator i = std::find(mPriv->mQueue.begin(), mPriv->mQueue.end(), work);
if (i == mPriv->mQueue.end())
{
- throw WorkNotFound(__FILE__, __LINE__);
+ return;
}
mPriv->mQueue.erase(i);
diff --git a/WorkQueue/src/WorkQueue.cpp b/WorkQueue/src/WorkQueue.cpp
index e1a8d69..6b2a6c1 100644
--- a/WorkQueue/src/WorkQueue.cpp
+++ b/WorkQueue/src/WorkQueue.cpp
@@ -111,7 +111,7 @@ void WorkQueue::cancelWork(const WorkPtr& work)
std::deque<WorkPtr>::iterator i = std::find(mPriv->mQueue.begin(), mPriv->mQueue.end(), work);
if (i == mPriv->mQueue.end())
{
- throw WorkNotFound(__FILE__, __LINE__);
+ return;
}
mPriv->mQueue.erase(i);
diff --git a/WorkQueue/test/TestSuspendableWorkQueue.cpp b/WorkQueue/test/TestSuspendableWorkQueue.cpp
index 8c230c2..36cc69f 100644
--- a/WorkQueue/test/TestSuspendableWorkQueue.cpp
+++ b/WorkQueue/test/TestSuspendableWorkQueue.cpp
@@ -48,7 +48,7 @@ public:
emptyNotice(false),
resumableNotice(false) { }
- void workAdded(int numNewWork, bool wasEmpty)
+ void workAdded(int, bool wasEmpty)
{
addedNotice = true;
addedEmptyNotice = wasEmpty;
@@ -78,7 +78,7 @@ class SimpleTask : public SuspendableWork
{
public:
SimpleTask() : taskExecuted(false) { }
- SuspendableWorkResult execute(const SuspendableWorkListenerPtr& listener)
+ SuspendableWorkResult execute(const SuspendableWorkListenerPtr&)
{
taskExecuted = true;
return Complete;
@@ -241,7 +241,7 @@ BOOST_AUTO_TEST_CASE(cancelWork)
{
queue->cancelWork(work1);
}
- catch (const WorkNotFound&)
+ catch (const WorkInProgress&)
{
excepted = true;
}
@@ -262,12 +262,12 @@ BOOST_AUTO_TEST_CASE(cancelNonExistent1)
{
queue->cancelWork(work);
}
- catch (const WorkNotFound&)
+ catch (const WorkInProgress&)
{
excepted = true;
}
- BOOST_CHECK(excepted == true);
+ BOOST_CHECK(excepted == false);
BOOST_CHECK(listener->addedNotice == false);
BOOST_CHECK(listener->addedEmptyNotice == false);
BOOST_CHECK(listener->emptyNotice == false);
@@ -288,12 +288,12 @@ BOOST_AUTO_TEST_CASE(cancelNonExistent2)
{
queue->cancelWork(work2);
}
- catch (const WorkNotFound&)
+ catch (const WorkInProgress&)
{
excepted = true;
}
- BOOST_CHECK(excepted == true);
+ BOOST_CHECK(excepted == false);
BOOST_CHECK(queue->getSize() == 1);
}
@@ -421,6 +421,21 @@ BOOST_AUTO_TEST_CASE(complexWork)
BOOST_CHECK(queue->getSize() == 1);
BOOST_CHECK(work->currentState == ComplexTask::Task1Complete);
+ //While the task is suspended, let's try to cancel it and make sure
+ //that the appropriate exception is thrown
+ bool excepted = false;
+ try
+ {
+ queue->cancelWork(work);
+ }
+ catch (const WorkInProgress&)
+ {
+ excepted = true;
+ }
+
+ BOOST_CHECK(excepted == true);
+ BOOST_CHECK(queue->getSize() == 1);
+
// We simulate the ComplexTask's asynchronous task here in
// the testing thread.
boost::unique_lock<boost::mutex> lock(globalLock);
diff --git a/WorkQueue/test/TestWorkQueue.cpp b/WorkQueue/test/TestWorkQueue.cpp
index a162da8..0ff5ed7 100644
--- a/WorkQueue/test/TestWorkQueue.cpp
+++ b/WorkQueue/test/TestWorkQueue.cpp
@@ -153,17 +153,8 @@ BOOST_AUTO_TEST_CASE(cancelWork)
queue->enqueueWork(work1);
- bool excepted = false;
- try
- {
- queue->cancelWork(work1);
- }
- catch (const WorkNotFound& )
- {
- excepted = true;
- }
+ queue->cancelWork(work1);
- BOOST_CHECK(excepted == false);
BOOST_CHECK(listener->emptyNotice == true);
BOOST_CHECK(queue->getSize() == 0);
}
@@ -174,17 +165,8 @@ BOOST_AUTO_TEST_CASE(cancelNonExistent1)
QueuePtr queue(new WorkQueue(listener));
TaskPtr work(new Task);
- bool excepted = false;
- try
- {
- queue->cancelWork(work);
- }
- catch (const WorkNotFound& )
- {
- excepted = true;
- }
+ queue->cancelWork(work);
- BOOST_CHECK(excepted == true);
BOOST_CHECK(listener->addedNotice == false);
BOOST_CHECK(listener->addedEmptyNotice == false);
BOOST_CHECK(listener->emptyNotice == false);
@@ -200,17 +182,8 @@ BOOST_AUTO_TEST_CASE(cancelNonExistent2)
queue->enqueueWork(work1);
- bool excepted = false;
- try
- {
- queue->cancelWork(work2);
- }
- catch (const WorkNotFound&)
- {
- excepted = true;
- }
+ queue->cancelWork(work2);
- BOOST_CHECK(excepted == true);
BOOST_CHECK(queue->getSize() == 1);
}
commit eb8d3361f2f9d0f576460ff023f46154c84cdeca
Author: Mark Michelson <mmichelson at digium.com>
Date: Mon Apr 25 15:25:55 2011 -0500
Adjust DefaultQueueListener test to not use busy loops.
diff --git a/WorkQueue/test/TestWorkQueue.cpp b/WorkQueue/test/TestWorkQueue.cpp
index 3244eb0..a162da8 100644
--- a/WorkQueue/test/TestWorkQueue.cpp
+++ b/WorkQueue/test/TestWorkQueue.cpp
@@ -15,6 +15,7 @@
*/
#include <boost/test/unit_test.hpp>
+#include <boost/thread.hpp>
#include <AsteriskSCF/WorkQueue.h>
#include <AsteriskSCF/DefaultQueueListener.h>
@@ -68,6 +69,28 @@ public:
typedef IceUtil::Handle<Task> TaskPtr;
+/**
+ * Like a Task, but it has the capability
+ * of notifying a waiting thread when the
+ * work has completed.
+ */
+class NotifyTask : public Work
+{
+public:
+ NotifyTask() : taskExecuted(false) { }
+ void execute()
+ {
+ boost::lock_guard<boost::mutex> lock(mLock);
+ taskExecuted = true;
+ mCond.notify_one();
+ }
+ bool taskExecuted;
+ boost::condition_variable mCond;
+ boost::mutex mLock;
+};
+
+typedef IceUtil::Handle<NotifyTask> NotifyTaskPtr;
+
BOOST_AUTO_TEST_SUITE(WorkQueueTest)
BOOST_AUTO_TEST_CASE(addWork)
@@ -290,14 +313,23 @@ BOOST_AUTO_TEST_CASE(executionOrder2)
BOOST_CHECK(queue->getSize() == 0);
}
+void waitForTaskCompletion(NotifyTaskPtr& task)
+{
+ boost::unique_lock<boost::mutex> lock(task->mLock);
+ while (!task->taskExecuted)
+ {
+ task->mCond.wait(lock);
+ }
+}
+
BOOST_AUTO_TEST_CASE(defaultListener)
{
QueuePtr queue(new WorkQueue());
DefaultQueueListenerPtr listener(new DefaultQueueListener(queue));
- TaskPtr work1(new Task);
- TaskPtr work2(new Task);
- TaskPtr work3(new Task);
- TaskPtr work4(new Task);
+ NotifyTaskPtr work1(new NotifyTask);
+ NotifyTaskPtr work2(new NotifyTask);
+ NotifyTaskPtr work3(new NotifyTask);
+ NotifyTaskPtr work4(new NotifyTask);
WorkSeq works;
queue->setListener(listener);
@@ -309,10 +341,7 @@ BOOST_AUTO_TEST_CASE(defaultListener)
queue->enqueueWorkSeq(works);
- while (work4->taskExecuted == false)
- {
- BOOST_TEST_MESSAGE("Waiting for work to complete");
- }
+ waitForTaskCompletion(work4);
BOOST_CHECK(work1->taskExecuted == true);
BOOST_CHECK(work2->taskExecuted == true);
@@ -322,13 +351,10 @@ BOOST_AUTO_TEST_CASE(defaultListener)
//Thread should be idle. It should get woken up if
//we give it more work to do.
- TaskPtr work5(new Task);
+ NotifyTaskPtr work5(new NotifyTask);
queue->enqueueWork(work5);
- while (work5->taskExecuted == false)
- {
- BOOST_TEST_MESSAGE("Waiting for work to complete");
- }
+ waitForTaskCompletion(work5);
BOOST_CHECK(work5->taskExecuted == true);
}
commit 6c933f0ee983a7d3c1637a84f0a4158a172b2bef
Author: Mark Michelson <mmichelson at digium.com>
Date: Mon Apr 25 15:08:18 2011 -0500
Add new test case that exercises thread reactivation.
diff --git a/ThreadPool/test/TestThreadPool.cpp b/ThreadPool/test/TestThreadPool.cpp
index 5b34ff9..7d12f54 100644
--- a/ThreadPool/test/TestThreadPool.cpp
+++ b/ThreadPool/test/TestThreadPool.cpp
@@ -336,6 +336,48 @@ BOOST_AUTO_TEST_CASE(oneThreadMultipleTasks)
BOOST_CHECK(listener->mZombie == 0);
}
+BOOST_AUTO_TEST_CASE(reactivation)
+{
+ BOOST_TEST_MESSAGE("Running reactivation test");
+
+ TestListenerPtr listener(new TestListener);
+ QueuePtr queue(new WorkQueue());
+ ThreadPoolFactoryPtr factory(new ThreadPoolFactory);
+ PoolPtr pool = factory->createPool(listener, queue);
+ SimpleTaskPtr work1(new SimpleTask());
+
+ queue->enqueueWork(work1);
+
+ pool->setSize(1);
+
+ waitForCompletion(work1);
+
+ BOOST_CHECK(work1->taskExecuted == true);
+
+ WAIT_WHILE(listener->mIdle < 1);
+
+ BOOST_CHECK(listener->mIdle == 1);
+ BOOST_CHECK(listener->mActive == 0);
+ BOOST_CHECK(listener->mZombie == 0);
+
+ //This is the point of the test. Make sure that
+ //the idle thread is reactivated when new work
+ //is queued.
+ SimpleTaskPtr work2(new SimpleTask());
+
+ queue->enqueueWork(work2);
+
+ waitForCompletion(work2);
+
+ BOOST_CHECK(work2->taskExecuted == true);
+
+ WAIT_WHILE(listener->mIdle < 1);
+
+ BOOST_CHECK(listener->mIdle == 1);
+ BOOST_CHECK(listener->mActive == 0);
+ BOOST_CHECK(listener->mZombie == 0);
+}
+
BOOST_AUTO_TEST_CASE(taskDistribution)
{
BOOST_TEST_MESSAGE("Running taskDistribution test");
commit b9a46f90817c1ce2966e6757b6482abb9aad37fe
Author: Mark Michelson <mmichelson at digium.com>
Date: Mon Apr 25 15:02:37 2011 -0500
Fix bug where awakened idle threads were not moved into the active container.
diff --git a/ThreadPool/src/ThreadPool.cpp b/ThreadPool/src/ThreadPool.cpp
index f583ac5..775154c 100644
--- a/ThreadPool/src/ThreadPool.cpp
+++ b/ThreadPool/src/ThreadPool.cpp
@@ -309,8 +309,10 @@ public:
for (ThreadIterator i = mPool->mIdleThreads.begin();
i != mPool->mIdleThreads.end(); ++i)
{
+ mPool->mActiveThreads.push_back(*i);
(*i)->setState(Alive);
}
+ mPool->mIdleThreads.erase(mPool->mIdleThreads.begin(), mPool->mIdleThreads.end());
}
private:
const int mNewWork;
commit 352eb6b0b2b957e76fe28da311e10f8a67fa1db2
Author: Mark Michelson <mmichelson at digium.com>
Date: Mon Apr 25 14:57:11 2011 -0500
Add asterisks-scf-api as a link target for WorkQueue and ThreadPool.
diff --git a/ThreadPool/src/CMakeLists.txt b/ThreadPool/src/CMakeLists.txt
index ed3a64c..7e82838 100644
--- a/ThreadPool/src/CMakeLists.txt
+++ b/ThreadPool/src/CMakeLists.txt
@@ -24,5 +24,6 @@ asterisk_scf_component_add_boost_libraries(ThreadPool thread)
asterisk_scf_component_build_library(ThreadPool)
target_link_libraries(ThreadPool WorkQueue)
+target_link_libraries(ThreadPool asterisk-scf-api)
asterisk_scf_headers_install(../include/)
diff --git a/WorkQueue/src/CMakeLists.txt b/WorkQueue/src/CMakeLists.txt
index c8e34e4..1dfd843 100644
--- a/WorkQueue/src/CMakeLists.txt
+++ b/WorkQueue/src/CMakeLists.txt
@@ -25,5 +25,6 @@ asterisk_scf_component_add_file(WorkQueue DefaultQueueListener.cpp)
asterisk_scf_component_add_boost_libraries(WorkQueue thread)
asterisk_scf_component_build_library(WorkQueue)
+target_link_libraries(WorkQueue asterisk-scf-api)
asterisk_scf_headers_install(../include/)
commit a70022e3d9b6778001fd3e9ae3b58182e6a269ab
Author: Mark Michelson <mmichelson at digium.com>
Date: Mon Apr 25 14:52:57 2011 -0500
s/WAIT_FOR_THREADS/WAIT_WHILE/g
diff --git a/ThreadPool/test/TestThreadPool.cpp b/ThreadPool/test/TestThreadPool.cpp
index 1af3b96..5b34ff9 100644
--- a/ThreadPool/test/TestThreadPool.cpp
+++ b/ThreadPool/test/TestThreadPool.cpp
@@ -119,7 +119,7 @@ public:
typedef IceUtil::Handle<ComplexTask> ComplexTaskPtr;
-#define WAIT_FOR_THREADS(condition) \
+#define WAIT_WHILE(condition) \
{\
boost::unique_lock<boost::mutex> lock(listener->mLock);\
while ((condition))\
@@ -201,7 +201,7 @@ BOOST_AUTO_TEST_CASE(threadCreation)
//The thread will initially be active but will
//turn idle nearly immediately since there is no
//work to do.
- WAIT_FOR_THREADS(listener->mIdle == 0);
+ WAIT_WHILE(listener->mIdle == 0);
BOOST_CHECK(listener->mIdle == 1);
BOOST_CHECK(listener->mActive == 0);
@@ -219,7 +219,7 @@ BOOST_AUTO_TEST_CASE(threadDestruction)
pool->setSize(3);
- WAIT_FOR_THREADS(listener->mIdle < 3);
+ WAIT_WHILE(listener->mIdle < 3);
BOOST_CHECK(listener->mIdle == 3);
BOOST_CHECK(listener->mActive == 0);
@@ -227,7 +227,7 @@ BOOST_AUTO_TEST_CASE(threadDestruction)
pool->setSize(2);
- WAIT_FOR_THREADS(listener->mIdle > 2);
+ WAIT_WHILE(listener->mIdle > 2);
BOOST_CHECK(listener->mIdle == 2);
BOOST_CHECK(listener->mActive == 0);
@@ -258,7 +258,7 @@ BOOST_AUTO_TEST_CASE(oneTaskOneThread)
//The thread should be idle now. Let's make sure
//that's happening.
- WAIT_FOR_THREADS(listener->mIdle == 0);
+ WAIT_WHILE(listener->mIdle == 0);
BOOST_CHECK(listener->mIdle == 1);
BOOST_CHECK(listener->mActive == 0);
@@ -277,7 +277,7 @@ BOOST_AUTO_TEST_CASE(oneThreadOneTask)
pool->setSize(1);
- WAIT_FOR_THREADS(listener->mIdle < 1);
+ WAIT_WHILE(listener->mIdle < 1);
//The thread is idle now. When we queue work, it should
//become active and execute the work.
@@ -291,7 +291,7 @@ BOOST_AUTO_TEST_CASE(oneThreadOneTask)
BOOST_CHECK(listener->mEmptyNotice == true);
//And of course, the thread should become idle once work is done
- WAIT_FOR_THREADS(listener->mIdle < 1);
+ WAIT_WHILE(listener->mIdle < 1);
BOOST_CHECK(listener->mIdle == 1);
BOOST_CHECK(listener->mActive == 0);
@@ -329,7 +329,7 @@ BOOST_AUTO_TEST_CASE(oneThreadMultipleTasks)
BOOST_CHECK(work3->taskExecuted == true);
//And of course, the thread should become idle once work is done
- WAIT_FOR_THREADS(listener->mIdle < 1);
+ WAIT_WHILE(listener->mIdle < 1);
BOOST_CHECK(listener->mIdle == 1);
BOOST_CHECK(listener->mActive == 0);
@@ -362,7 +362,7 @@ BOOST_AUTO_TEST_CASE(taskDistribution)
//Pool operations are asynchronous, so we need to
//wait until the listener is informed that there are
//two active threads
- WAIT_FOR_THREADS(listener->mActive < 2);
+ WAIT_WHILE(listener->mActive < 2);
BOOST_CHECK(listener->mActive == 2);
BOOST_CHECK(listener->mIdle == 0);
@@ -380,7 +380,7 @@ BOOST_AUTO_TEST_CASE(taskDistribution)
BOOST_CHECK(work2->taskExecuted == true);
//And of course, the threads should become idle once work is done
- WAIT_FOR_THREADS(listener->mIdle < 2);
+ WAIT_WHILE(listener->mIdle < 2);
BOOST_CHECK(listener->mIdle == 2);
BOOST_CHECK(listener->mActive == 0);
@@ -406,7 +406,7 @@ BOOST_AUTO_TEST_CASE(zombies)
pool->setSize(2);
- WAIT_FOR_THREADS(listener->mActive < 2);
+ WAIT_WHILE(listener->mActive < 2);
BOOST_CHECK(listener->mActive == 2);
BOOST_CHECK(listener->mIdle == 0);
@@ -417,7 +417,7 @@ BOOST_AUTO_TEST_CASE(zombies)
//zombies
pool->setSize(0);
- WAIT_FOR_THREADS(listener->mZombie < 2);
+ WAIT_WHILE(listener->mZombie < 2);
BOOST_CHECK(listener->mActive == 0);
BOOST_CHECK(listener->mIdle == 0);
@@ -437,7 +437,7 @@ BOOST_AUTO_TEST_CASE(zombies)
//Since the tasks finished executing, the zombie
//threads should die.
- WAIT_FOR_THREADS(listener->mZombie > 0);
+ WAIT_WHILE(listener->mZombie > 0);
BOOST_CHECK(listener->mActive == 0);
BOOST_CHECK(listener->mIdle == 0);
@@ -466,7 +466,7 @@ BOOST_AUTO_TEST_CASE(moreThreadDestruction)
// All threads start as active, but 2 should become
// idle nearly immediately. We don't want to proceed
// until we know the two threads have become idle.
- WAIT_FOR_THREADS(listener->mIdle < 2);
+ WAIT_WHILE(listener->mIdle < 2);
BOOST_CHECK(listener->mActive == 2);
BOOST_CHECK(listener->mIdle == 2);
@@ -477,7 +477,7 @@ BOOST_AUTO_TEST_CASE(moreThreadDestruction)
//Previous state was 2 active and 2 idle threads.
//Removing 3 threads should kill the 2 idle threads
//and change one of the active threads to a zombie.
- WAIT_FOR_THREADS(listener->mIdle > 0 || listener->mZombie == 0);
+ WAIT_WHILE(listener->mIdle > 0 || listener->mZombie == 0);
BOOST_CHECK(listener->mActive == 1);
BOOST_CHECK(listener->mIdle == 0);
@@ -492,7 +492,7 @@ BOOST_AUTO_TEST_CASE(moreThreadDestruction)
BOOST_CHECK(work1->taskExecuted == true);
BOOST_CHECK(work2->taskExecuted == true);
- WAIT_FOR_THREADS(listener->mZombie > 0 || listener->mActive > 0);
+ WAIT_WHILE(listener->mZombie > 0 || listener->mActive > 0);
BOOST_CHECK(listener->mActive == 0);
BOOST_CHECK(listener->mIdle == 1);
commit 6aba2bf84394577a59038d24016ea782596be054
Author: Mark Michelson <mmichelson at digium.com>
Date: Mon Apr 25 14:36:59 2011 -0500
Resolve potential optimiser infinite loops through trickery and deceit.
Don't ask why I used the commonwealth spellings in my comments. There
is no satisfactory reason.
diff --git a/ThreadPool/src/WorkerThread.cpp b/ThreadPool/src/WorkerThread.cpp
index ea5b8fa..59cef80 100644
--- a/ThreadPool/src/WorkerThread.cpp
+++ b/ThreadPool/src/WorkerThread.cpp
@@ -37,11 +37,19 @@ public:
void active()
{
- while (mState == Alive)
+ /**
+ * For all intents and purposes, localAlive
+ * should evaluate to (mState == Alive). We use
+ * a local variable here to get around potential
+ * optimiser behaviour that could result in an
+ * infinite loop.
+ */
+ bool localAlive = true;
+ while (localAlive)
{
if (!mQueue->executeWork())
{
- idle();
+ localAlive = idle();
}
}
@@ -58,13 +66,13 @@ public:
}
}
- void idle()
+ bool idle()
{
{
boost::unique_lock<boost::mutex> lock(mLock);
if (mState != Alive)
{
- return;
+ return false;
}
mListener->activeThreadIdle(mWorkerThread);
while (!mWakeUp)
@@ -72,6 +80,7 @@ public:
mCond.wait(lock);
}
mWakeUp = false;
+ return mState == Alive;
}
}
diff --git a/WorkQueue/src/DefaultQueueListener.cpp b/WorkQueue/src/DefaultQueueListener.cpp
index 5bb45bb..267571c 100644
--- a/WorkQueue/src/DefaultQueueListener.cpp
+++ b/WorkQueue/src/DefaultQueueListener.cpp
@@ -31,28 +31,32 @@ public:
: mWakeUp(false), mDead(false), mQueue(queue.get()),
mThread(boost::bind(&DefaultQueueListenerPriv::run, this)) { }
- void idle()
+ bool idle()
{
boost::unique_lock<boost::mutex> lock(mLock);
- if (mDead)
- {
- return;
- }
-
while (!mWakeUp)
{
mCond.wait(lock);
}
mWakeUp = false;
+ return mDead;
}
void run()
{
- while (!mDead)
+ /**
+ * For all intents and purposes localDead
+ * mirrors the value of the mDead member. The
+ * reason it is used is to work around potential
+ * optimiser behaviour that could result in
+ * an infinite loop.
+ */
+ bool localDead;
+ while (!localDead)
{
if (!mQueue->executeWork())
{
- idle();
+ localDead = idle();
}
}
}
commit 65ec9d71bfb5f42fae1f3cb7b3666431d635762f
Author: Mark Michelson <mmichelson at digium.com>
Date: Mon Apr 25 13:21:51 2011 -0500
Fix up some documentation.
diff --git a/ThreadPool/include/AsteriskSCF/ThreadPool.h b/ThreadPool/include/AsteriskSCF/ThreadPool.h
index d86fa92..2afcd75 100644
--- a/ThreadPool/include/AsteriskSCF/ThreadPool.h
+++ b/ThreadPool/include/AsteriskSCF/ThreadPool.h
@@ -25,8 +25,13 @@ namespace AsteriskSCF
namespace ThreadPool
{
-// For more information on these methods, see
-// AsteriskSCF/System/ThreadPool/ThreadPoolIf.ice
+/**
+ * Forward declaration of our implementation
+ * of an AsteriskSCF::System::ThreadPool::V1::Pool.
+ *
+ * For more information on its methods, see
+ * AsteriskSCF/System/ThreadPool/ThreadPoolIf.ice
+ */
class ThreadPool;
class ThreadQueueListener;
@@ -39,8 +44,10 @@ public:
ThreadQueueListenerPtr createThreadQueueListener(ThreadPool *pool);
};
-// For more information on these methods, see
-// AsteriskSCF/System/ThreadPool/ThreadPoolIf.ice
+/**
+ * For more information on these methods, see
+ * AsteriskSCF/System/ThreadPool/ThreadPoolIf.ice
+ */
class ThreadPoolFactory : public AsteriskSCF::System::ThreadPool::V1::PoolFactory
{
public:
diff --git a/ThreadPool/src/ThreadPool.cpp b/ThreadPool/src/ThreadPool.cpp
index 01b8beb..f583ac5 100644
--- a/ThreadPool/src/ThreadPool.cpp
+++ b/ThreadPool/src/ThreadPool.cpp
@@ -287,6 +287,16 @@ public:
}
}
+ /**
+ * Queued task called when we are notified work has been added to the queue.
+ *
+ * When executed, we notify our listener that work has been added and we
+ * awaken idle threads.
+ *
+ * XXX The current method awakens all idle threads. A potential alternative
+ * would be to awaken a number of idle threads equal to the number of
+ * tasks added to the queue.
+ */
class WorkAdded : public Work
{
public:
@@ -296,11 +306,6 @@ public:
void execute()
{
mPool->mListener->queueWorkAdded(mPool, mNewWork, mWasEmpty);
- // XXX This could potentially stand to be more sophisticated, but poking all the
- // idle threads will get the job done too.
- //
- // A potential alternative would be to poke a number of idle threads equal to the
- // new work count.
for (ThreadIterator i = mPool->mIdleThreads.begin();
i != mPool->mIdleThreads.end(); ++i)
{
@@ -325,6 +330,14 @@ public:
}
}
+ /**
+ * Queued task called when we are notified our queue has been emptied.
+ *
+ * All that happens here is we relay the empty notice to our pool
+ * listener. The reason this task is queued is not due to resource
+ * contention. Instead, the task is queued in order to preserve proper
+ * order of outbound notifications.
+ */
class Emptied : public Work
{
public:
@@ -349,15 +362,17 @@ public:
}
}
- //The control queue is where operations to modify
- //the thread pool are queued. This allows for thread
- //pool listeners to guarantee that the notifications
- //they receive arrive in the order that the thread
- //pool processed the operations that caused those
- //notifications to be sent.
- //
- //This also has the benefit of eliminating locking
- //from the majority of the thread pool.
+ /**
+ * The control queue is where operations to modify
+ * the thread pool are queued. This allows for thread
+ * pool listeners to guarantee that the notifications
+ * they receive arrive in the order that the thread
+ * pool processed the operations that caused those
+ * notifications to be sent.
+ *
+ * This also has the benefit of eliminating locking
+ * from the majority of the thread pool.
+ */
QueuePtr mControlQueue;
PoolListenerPtr mListener;
@@ -367,13 +382,15 @@ public:
ThreadContainer mIdleThreads;
ThreadContainer mZombieThreads;
- //These two members are required solely to make
- //destruction of the thread pool safe. Since destruction
- //of the thread pool is the only operation that does not
- //go through the control queue, we have to be careful
- //to make sure that no one attempts to make use of the
- //control queue once we are in the process of
- //destroying the thread pool.
+ /**
+ * These two members are required solely to make
+ * destruction of the thread pool safe. Since destruction
+ * of the thread pool is the only operation that does not
+ * go through the control queue, we have to be careful
+ * to make sure that no one attempts to make use of the
+ * control queue once we are in the process of
+ * destroying the thread pool.
+ */
bool mShuttingDown;
boost::mutex mQueueLock;
};
commit 59b0c3247b2faf3ed662d8d5792f077306593e1a
Author: Mark Michelson <mmichelson at digium.com>
Date: Mon Apr 25 11:44:24 2011 -0500
Make the empty notification a queued operation.
This isn't done because of resource contention. This is done in order
to preserve order of operations when reporting to a pool listener.
diff --git a/ThreadPool/src/ThreadPool.cpp b/ThreadPool/src/ThreadPool.cpp
index 0a00549..01b8beb 100644
--- a/ThreadPool/src/ThreadPool.cpp
+++ b/ThreadPool/src/ThreadPool.cpp
@@ -325,6 +325,30 @@ public:
}
}
+ class Emptied : public Work
+ {
+ public:
+ Emptied(ThreadPool *pool) : mPool(pool) { }
+ void execute()
+ {
+ mPool->mListener->queueEmptied(mPool);
+ }
+ private:
+ ThreadPool *mPool;
+ };
+
+ typedef IceUtil::Handle<Emptied> EmptiedPtr;
+
+ void handleEmptied()
+ {
+ boost::lock_guard<boost::mutex> lock(mQueueLock);
+ if (!mShuttingDown)
+ {
+ EmptiedPtr task(new Emptied(this));
+ mControlQueue->enqueueWork(task);
+ }
+ }
+
//The control queue is where operations to modify
//the thread pool are queued. This allows for thread
//pool listeners to guarantee that the notifications
@@ -390,7 +414,7 @@ public:
*/
void emptied()
{
- mThreadPool->mListener->queueEmptied(mThreadPool);
+ mThreadPool->handleEmptied();
}
private:
ThreadPool *mThreadPool;
diff --git a/ThreadPool/test/TestThreadPool.cpp b/ThreadPool/test/TestThreadPool.cpp
index c2b30af..1af3b96 100644
--- a/ThreadPool/test/TestThreadPool.cpp
+++ b/ThreadPool/test/TestThreadPool.cpp
@@ -154,6 +154,15 @@ static void waitForWorkNotice(TestListenerPtr& listener)
}
}
+static void waitForEmptyNotice(TestListenerPtr& listener)
+{
+ boost::unique_lock<boost::mutex> lock(listener->mLock);
+ while (!listener->mEmptyNotice)
+ {
+ listener->mDone.wait(lock);
+ }
+}
+
BOOST_AUTO_TEST_SUITE(ThreadPoolTest)
BOOST_AUTO_TEST_CASE(addWork)
@@ -243,6 +252,8 @@ BOOST_AUTO_TEST_CASE(oneTaskOneThread)
waitForCompletion(work);
BOOST_CHECK(work->taskExecuted == true);
+
+ waitForEmptyNotice(listener);
BOOST_CHECK(listener->mEmptyNotice == true);
//The thread should be idle now. Let's make sure
@@ -275,6 +286,8 @@ BOOST_AUTO_TEST_CASE(oneThreadOneTask)
waitForCompletion(work);
BOOST_CHECK(work->taskExecuted == true);
+
+ waitForEmptyNotice(listener);
BOOST_CHECK(listener->mEmptyNotice == true);
//And of course, the thread should become idle once work is done
commit c16907ace888a1ed1020e6f49e764c330ad40dd5
Author: Mark Michelson <mmichelson at digium.com>
Date: Mon Apr 25 10:57:51 2011 -0500
Make workAdded and idle thread awakening a queued operation.
Adjusted tests as well. AND THEY PASS
diff --git a/ThreadPool/src/ThreadPool.cpp b/ThreadPool/src/ThreadPool.cpp
index 645a089..0a00549 100644
--- a/ThreadPool/src/ThreadPool.cpp
+++ b/ThreadPool/src/ThreadPool.cpp
@@ -287,6 +287,44 @@ public:
}
}
+ class WorkAdded : public Work
+ {
+ public:
+ WorkAdded(int numNewWork, bool wasEmpty, ThreadPool *pool)
+ : mNewWork(numNewWork), mWasEmpty(wasEmpty), mPool(pool) { }
+
+ void execute()
+ {
+ mPool->mListener->queueWorkAdded(mPool, mNewWork, mWasEmpty);
+ // XXX This could potentially stand to be more sophisticated, but poking all the
+ // idle threads will get the job done too.
+ //
+ // A potential alternative would be to poke a number of idle threads equal to the
+ // new work count.
+ for (ThreadIterator i = mPool->mIdleThreads.begin();
+ i != mPool->mIdleThreads.end(); ++i)
+ {
+ (*i)->setState(Alive);
+ }
+ }
+ private:
+ const int mNewWork;
+ const bool mWasEmpty;
+ ThreadPool *mPool;
+ };
+
+ typedef IceUtil::Handle<WorkAdded> WorkAddedPtr;
+
+ void handleWorkAdded(int numNewWork, bool wasEmpty)
+ {
+ boost::lock_guard<boost::mutex> lock(mQueueLock);
+ if (!mShuttingDown)
+ {
+ WorkAddedPtr task(new WorkAdded(numNewWork, wasEmpty, this));
+ mControlQueue->enqueueWork(task);
+ }
+ }
+
//The control queue is where operations to modify
//the thread pool are queued. This allows for thread
//pool listeners to guarantee that the notifications
@@ -335,17 +373,7 @@ public:
*/
void workAdded(int numNewWork, bool wasEmpty)
{
- mThreadPool->mListener->queueWorkAdded(mThreadPool, numNewWork, wasEmpty);
- // XXX This could potentially stand to be more sophisticated, but poking all the
- // idle threads will get the job done too.
- //
- // A potential alternative would be to poke a number of idle threads equal to the
- // new work count.
- for (ThreadIterator i = mThreadPool->mIdleThreads.begin();
- i != mThreadPool->mIdleThreads.end(); ++i)
- {
- (*i)->setState(Alive);
- }
+ mThreadPool->handleWorkAdded(numNewWork, wasEmpty);
}
/**
@@ -354,9 +382,6 @@ public:
*/
void workResumable()
{
- /* This should never be called since the ThreadPool does not
- * use a SuspendableQueue
- */
assert(false);
}
diff --git a/ThreadPool/test/TestThreadPool.cpp b/ThreadPool/test/TestThreadPool.cpp
index cef9102..c2b30af 100644
--- a/ThreadPool/test/TestThreadPool.cpp
+++ b/ThreadPool/test/TestThreadPool.cpp
@@ -48,6 +48,7 @@ public:
mTasks = count;
mWasEmpty = wasEmpty;
mWorkAddedNotice = true;
+ mDone.notify_one();
}
void queueEmptied(const PoolPtr&)
@@ -144,6 +145,15 @@ static void waitForCompletion(T& task)
}
}
+static void waitForWorkNotice(TestListenerPtr& listener)
+{
+ boost::unique_lock<boost::mutex> lock(listener->mLock);
+ while (!listener->mWorkAddedNotice)
+ {
+ listener->mDone.wait(lock);
+ }
+}
+
BOOST_AUTO_TEST_SUITE(ThreadPoolTest)
BOOST_AUTO_TEST_CASE(addWork)
@@ -158,6 +168,8 @@ BOOST_AUTO_TEST_CASE(addWork)
queue->enqueueWork(work);
+ waitForWorkNotice(listener);
+
BOOST_CHECK(listener->mWorkAddedNotice == true);
BOOST_CHECK(listener->mWasEmpty == true);
BOOST_CHECK(listener->mTasks == 1);
commit 4c66ace69ba6180b85f2869955a1efc1c078d8fb
Author: Mark Michelson <mmichelson at digium.com>
Date: Mon Apr 25 10:17:12 2011 -0500
Fix problems that could cause potential hanging.
During testing, about 1 time in 20, the ThreadPool test
would hang. After looking into it further, it was apparent that
the DefaultQueueListener was sitting idle despite the fact that
work had been queued.
The problem is that when either the DefaultQueueListener or
a ThreadPool's WorkerThread goes idle, it would set its own state
to Idle. This could override a different state set externally. The
only way to get out of this state was to queue new work, but in
the tests, all work had already been queued.
Now, there is a special boolean used expressly for thread
awakening. The states of DefaultQueueListener and WorkerThread
have been altered to remove states that are no longer used as
a result.
diff --git a/ThreadPool/include/AsteriskSCF/WorkerThread.h b/ThreadPool/include/AsteriskSCF/WorkerThread.h
index 36f0124..4597a11 100644
--- a/ThreadPool/include/AsteriskSCF/WorkerThread.h
+++ b/ThreadPool/include/AsteriskSCF/WorkerThread.h
@@ -28,13 +28,11 @@ namespace ThreadPool
enum ThreadState
{
/**
- * Actively doing work
+ * Thread is not scheduled for deletion. The
+ * thread may be actively executing tasks, or it
+ * may be idle, waiting to be woken up.
*/
- Active,
- /**
- * Nothing to do; waiting to be poked
- */
- Idle,
+ Alive,
/**
* Marked for deletion. May still be executing
* code but next time it checks its state, it
@@ -42,7 +40,7 @@ enum ThreadState
*/
Zombie,
/**
- * The ThreadPoolImpl considers this thread to
+ * The ThreadPool considers this thread to
* be gone. The thread just needs to get out of
* the way ASAP.
*/
diff --git a/ThreadPool/src/ThreadPool.cpp b/ThreadPool/src/ThreadPool.cpp
index 3b984c7..645a089 100644
--- a/ThreadPool/src/ThreadPool.cpp
+++ b/ThreadPool/src/ThreadPool.cpp
@@ -344,7 +344,7 @@ public:
for (ThreadIterator i = mThreadPool->mIdleThreads.begin();
i != mThreadPool->mIdleThreads.end(); ++i)
{
- (*i)->setState(Active);
+ (*i)->setState(Alive);
}
}
diff --git a/ThreadPool/src/WorkerThread.cpp b/ThreadPool/src/WorkerThread.cpp
index 821b3df..ea5b8fa 100644
--- a/ThreadPool/src/WorkerThread.cpp
+++ b/ThreadPool/src/WorkerThread.cpp
@@ -32,12 +32,12 @@ class WorkerThreadPriv
public:
WorkerThreadPriv(const QueuePtr& workQueue, WorkerThreadListener *listener,
WorkerThread *workerThread)
- : mState(Active), mListener(listener), mQueue(workQueue), mWorkerThread(workerThread),
+ : mWakeUp(false), mState(Alive), mListener(listener), mQueue(workQueue), mWorkerThread(workerThread),
mThread(boost::bind(&WorkerThreadPriv::active, this)) { }
void active()
{
- while (mState == Active)
+ while (mState == Alive)
{
if (!mQueue->executeWork())
{
@@ -62,29 +62,20 @@ public:
{
{
boost::unique_lock<boost::mutex> lock(mLock);
- // If we've been turned into a zombie while we
- // were active, then just go ahead and return.
- if (mState == Zombie)
+ if (mState != Alive)
{
return;
}
-
- // Otherwise, we'll set ourselves idle and wait
- // for a poke
- mState = Idle;
- }
-
- mListener->activeThreadIdle(mWorkerThread);
-
- {
- boost::unique_lock<boost::mutex> lock(mLock);
- while (mState == Idle)
+ mListener->activeThreadIdle(mWorkerThread);
+ while (!mWakeUp)
{
mCond.wait(lock);
}
+ mWakeUp = false;
}
}
+ bool mWakeUp;
ThreadState mState;
WorkerThreadListener *mListener;
QueuePtr mQueue;
@@ -107,6 +98,7 @@ void WorkerThread::setState(ThreadState newState)
{
boost::unique_lock<boost::mutex> lock(mPriv->mLock);
mPriv->mState = newState;
+ mPriv->mWakeUp = true;
mPriv->mCond.notify_one();
}
diff --git a/WorkQueue/src/DefaultQueueListener.cpp b/WorkQueue/src/DefaultQueueListener.cpp
index 77043cb..5bb45bb 100644
--- a/WorkQueue/src/DefaultQueueListener.cpp
+++ b/WorkQueue/src/DefaultQueueListener.cpp
@@ -22,40 +22,33 @@ namespace AsteriskSCF
namespace WorkQueue
{
-enum ListenerState
-{
- Active,
- Idle,
- Dead
-};
-
using namespace AsteriskSCF::System::WorkQueue::V1;
class DefaultQueueListenerPriv
{
public:
DefaultQueueListenerPriv(const QueuePtr& queue)
- : mState(Active), mQueue(queue.get()),
+ : mWakeUp(false), mDead(false), mQueue(queue.get()),
mThread(boost::bind(&DefaultQueueListenerPriv::run, this)) { }
void idle()
{
boost::unique_lock<boost::mutex> lock(mLock);
- if (mState == Dead)
+ if (mDead)
{
return;
}
- mState = Idle;
- while (mState == Idle)
+ while (!mWakeUp)
{
mCond.wait(lock);
}
+ mWakeUp = false;
}
void run()
{
- while (mState != Dead)
+ while (!mDead)
{
if (!mQueue->executeWork())
{
@@ -64,14 +57,16 @@ public:
}
}
- void setState(ListenerState newState)
+ void setState(bool dead)
{
boost::unique_lock<boost::mutex> lock(mLock);
- mState = newState;
+ mWakeUp = true;
+ mDead = dead;
mCond.notify_one();
}
- ListenerState mState;
+ bool mWakeUp;
+ bool mDead;
Queue *mQueue;
boost::mutex mLock;
boost::condition_variable mCond;
@@ -83,18 +78,21 @@ DefaultQueueListener::DefaultQueueListener(const QueuePtr& queue)
DefaultQueueListener::~DefaultQueueListener()
{
- mPriv->setState(Dead);
+ mPriv->setState(true);
mPriv->mThread.join();
}
-void DefaultQueueListener::workAdded(int, bool)
+void DefaultQueueListener::workAdded(int, bool wasEmpty)
{
- mPriv->setState(Active);
+ if (wasEmpty)
+ {
+ mPriv->setState(false);
+ }
}
void DefaultQueueListener::workResumable()
{
- mPriv->setState(Active);
+ mPriv->setState(false);
}
void DefaultQueueListener::emptied()
-----------------------------------------------------------------------
--
asterisk-scf/integration/ice-util-cpp.git
More information about the asterisk-scf-commits
mailing list