Browse Source

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 <[email protected]>
Aleksandar Fabijanic 2 years ago
parent
commit
1e90f64bbf

+ 5 - 1
.github/workflows/ci.yml

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

+ 7 - 3
Crypto/testsuite/Makefile

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

+ 4 - 3
Foundation/include/Poco/ArchiveStrategy.h

@@ -23,6 +23,7 @@
 #include "Poco/File.h"
 #include "Poco/DateTimeFormatter.h"
 #include "Poco/NumberFormatter.h"
+#include <atomic>
 
 
 namespace Poco {
@@ -44,7 +45,7 @@ public:
 
 	virtual LogFile* open(LogFile* pFile) = 0;
 		/// Open a new log file and return it.
-	
+
 	virtual LogFile* archive(LogFile* pFile) = 0;
 		/// Renames the given log file for archiving
 		/// and creates and returns a new log file.
@@ -61,8 +62,8 @@ private:
 	ArchiveStrategy(const ArchiveStrategy&);
 	ArchiveStrategy& operator = (const ArchiveStrategy&);
 
-	bool _compress;
-	ArchiveCompressor* _pCompressor;
+	std::atomic<bool> _compress;
+	std::atomic<ArchiveCompressor*> _pCompressor;
 };
 
 

+ 4 - 3
Foundation/include/Poco/NumericString.h

@@ -387,7 +387,8 @@ bool intToStr(T value,
 	char fill = ' ',
 	char thSep = 0,
 	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.
 	/// 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
@@ -534,8 +535,8 @@ bool intToStr(T value,
 }
 
 
-//@ deprecated
 template <typename T>
+[[deprecated("use intToStr instead")]]
 bool uIntToStr(T value,
 	unsigned short base,
 	char* result,
@@ -579,8 +580,8 @@ bool intToStr (T number,
 }
 
 
-//@ deprecated
 template <typename T>
+[[deprecated("use intToStr instead")]]
 bool uIntToStr (T number,
 	unsigned short base,
 	std::string& result,

+ 8 - 0
Foundation/include/Poco/String.h

@@ -23,6 +23,11 @@
 #include <cstring>
 #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 {
 
@@ -760,5 +765,8 @@ struct CILess
 
 } // namespace Poco
 
+#if defined(__clang__) && ((__clang_major__ > 3) || (__clang_major__ == 3 && __clang_minor__ >= 6))
+#	pragma clang diagnostic pop
+#endif
 
 #endif // Foundation_String_INCLUDED

+ 23 - 12
Foundation/include/Poco/Task.h

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

+ 9 - 2
Foundation/include/Poco/TaskManager.h

@@ -65,9 +65,16 @@ public:
 	~TaskManager();
 		/// Destroys the TaskManager.
 
-	void start(Task* pTask);
+	bool start(Task* pTask);
 		/// 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
 		/// and deletes it when it is finished.

+ 30 - 26
Foundation/src/ActiveThreadPool.cpp

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

+ 1 - 1
Foundation/src/ArchiveStrategy.cpp

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

+ 3 - 2
Foundation/src/DirectoryWatcher.cpp

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

+ 12 - 0
Foundation/src/NotificationCenter.cpp

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

+ 26 - 25
Foundation/src/Task.cpp

@@ -41,7 +41,7 @@ void Task::cancel()
 	_state = TASK_CANCELLING;
 	_cancelEvent.set();
 	if (_pOwner)
-		_pOwner->taskCancelled(this);
+		_pOwner.load()->taskCancelled(this);
 }
 
 
@@ -56,27 +56,29 @@ void Task::reset()
 void Task::run()
 {
 	TaskManager* pOwner = getOwner();
-	if (pOwner)
-		pOwner->taskStarted(this);
-	try
-	{
-		_state = TASK_RUNNING;
-		runTask();
-	}
-	catch (Exception& exc)
-	{
-		if (pOwner)
-			pOwner->taskFailed(this, exc);
-	}
-	catch (std::exception& exc)
+	if (_state.exchange(TASK_RUNNING) < TASK_RUNNING)
 	{
 		if (pOwner)
-			pOwner->taskFailed(this, SystemException("Task::run()", exc.what()));
-	}
-	catch (...)
-	{
-		if (pOwner)
-			pOwner->taskFailed(this, SystemException("Task::run(): unknown exception"));
+			pOwner->taskStarted(this);
+		try
+		{
+			runTask();
+		}
+		catch (Exception& exc)
+		{
+			if (pOwner)
+				pOwner->taskFailed(this, exc);
+		}
+		catch (std::exception& exc)
+		{
+			if (pOwner)
+				pOwner->taskFailed(this, SystemException("Task::run()", exc.what()));
+		}
+		catch (...)
+		{
+			if (pOwner)
+				pOwner->taskFailed(this, SystemException("Task::run(): unknown exception"));
+		}
 	}
 	_state = TASK_FINISHED;
 	if (pOwner) pOwner->taskFinished(this);
@@ -102,21 +104,20 @@ void Task::setProgress(float progress)
 	{
 		FastMutex::ScopedLock lock(_mutex);
 		if (_pOwner)
-			_pOwner->taskProgress(this, _progress);
+			_pOwner.load()->taskProgress(this, _progress);
 	}
 }
 
 
 void Task::setOwner(TaskManager* pOwner)
 {
-	FastMutex::ScopedLock lock(_mutex);
 	_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);
 
 	if (_pOwner)
-		_pOwner->postNotification(pNf);
+		_pOwner.load()->postNotification(pNf);
 	else if (pNf)
 		pNf->release();
 }

+ 25 - 15
Foundation/src/TaskManager.cpp

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

+ 16 - 6
Foundation/testsuite/src/FIFOEventTest.cpp

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

+ 71 - 6
Foundation/testsuite/src/TaskManagerTest.cpp

@@ -22,6 +22,7 @@
 #include "Poco/Observer.h"
 #include "Poco/Exception.h"
 #include "Poco/AutoPtr.h"
+#include <iostream>
 
 
 using Poco::TaskManager;
@@ -51,12 +52,14 @@ namespace
 	public:
 		TestTask():
 			Task("TestTask"),
-			_fail(false)
+			_fail(false),
+			_started(false)
 		{
 		}
 
 		void runTask()
 		{
+			_started = true;
 			_event.wait();
 			setProgress(0.5);
 			_event.wait();
@@ -78,9 +81,15 @@ namespace
 			_event.set();
 		}
 
+		bool started() const
+		{
+			return _started;
+		}
+
 	private:
 		Event _event;
-		bool  _fail;
+		std::atomic<bool> _fail;
+		std::atomic<bool> _started;
 	};
 
 	class SimpleTask: public Task
@@ -277,6 +286,11 @@ void TaskManagerTest::testFinish()
 	assertTrue (!to.error());
 	tm.cancelAll();
 	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());
 	tm.cancelAll();
 	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, TaskProgressNotification>(to, &TaskObserver::taskProgress));
 	AutoPtr<TestTask> pTT = new TestTask;
-	tm.start(pTT.duplicate());
+	assertTrue (tm.start(pTT.duplicate()));
 	while (pTT->state() < Task::TASK_RUNNING) Thread::sleep(50);
 	assertTrue (pTT->progress() == 0);
 	Thread::sleep(200);
@@ -356,6 +375,11 @@ void TaskManagerTest::testError()
 	assertTrue (list.empty());
 	tm.cancelAll();
 	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()
 {
 	TaskManager tm;
-	tm.start(new SimpleTask);
-	tm.start(new SimpleTask);
-	tm.start(new SimpleTask);
+
+	AutoPtr<SimpleTask> pTT1 = 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();
 	assertTrue (list.size() == 3);
@@ -457,6 +514,13 @@ void TaskManagerTest::testMultiTasks()
 	while (tm.count() > 0) Thread::sleep(100);
 	assertTrue (tm.count() == 0);
 	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, testCancel);
 	CppUnit_addTest(pSuite, TaskManagerTest, testError);
+	CppUnit_addTest(pSuite, TaskManagerTest, testCancelNoStart);
 	CppUnit_addTest(pSuite, TaskManagerTest, testMultiTasks);
 	CppUnit_addTest(pSuite, TaskManagerTest, testCustom);
 	CppUnit_addTest(pSuite, TaskManagerTest, testCustomThreadPool);

+ 1 - 0
Foundation/testsuite/src/TaskManagerTest.h

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

+ 39 - 1
Foundation/testsuite/src/TaskTest.cpp

@@ -29,12 +29,14 @@ namespace
 	class TestTask: public Task
 	{
 	public:
-		TestTask(): Task("TestTask")
+		TestTask(): Task("TestTask"),
+			_started(false)
 		{
 		}
 
 		void runTask()
 		{
+			_started = true;
 			try
 			{
 				_event.wait();
@@ -73,8 +75,14 @@ namespace
 			}
 		}
 
+		bool started() const
+		{
+			return _started;
+		}
+
 	private:
 		Event _event;
+		std::atomic<bool> _started;
 	};
 }
 
@@ -104,6 +112,20 @@ void TaskTest::testFinish()
 	pTT->cont();
 	thr.join();
 	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()
 {
 }
@@ -159,6 +196,7 @@ CppUnit::Test* TaskTest::suite()
 	CppUnit_addTest(pSuite, TaskTest, testFinish);
 	CppUnit_addTest(pSuite, TaskTest, testCancel1);
 	CppUnit_addTest(pSuite, TaskTest, testCancel2);
+	CppUnit_addTest(pSuite, TaskTest, testCancelNoStart);
 
 	return pSuite;
 }

+ 1 - 0
Foundation/testsuite/src/TaskTest.h

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

+ 19 - 6
runLibTests.sh

@@ -7,11 +7,15 @@
 # to clean and rebuild a single library, with all of its dependencies,
 # 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
 # (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
 # the shellcheck wants them quoted to remain quiet
@@ -20,7 +24,7 @@
 path=$1
 if [ -z "${path}" ]; then
 	echo "Library not specified"
-	echo "Usage: $0 path	 [address | undefined | thread ]"
+	echo "Usage: $0 path [address | undefined | thread] [<test> | -all | none]"
 	exit 1
 fi
 
@@ -52,13 +56,20 @@ make distclean -C "$basedir"/CppUnit
 make -s -j4 -C "$basedir"/Foundation $flags
 make -s -j4 -C "$basedir"/CppUnit $flags
 
+test=$3
+if [ -z "${test}" ]; then
+	test="-all"
+fi
+
 # Foundation requested, build/run tests and exit
 if [[ "$path" == "$basedir"/"Foundation" ]]; then
 	cd "$path/testsuite/" || exit
 	make -s -j4 -C ./ $flags
 	cd "bin/$OSNAME/$OSARCH/" || exit
-	./testrunner -all
-	./testrunnerd -all
+	if [[ "$test" != "none" ]]; then
+		./testrunner "${test}"
+		./testrunnerd "${test}"
+	fi
 	echo "$path $flags done."
 	exit 0
 fi
@@ -72,8 +83,10 @@ do
 	make distclean
 	make -s -j4 -C ./ $flags
 	cd bin/"$OSNAME"/"$OSARCH"/ || exit
-	./testrunner -all
-	./testrunnerd -all
+	if [[ "$test" != "none" ]]; then
+		./testrunner "${test}"
+		./testrunnerd "${test}"
+	fi
 	echo "$1 $flags done."
 	cd ../../../../ || exit
 done