4307/8/9/10 data races (#4312)

* fix(NumericString): properly mark uIntToString deprecated #4304

* dev(runLibtests): allow to specify test to run

* fix(NotificationCenter): data race #4307

* fix(DirectoryWatcher): data race #4308

* fix(ArchiveStrategy): data race #4309

* fix(ActiveThread): data race #4310

* fix(Task): Cancelled Task shouldn't start running #4311 (WIP)

* fix(String): ignore clang loop unrolling warnings

* fix(TaskManager): task ownership #4311

* chore(FIFOEventTest): fix unused var warning; disable benchmark in test

* fix(Task): remove unnecessary mutex (and prevent cyclic locking reported by TSAN)

* fix(CryptoTest): disable testEncryptDecryptGCM

* fix(ci): typo

* fix(NotificationCenter): disable and clear observers in dtor (#4307)

---------

Co-authored-by: Matej Kenda <matejken@gmail.com>
This commit is contained in:
Aleksandar Fabijanic 2023-12-09 21:16:24 +01:00 committed by GitHub
parent 35e1490b26
commit 1e90f64bbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 303 additions and 111 deletions

View File

@ -2,6 +2,9 @@
# To retry only on timeout set retry_on: timeout # To retry only on timeout set retry_on: timeout
# To retry only on error set retry_on: error # To retry only on error set retry_on: error
# For more information on the retry action see https://github.com/nick-fields/retry # For more information on the retry action see https://github.com/nick-fields/retry
name: Compile and Testrun
on: on:
pull_request: pull_request:
types: [opened] types: [opened]
@ -231,7 +234,8 @@ jobs:
CppUnit::TestCaller<ExpireLRUCacheTest>.testAccessExpireN, CppUnit::TestCaller<ExpireLRUCacheTest>.testAccessExpireN,
CppUnit::TestCaller<UniqueExpireLRUCacheTest>.testExpireN, CppUnit::TestCaller<UniqueExpireLRUCacheTest>.testExpireN,
CppUnit::TestCaller<ExpireLRUCacheTest>.testAccessExpireN, CppUnit::TestCaller<ExpireLRUCacheTest>.testAccessExpireN,
CppUnit::TestCaller<PollSetTest>.testPollClosedServer" CppUnit::TestCaller<PollSetTest>.testPollClosedServer,
CppUnit::TestCaller<CryptoTest>.testEncryptDecryptGCM"
PWD=`pwd` PWD=`pwd`
ctest --output-on-failure -E "(DataMySQL)|(DataODBC)|(PostgreSQL)|(MongoDB)|(Redis)" ctest --output-on-failure -E "(DataMySQL)|(DataODBC)|(PostgreSQL)|(MongoDB)|(Redis)"

View File

@ -16,10 +16,14 @@ else
ifeq ($(findstring AIX, $(POCO_CONFIG)), AIX) ifeq ($(findstring AIX, $(POCO_CONFIG)), AIX)
SYSLIBS += -lssl_a -lcrypto_a -lz -ldl SYSLIBS += -lssl_a -lcrypto_a -lz -ldl
else else
ifeq ($(POCO_CONFIG),Darwin)
SYSLIBS += -lssl -lcrypto -lz
else
SYSLIBS += -lssl -lcrypto -lz -ldl SYSLIBS += -lssl -lcrypto -lz -ldl
endif endif # Darwin
endif endif # AIX
endif endif # QNX
endif # FreeBSD
objects = CryptoTestSuite Driver \ objects = CryptoTestSuite Driver \
CryptoTest DigestEngineTest ECTest \ CryptoTest DigestEngineTest ECTest \

View File

@ -23,6 +23,7 @@
#include "Poco/File.h" #include "Poco/File.h"
#include "Poco/DateTimeFormatter.h" #include "Poco/DateTimeFormatter.h"
#include "Poco/NumberFormatter.h" #include "Poco/NumberFormatter.h"
#include <atomic>
namespace Poco { namespace Poco {
@ -61,8 +62,8 @@ private:
ArchiveStrategy(const ArchiveStrategy&); ArchiveStrategy(const ArchiveStrategy&);
ArchiveStrategy& operator = (const ArchiveStrategy&); ArchiveStrategy& operator = (const ArchiveStrategy&);
bool _compress; std::atomic<bool> _compress;
ArchiveCompressor* _pCompressor; std::atomic<ArchiveCompressor*> _pCompressor;
}; };

View File

@ -387,7 +387,8 @@ bool intToStr(T value,
char fill = ' ', char fill = ' ',
char thSep = 0, char thSep = 0,
bool lowercase = false) bool lowercase = false)
/// Converts integer to string. Standard numeric bases from binary to hexadecimal are supported. /// Converts signed integer to string. Standard numeric bases from binary to hexadecimal
/// are supported.
/// If width is non-zero, it pads the return value with fill character to the specified width. /// If width is non-zero, it pads the return value with fill character to the specified width.
/// When padding is zero character ('0'), it is prepended to the number itself; all other /// When padding is zero character ('0'), it is prepended to the number itself; all other
/// paddings are prepended to the formatted result with minus sign or base prefix included /// paddings are prepended to the formatted result with minus sign or base prefix included
@ -534,8 +535,8 @@ bool intToStr(T value,
} }
//@ deprecated
template <typename T> template <typename T>
[[deprecated("use intToStr instead")]]
bool uIntToStr(T value, bool uIntToStr(T value,
unsigned short base, unsigned short base,
char* result, char* result,
@ -579,8 +580,8 @@ bool intToStr (T number,
} }
//@ deprecated
template <typename T> template <typename T>
[[deprecated("use intToStr instead")]]
bool uIntToStr (T number, bool uIntToStr (T number,
unsigned short base, unsigned short base,
std::string& result, std::string& result,

View File

@ -23,6 +23,11 @@
#include <cstring> #include <cstring>
#include <algorithm> #include <algorithm>
// ignore loop unrolling warnings in this file
#if defined(__clang__) && ((__clang_major__ > 3) || (__clang_major__ == 3 && __clang_minor__ >= 6))
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wpass-failed"
#endif
namespace Poco { namespace Poco {
@ -760,5 +765,8 @@ struct CILess
} // namespace Poco } // namespace Poco
#if defined(__clang__) && ((__clang_major__ > 3) || (__clang_major__ == 3 && __clang_minor__ >= 6))
# pragma clang diagnostic pop
#endif
#endif // Foundation_String_INCLUDED #endif // Foundation_String_INCLUDED

View File

@ -77,6 +77,8 @@ public:
/// A Task's runTask() method should periodically /// A Task's runTask() method should periodically
/// call this method and stop whatever it is doing in an /// call this method and stop whatever it is doing in an
/// orderly way when this method returns true. /// orderly way when this method returns true.
/// If task is cancelled before it had a chance to run,
/// runTask() will never be called.
TaskState state() const; TaskState state() const;
/// Returns the task's current state. /// Returns the task's current state.
@ -90,9 +92,14 @@ public:
/// be overridden by subclasses. /// be overridden by subclasses.
void run(); void run();
/// Calls the task's runTask() method and notifies the owner /// If task has not been cancelled prior to this call, it
/// of the task's start and completion. /// calls the task's runTask() method and notifies the owner of
/// the task's start and completion.
/// If task has been cancelled prior to this call, it only sets
/// the state to TASK_FINISHED and notifies the owner.
bool hasOwner() const;
/// Returns true iff the task has an owner.
protected: protected:
bool sleep(long milliseconds); bool sleep(long milliseconds);
/// Suspends the current thread for the specified /// Suspends the current thread for the specified
@ -134,7 +141,7 @@ protected:
TaskManager* getOwner() const; TaskManager* getOwner() const;
/// Returns the owner of the task, which may be NULL. /// Returns the owner of the task, which may be NULL.
void setState(TaskState state); TaskState setState(TaskState state);
/// Sets the task's state. /// Sets the task's state.
virtual ~Task(); virtual ~Task();
@ -146,7 +153,7 @@ private:
Task& operator = (const Task&); Task& operator = (const Task&);
std::string _name; std::string _name;
TaskManager* _pOwner; std::atomic<TaskManager*> _pOwner;
std::atomic<float> _progress; std::atomic<float> _progress;
std::atomic<TaskState> _state; std::atomic<TaskState> _state;
Event _cancelEvent; Event _cancelEvent;
@ -185,12 +192,16 @@ inline Task::TaskState Task::state() const
inline TaskManager* Task::getOwner() const inline TaskManager* Task::getOwner() const
{ {
FastMutex::ScopedLock lock(_mutex);
return _pOwner; return _pOwner;
} }
inline bool Task::hasOwner() const
{
return _pOwner != nullptr;
}
} // namespace Poco } // namespace Poco

View File

@ -65,9 +65,16 @@ public:
~TaskManager(); ~TaskManager();
/// Destroys the TaskManager. /// Destroys the TaskManager.
void start(Task* pTask); bool start(Task* pTask);
/// Starts the given task in a thread obtained /// Starts the given task in a thread obtained
/// from the thread pool. /// from the thread pool; returns true if successful.
///
/// If this method returns false, the task was cancelled
/// before it could be started, or it was already running;
/// in any case, a false return means refusal of ownership
/// and indicates that the task pointer may not be valid
/// anymore (it will only be valid if it was duplicated
/// prior to this call).
/// ///
/// The TaskManager takes ownership of the Task object /// The TaskManager takes ownership of the Task object
/// and deletes it when it is finished. /// and deletes it when it is finished.

View File

@ -28,11 +28,14 @@ namespace Poco {
class NewActionNotification: public Notification class NewActionNotification: public Notification
{ {
public: public:
NewActionNotification(Thread::Priority priority, Runnable &runnable, std::string name) : using Ptr = AutoPtr<NewActionNotification>;
NewActionNotification(Thread::Priority priority, Runnable& runnable, const std::string& name) :
_priority(priority), _priority(priority),
_runnable(runnable), _runnable(runnable),
_name(std::move(name)) _name(name)
{ } {
}
~NewActionNotification() override = default; ~NewActionNotification() override = default;
@ -41,7 +44,7 @@ public:
return _runnable; return _runnable;
} }
Thread::Priority priotity() const Thread::Priority priority() const
{ {
return _priority; return _priority;
} }
@ -68,8 +71,8 @@ public:
} }
private: private:
Thread::Priority _priority; std::atomic<Thread::Priority> _priority;
Runnable &_runnable; Runnable& _runnable;
std::string _name; std::string _name;
}; };
@ -157,16 +160,18 @@ void ActiveThread::release()
void ActiveThread::run() void ActiveThread::run()
{ {
do { do
auto *_pTarget = dynamic_cast<NewActionNotification*>(_pTargetQueue.waitDequeueNotification());
while (_pTarget)
{ {
Runnable* pTarget = &_pTarget->runnable(); AutoPtr<Notification> pN = _pTargetQueue.waitDequeueNotification();
_thread.setPriority(_pTarget->priotity()); while (pN)
_thread.setName(_pTarget->name()); {
NewActionNotification::Ptr pNAN = pN.cast<NewActionNotification>();
Runnable& target = pNAN->runnable();
_thread.setPriority(pNAN->priority());
_thread.setName(pNAN->name());
try try
{ {
pTarget->run(); target.run();
} }
catch (Exception& exc) catch (Exception& exc)
{ {
@ -180,11 +185,10 @@ void ActiveThread::run()
{ {
ErrorHandler::handle(); ErrorHandler::handle();
} }
_pTarget->release();
_thread.setName(_name); _thread.setName(_name);
_thread.setPriority(Thread::PRIO_NORMAL); _thread.setPriority(Thread::PRIO_NORMAL);
ThreadLocalStorage::clear(); ThreadLocalStorage::clear();
_pTarget = dynamic_cast<NewActionNotification*>(_pTargetQueue.waitDequeueNotification(1000)); pN = _pTargetQueue.waitDequeueNotification(1000);
} }
_targetCompleted.set(); _targetCompleted.set();
} }

View File

@ -123,7 +123,7 @@ void ArchiveStrategy::moveFile(const std::string& oldPath, const std::string& ne
{ {
f.renameTo(newPath); f.renameTo(newPath);
if (!_pCompressor) _pCompressor = new ArchiveCompressor; if (!_pCompressor) _pCompressor = new ArchiveCompressor;
_pCompressor->compress(newPath); _pCompressor.load()->compress(newPath);
} }
} }

View File

@ -40,6 +40,7 @@
#endif #endif
#include <algorithm> #include <algorithm>
#include <map> #include <map>
#include <atomic>
namespace Poco { namespace Poco {
@ -244,7 +245,7 @@ public:
} }
private: private:
HANDLE _hStopped; std::atomic<HANDLE> _hStopped;
}; };
@ -455,7 +456,7 @@ public:
private: private:
int _queueFD; int _queueFD;
int _dirFD; int _dirFD;
bool _stopped; std::atomic<bool> _stopped;
}; };

View File

@ -29,6 +29,18 @@ NotificationCenter::NotificationCenter()
NotificationCenter::~NotificationCenter() NotificationCenter::~NotificationCenter()
{ {
try
{
Mutex::ScopedLock lock(_mutex);
for (auto& o: _observers)
o->disable();
_observers.clear();
}
catch(...)
{
poco_unexpected();
}
} }

View File

@ -41,7 +41,7 @@ void Task::cancel()
_state = TASK_CANCELLING; _state = TASK_CANCELLING;
_cancelEvent.set(); _cancelEvent.set();
if (_pOwner) if (_pOwner)
_pOwner->taskCancelled(this); _pOwner.load()->taskCancelled(this);
} }
@ -56,11 +56,12 @@ void Task::reset()
void Task::run() void Task::run()
{ {
TaskManager* pOwner = getOwner(); TaskManager* pOwner = getOwner();
if (_state.exchange(TASK_RUNNING) < TASK_RUNNING)
{
if (pOwner) if (pOwner)
pOwner->taskStarted(this); pOwner->taskStarted(this);
try try
{ {
_state = TASK_RUNNING;
runTask(); runTask();
} }
catch (Exception& exc) catch (Exception& exc)
@ -78,6 +79,7 @@ void Task::run()
if (pOwner) if (pOwner)
pOwner->taskFailed(this, SystemException("Task::run(): unknown exception")); pOwner->taskFailed(this, SystemException("Task::run(): unknown exception"));
} }
}
_state = TASK_FINISHED; _state = TASK_FINISHED;
if (pOwner) pOwner->taskFinished(this); if (pOwner) pOwner->taskFinished(this);
} }
@ -102,21 +104,20 @@ void Task::setProgress(float progress)
{ {
FastMutex::ScopedLock lock(_mutex); FastMutex::ScopedLock lock(_mutex);
if (_pOwner) if (_pOwner)
_pOwner->taskProgress(this, _progress); _pOwner.load()->taskProgress(this, _progress);
} }
} }
void Task::setOwner(TaskManager* pOwner) void Task::setOwner(TaskManager* pOwner)
{ {
FastMutex::ScopedLock lock(_mutex);
_pOwner = pOwner; _pOwner = pOwner;
} }
void Task::setState(TaskState state) Task::TaskState Task::setState(TaskState state)
{ {
_state = state; return _state.exchange(state);
} }
@ -127,7 +128,7 @@ void Task::postNotification(Notification* pNf)
FastMutex::ScopedLock lock(_mutex); FastMutex::ScopedLock lock(_mutex);
if (_pOwner) if (_pOwner)
_pOwner->postNotification(pNf); _pOwner.load()->postNotification(pNf);
else if (pNf) else if (pNf)
pNf->release(); pNf->release();
} }

View File

@ -48,30 +48,39 @@ TaskManager::TaskManager(ThreadPool& pool):
TaskManager::~TaskManager() TaskManager::~TaskManager()
{ {
for (auto& pTask: _taskList)
pTask->setOwner(nullptr);
if (_ownPool) delete &_threadPool; if (_ownPool) delete &_threadPool;
} }
void TaskManager::start(Task* pTask) bool TaskManager::start(Task* pTask)
{ {
TaskPtr pAutoTask(pTask); // take ownership immediately TaskPtr pAutoTask(pTask); // take ownership immediately
pAutoTask->setOwner(this); if (pTask->getOwner())
pAutoTask->setState(Task::TASK_STARTING); throw IllegalStateException("Task already owned by another TaskManager");
ScopedLockT lock(_mutex); if (pTask->state() == Task::TASK_IDLE)
_taskList.push_back(pAutoTask); {
pTask->setOwner(this);
pTask->setState(Task::TASK_STARTING);
try try
{ {
_threadPool.start(*pAutoTask, pAutoTask->name()); _threadPool.start(*pTask, pTask->name());
ScopedLockT lock(_mutex);
_taskList.push_back(pAutoTask);
return true;
} }
catch (...) catch (...)
{ {
// Make sure that we don't act like we own the task since pTask->setOwner(nullptr);
// we never started it. If we leave the task on our task
// list, the size of the list is incorrect.
_taskList.pop_back();
throw; throw;
} }
}
pTask->setOwner(nullptr);
return false;
} }
@ -152,6 +161,7 @@ void TaskManager::taskFinished(Task* pTask)
{ {
if (*it == pTask) if (*it == pTask)
{ {
pTask->setOwner(nullptr);
_taskList.erase(it); _taskList.erase(it);
break; break;
} }

View File

@ -18,6 +18,7 @@
#include "Poco/Exception.h" #include "Poco/Exception.h"
#include "Poco/Stopwatch.h" #include "Poco/Stopwatch.h"
#include <iostream> #include <iostream>
#include <numeric>
using namespace Poco; using namespace Poco;
@ -357,12 +358,14 @@ void FIFOEventTest::testAsyncNotifyBenchmark()
const int cnt = 10000; const int cnt = 10000;
int runCount = 1000; int runCount = 1000;
const Poco::Int64 allCount = cnt * runCount; const Poco::Int64 allCount = cnt * runCount;
Poco::Stopwatch sw; std::vector<int> times;
sw.restart(); times.reserve(allCount);
while (runCount-- > 0)
{
std::vector<Poco::ActiveResult<int>> vresult; std::vector<Poco::ActiveResult<int>> vresult;
vresult.reserve(cnt); vresult.reserve(cnt);
Poco::Stopwatch sw;
while (runCount-- > 0)
{
sw.restart();
for (int i = 0; i < cnt; ++i) for (int i = 0; i < cnt; ++i)
{ {
vresult.push_back(simple.notifyAsync(this, i)); vresult.push_back(simple.notifyAsync(this, i));
@ -373,12 +376,19 @@ void FIFOEventTest::testAsyncNotifyBenchmark()
vresult[i].wait(); vresult[i].wait();
assertTrue (vresult[i].data() == (i*2)); assertTrue (vresult[i].data() == (i*2));
} }
}
sw.stop(); sw.stop();
std::cout << "notify and wait time = " << sw.elapsed() / 1000 << std::endl; times.push_back(sw.elapsed()/1000);
vresult.clear();
}
Poco::UInt64 totTime = std::accumulate(times.begin(), times.end(), 0);
double avgTime = static_cast<double>(totTime)/times.size();
std::cout << "Total notify/wait time for " << allCount << " runs of "
<< cnt << " tasks = " << totTime << "ms (avg/run=" << avgTime << "ms)";
assertTrue (_count == allCount); assertTrue (_count == allCount);
} }
void FIFOEventTest::onVoid(const void* pSender) void FIFOEventTest::onVoid(const void* pSender)
{ {
_count++; _count++;
@ -482,6 +492,6 @@ CppUnit::Test* FIFOEventTest::suite()
CppUnit_addTest(pSuite, FIFOEventTest, testExpireReRegister); CppUnit_addTest(pSuite, FIFOEventTest, testExpireReRegister);
CppUnit_addTest(pSuite, FIFOEventTest, testOverwriteDelegate); CppUnit_addTest(pSuite, FIFOEventTest, testOverwriteDelegate);
CppUnit_addTest(pSuite, FIFOEventTest, testAsyncNotify); CppUnit_addTest(pSuite, FIFOEventTest, testAsyncNotify);
CppUnit_addTest(pSuite, FIFOEventTest, testAsyncNotifyBenchmark); //CppUnit_addTest(pSuite, FIFOEventTest, testAsyncNotifyBenchmark);
return pSuite; return pSuite;
} }

View File

@ -22,6 +22,7 @@
#include "Poco/Observer.h" #include "Poco/Observer.h"
#include "Poco/Exception.h" #include "Poco/Exception.h"
#include "Poco/AutoPtr.h" #include "Poco/AutoPtr.h"
#include <iostream>
using Poco::TaskManager; using Poco::TaskManager;
@ -51,12 +52,14 @@ namespace
public: public:
TestTask(): TestTask():
Task("TestTask"), Task("TestTask"),
_fail(false) _fail(false),
_started(false)
{ {
} }
void runTask() void runTask()
{ {
_started = true;
_event.wait(); _event.wait();
setProgress(0.5); setProgress(0.5);
_event.wait(); _event.wait();
@ -78,9 +81,15 @@ namespace
_event.set(); _event.set();
} }
bool started() const
{
return _started;
}
private: private:
Event _event; Event _event;
bool _fail; std::atomic<bool> _fail;
std::atomic<bool> _started;
}; };
class SimpleTask: public Task class SimpleTask: public Task
@ -277,6 +286,11 @@ void TaskManagerTest::testFinish()
assertTrue (!to.error()); assertTrue (!to.error());
tm.cancelAll(); tm.cancelAll();
tm.joinAll(); tm.joinAll();
tm.removeObserver(Observer<TaskObserver, TaskStartedNotification>(to, &TaskObserver::taskStarted));
tm.removeObserver(Observer<TaskObserver, TaskCancelledNotification>(to, &TaskObserver::taskCancelled));
tm.removeObserver(Observer<TaskObserver, TaskFailedNotification>(to, &TaskObserver::taskFailed));
tm.removeObserver(Observer<TaskObserver, TaskFinishedNotification>(to, &TaskObserver::taskFinished));
tm.removeObserver(Observer<TaskObserver, TaskProgressNotification>(to, &TaskObserver::taskProgress));
} }
@ -317,6 +331,11 @@ void TaskManagerTest::testCancel()
assertTrue (!to.error()); assertTrue (!to.error());
tm.cancelAll(); tm.cancelAll();
tm.joinAll(); tm.joinAll();
tm.removeObserver(Observer<TaskObserver, TaskStartedNotification>(to, &TaskObserver::taskStarted));
tm.removeObserver(Observer<TaskObserver, TaskCancelledNotification>(to, &TaskObserver::taskCancelled));
tm.removeObserver(Observer<TaskObserver, TaskFailedNotification>(to, &TaskObserver::taskFailed));
tm.removeObserver(Observer<TaskObserver, TaskFinishedNotification>(to, &TaskObserver::taskFinished));
tm.removeObserver(Observer<TaskObserver, TaskProgressNotification>(to, &TaskObserver::taskProgress));
} }
@ -330,7 +349,7 @@ void TaskManagerTest::testError()
tm.addObserver(Observer<TaskObserver, TaskFinishedNotification>(to, &TaskObserver::taskFinished)); tm.addObserver(Observer<TaskObserver, TaskFinishedNotification>(to, &TaskObserver::taskFinished));
tm.addObserver(Observer<TaskObserver, TaskProgressNotification>(to, &TaskObserver::taskProgress)); tm.addObserver(Observer<TaskObserver, TaskProgressNotification>(to, &TaskObserver::taskProgress));
AutoPtr<TestTask> pTT = new TestTask; AutoPtr<TestTask> pTT = new TestTask;
tm.start(pTT.duplicate()); assertTrue (tm.start(pTT.duplicate()));
while (pTT->state() < Task::TASK_RUNNING) Thread::sleep(50); while (pTT->state() < Task::TASK_RUNNING) Thread::sleep(50);
assertTrue (pTT->progress() == 0); assertTrue (pTT->progress() == 0);
Thread::sleep(200); Thread::sleep(200);
@ -356,6 +375,11 @@ void TaskManagerTest::testError()
assertTrue (list.empty()); assertTrue (list.empty());
tm.cancelAll(); tm.cancelAll();
tm.joinAll(); tm.joinAll();
tm.removeObserver(Observer<TaskObserver, TaskStartedNotification>(to, &TaskObserver::taskStarted));
tm.removeObserver(Observer<TaskObserver, TaskCancelledNotification>(to, &TaskObserver::taskCancelled));
tm.removeObserver(Observer<TaskObserver, TaskFailedNotification>(to, &TaskObserver::taskFailed));
tm.removeObserver(Observer<TaskObserver, TaskFinishedNotification>(to, &TaskObserver::taskFinished));
tm.removeObserver(Observer<TaskObserver, TaskProgressNotification>(to, &TaskObserver::taskProgress));
} }
@ -443,12 +467,45 @@ void TaskManagerTest::testCustom()
} }
void TaskManagerTest::testCancelNoStart()
{
TaskManager tm;
TaskObserver to;
tm.addObserver(Observer<TaskObserver, TaskStartedNotification>(to, &TaskObserver::taskStarted));
tm.addObserver(Observer<TaskObserver, TaskCancelledNotification>(to, &TaskObserver::taskCancelled));
tm.addObserver(Observer<TaskObserver, TaskFailedNotification>(to, &TaskObserver::taskFailed));
tm.addObserver(Observer<TaskObserver, TaskFinishedNotification>(to, &TaskObserver::taskFinished));
tm.addObserver(Observer<TaskObserver, TaskProgressNotification>(to, &TaskObserver::taskProgress));
AutoPtr<TestTask> pTT = new TestTask;
pTT->cancel();
assertTrue (pTT->isCancelled());
assertFalse(tm.start(pTT.duplicate()));
assertTrue (pTT->progress() == 0);
assertTrue (pTT->isCancelled());
assertFalse (pTT->hasOwner());
tm.removeObserver(Observer<TaskObserver, TaskStartedNotification>(to, &TaskObserver::taskStarted));
tm.removeObserver(Observer<TaskObserver, TaskCancelledNotification>(to, &TaskObserver::taskCancelled));
tm.removeObserver(Observer<TaskObserver, TaskFailedNotification>(to, &TaskObserver::taskFailed));
tm.removeObserver(Observer<TaskObserver, TaskFinishedNotification>(to, &TaskObserver::taskFinished));
tm.removeObserver(Observer<TaskObserver, TaskProgressNotification>(to, &TaskObserver::taskProgress));
}
void TaskManagerTest::testMultiTasks() void TaskManagerTest::testMultiTasks()
{ {
TaskManager tm; TaskManager tm;
tm.start(new SimpleTask);
tm.start(new SimpleTask); AutoPtr<SimpleTask> pTT1 = new SimpleTask;
tm.start(new SimpleTask); AutoPtr<SimpleTask> pTT2 = new SimpleTask;
AutoPtr<SimpleTask> pTT3 = new SimpleTask;
tm.start(pTT1.duplicate());
tm.start(pTT2.duplicate());
tm.start(pTT3.duplicate());
assertTrue (pTT1->hasOwner());
assertTrue (pTT2->hasOwner());
assertTrue (pTT3->hasOwner());
TaskManager::TaskList list = tm.taskList(); TaskManager::TaskList list = tm.taskList();
assertTrue (list.size() == 3); assertTrue (list.size() == 3);
@ -457,6 +514,13 @@ void TaskManagerTest::testMultiTasks()
while (tm.count() > 0) Thread::sleep(100); while (tm.count() > 0) Thread::sleep(100);
assertTrue (tm.count() == 0); assertTrue (tm.count() == 0);
tm.joinAll(); tm.joinAll();
while (pTT1->state() != Task::TASK_FINISHED) Thread::sleep(50);
assertFalse (pTT1->hasOwner());
while (pTT2->state() != Task::TASK_FINISHED) Thread::sleep(50);
assertFalse (pTT2->hasOwner());
while (pTT3->state() != Task::TASK_FINISHED) Thread::sleep(50);
assertFalse (pTT3->hasOwner());
} }
@ -510,6 +574,7 @@ CppUnit::Test* TaskManagerTest::suite()
CppUnit_addTest(pSuite, TaskManagerTest, testFinish); CppUnit_addTest(pSuite, TaskManagerTest, testFinish);
CppUnit_addTest(pSuite, TaskManagerTest, testCancel); CppUnit_addTest(pSuite, TaskManagerTest, testCancel);
CppUnit_addTest(pSuite, TaskManagerTest, testError); CppUnit_addTest(pSuite, TaskManagerTest, testError);
CppUnit_addTest(pSuite, TaskManagerTest, testCancelNoStart);
CppUnit_addTest(pSuite, TaskManagerTest, testMultiTasks); CppUnit_addTest(pSuite, TaskManagerTest, testMultiTasks);
CppUnit_addTest(pSuite, TaskManagerTest, testCustom); CppUnit_addTest(pSuite, TaskManagerTest, testCustom);
CppUnit_addTest(pSuite, TaskManagerTest, testCustomThreadPool); CppUnit_addTest(pSuite, TaskManagerTest, testCustomThreadPool);

View File

@ -33,6 +33,7 @@ public:
void testFinish(); void testFinish();
void testCancel(); void testCancel();
void testError(); void testError();
void testCancelNoStart();
void testCustom(); void testCustom();
void testMultiTasks(); void testMultiTasks();
void testCustomThreadPool(); void testCustomThreadPool();

View File

@ -29,12 +29,14 @@ namespace
class TestTask: public Task class TestTask: public Task
{ {
public: public:
TestTask(): Task("TestTask") TestTask(): Task("TestTask"),
_started(false)
{ {
} }
void runTask() void runTask()
{ {
_started = true;
try try
{ {
_event.wait(); _event.wait();
@ -73,8 +75,14 @@ namespace
} }
} }
bool started() const
{
return _started;
}
private: private:
Event _event; Event _event;
std::atomic<bool> _started;
}; };
} }
@ -104,6 +112,20 @@ void TaskTest::testFinish()
pTT->cont(); pTT->cont();
thr.join(); thr.join();
assertTrue (pTT->state() == Task::TASK_FINISHED); assertTrue (pTT->state() == Task::TASK_FINISHED);
pTT->reset();
assertTrue (pTT->progress() == 0);
assertTrue (pTT->state() == Task::TASK_IDLE);
thr.start(*pTT);
assertTrue (pTT->progress() == 0);
pTT->cont();
while (pTT->progress() != 0.5) Thread::sleep(50);
assertTrue (pTT->state() == Task::TASK_RUNNING);
pTT->cont();
while (pTT->progress() != 1.0) Thread::sleep(50);
pTT->cont();
thr.join();
assertTrue (pTT->state() == Task::TASK_FINISHED);
} }
@ -142,6 +164,21 @@ void TaskTest::testCancel2()
} }
void TaskTest::testCancelNoStart()
{
AutoPtr<TestTask> pTT = new TestTask;
assertTrue (pTT->state() == Task::TASK_IDLE);
pTT->cancel();
assertTrue (pTT->state() == Task::TASK_CANCELLING);
Thread thr;
thr.start(*pTT);
while (pTT->state() != Task::TASK_FINISHED)
Thread::sleep(50);
assertTrue (pTT->state() == Task::TASK_FINISHED);
assertFalse (pTT->started());
}
void TaskTest::setUp() void TaskTest::setUp()
{ {
} }
@ -159,6 +196,7 @@ CppUnit::Test* TaskTest::suite()
CppUnit_addTest(pSuite, TaskTest, testFinish); CppUnit_addTest(pSuite, TaskTest, testFinish);
CppUnit_addTest(pSuite, TaskTest, testCancel1); CppUnit_addTest(pSuite, TaskTest, testCancel1);
CppUnit_addTest(pSuite, TaskTest, testCancel2); CppUnit_addTest(pSuite, TaskTest, testCancel2);
CppUnit_addTest(pSuite, TaskTest, testCancelNoStart);
return pSuite; return pSuite;
} }

View File

@ -27,6 +27,7 @@ public:
void testFinish(); void testFinish();
void testCancel1(); void testCancel1();
void testCancel2(); void testCancel2();
void testCancelNoStart();
void setUp(); void setUp();
void tearDown(); void tearDown();

View File

@ -7,11 +7,15 @@
# to clean and rebuild a single library, with all of its dependencies, # to clean and rebuild a single library, with all of its dependencies,
# and run the tests. # and run the tests.
# #
# Usage: ./runLibTests.sh library [address | undefined | thread ] # Usage: ./runLibTests.sh library [address | undefined | thread] [<test> | -all | none]
# #
# Example: ./runLibTests.sh Data/SQLite address # Example: ./runLibTests.sh Data/SQLite address
# (distcleans, rebuilds and runs tests for Data/SQLite with address sanitizer) # (distcleans, rebuilds and runs tests for Data/SQLite with address sanitizer)
# #
# Known shortcomings (TODO):
# - the script does not check if the library is a dependency of another library
# workaround: run the script for the dependent libraries first
#
# g++ does not like empty quoted arguments, but # g++ does not like empty quoted arguments, but
# the shellcheck wants them quoted to remain quiet # the shellcheck wants them quoted to remain quiet
@ -20,7 +24,7 @@
path=$1 path=$1
if [ -z "${path}" ]; then if [ -z "${path}" ]; then
echo "Library not specified" echo "Library not specified"
echo "Usage: $0 path [address | undefined | thread ]" echo "Usage: $0 path [address | undefined | thread] [<test> | -all | none]"
exit 1 exit 1
fi fi
@ -52,13 +56,20 @@ make distclean -C "$basedir"/CppUnit
make -s -j4 -C "$basedir"/Foundation $flags make -s -j4 -C "$basedir"/Foundation $flags
make -s -j4 -C "$basedir"/CppUnit $flags make -s -j4 -C "$basedir"/CppUnit $flags
test=$3
if [ -z "${test}" ]; then
test="-all"
fi
# Foundation requested, build/run tests and exit # Foundation requested, build/run tests and exit
if [[ "$path" == "$basedir"/"Foundation" ]]; then if [[ "$path" == "$basedir"/"Foundation" ]]; then
cd "$path/testsuite/" || exit cd "$path/testsuite/" || exit
make -s -j4 -C ./ $flags make -s -j4 -C ./ $flags
cd "bin/$OSNAME/$OSARCH/" || exit cd "bin/$OSNAME/$OSARCH/" || exit
./testrunner -all if [[ "$test" != "none" ]]; then
./testrunnerd -all ./testrunner "${test}"
./testrunnerd "${test}"
fi
echo "$path $flags done." echo "$path $flags done."
exit 0 exit 0
fi fi
@ -72,8 +83,10 @@ do
make distclean make distclean
make -s -j4 -C ./ $flags make -s -j4 -C ./ $flags
cd bin/"$OSNAME"/"$OSARCH"/ || exit cd bin/"$OSNAME"/"$OSARCH"/ || exit
./testrunner -all if [[ "$test" != "none" ]]; then
./testrunnerd -all ./testrunner "${test}"
./testrunnerd "${test}"
fi
echo "$1 $flags done." echo "$1 $flags done."
cd ../../../../ || exit cd ../../../../ || exit
done done