diff --git a/Foundation/include/Poco/ActiveThreadPool.h b/Foundation/include/Poco/ActiveThreadPool.h index 5c04a8ff02..8b72d98f5c 100644 --- a/Foundation/include/Poco/ActiveThreadPool.h +++ b/Foundation/include/Poco/ActiveThreadPool.h @@ -20,38 +20,35 @@ #include "Poco/Foundation.h" #include "Poco/Thread.h" -#include "Poco/Mutex.h" #include "Poco/Environment.h" -#include +#include namespace Poco { class Runnable; -class ActiveThread; +class ActiveThreadPoolPrivate; class Foundation_API ActiveThreadPool - /// A thread pool always keeps a number of threads running, ready - /// to accept work. - /// Threads in an active thread pool are re-used - /// Every thread in the pool has own notification-queue with Runnable - /// Every Runnable executes on next thread (round-robin model) - /// The thread pool always keeps fixed number of threads running. + /// A thread pool manages and recycles individual Poco::Thread objects + /// to help reduce thread creation costs in programs that use threads. + /// + /// The thread pool supports a task queue. + /// When there are no idle threads, tasks are placed in the task queue to wait for execution. /// Use case for this pool is running many (more than os-max-thread-count) short live tasks - /// Round-robin model allow efficiently utilize cpu cores { public: ActiveThreadPool(int capacity = static_cast(Environment::processorCount()) + 1, int stackSize = POCO_THREAD_STACK_SIZE); - /// Creates a thread pool with fixed capacity threads. + /// Creates a thread pool with a maximum thread count of capacity. /// Threads are created with given stack size. - ActiveThreadPool(std::string name, + ActiveThreadPool(const std::string& name, int capacity = static_cast(Environment::processorCount()) + 1, int stackSize = POCO_THREAD_STACK_SIZE); - /// Creates a thread pool with the given name and fixed capacity threads. + /// Creates a thread pool with the given name and a maximum thread count of capacity. /// Threads are created with given stack size. ~ActiveThreadPool(); @@ -64,39 +61,19 @@ class Foundation_API ActiveThreadPool int getStackSize() const; /// Returns the stack size used to create new threads. - void start(Runnable& target); - /// Obtains a thread and starts the target. + int expiryTimeout() const; + /// Returns the thread expiry timeout value in milliseconds. + /// The default expiryTimeout is 30000 milliseconds (30 seconds). + + void setExpiryTimeout(int expiryTimeout); + /// Set the thread expiry timeout value in milliseconds. + /// The default expiryTimeout is 30000 milliseconds (30 seconds). - void start(Runnable& target, const std::string& name); + void start(Runnable& target, int priority = 0); /// Obtains a thread and starts the target. - /// Assigns the given name to the thread. - - void startWithPriority(Thread::Priority priority, Runnable& target); - /// Obtains a thread, adjusts the thread's priority, and starts the target. - - void startWithPriority(Thread::Priority priority, Runnable& target, const std::string& name); - /// Obtains a thread, adjusts the thread's priority, and starts the target. - /// Assigns the given name to the thread. - - void stopAll(); - /// Stops all running threads and waits for their completion. - /// - /// Will also delete all thread objects. - /// If used, this method should be the last action before - /// the thread pool is deleted. - /// - /// Note: If a thread fails to stop within 10 seconds - /// (due to a programming error, for example), the - /// underlying thread object will not be deleted and - /// this method will return anyway. This allows for a - /// more or less graceful shutdown in case of a misbehaving - /// thread. void joinAll(); - /// Waits for all threads to complete. - /// - /// Note that this will join() underlying - /// threads and restart them for next tasks. + /// Waits for all threads to exit and removes all threads from the thread pool. const std::string& name() const; /// Returns the name of the thread pool, @@ -107,38 +84,14 @@ class Foundation_API ActiveThreadPool /// Returns a reference to the default /// thread pool. -protected: - ActiveThread* getThread(); - ActiveThread* createThread(); - private: ActiveThreadPool(const ActiveThreadPool& pool); ActiveThreadPool& operator = (const ActiveThreadPool& pool); - typedef std::vector ThreadVec; - - std::string _name; - int _capacity; - int _serial; - int _stackSize; - ThreadVec _threads; - mutable FastMutex _mutex; - std::atomic _lastThreadIndex{0}; +private: + std::unique_ptr _impl; }; - -inline int ActiveThreadPool::getStackSize() const -{ - return _stackSize; -} - - -inline const std::string& ActiveThreadPool::name() const -{ - return _name; -} - - } // namespace Poco diff --git a/Foundation/src/ActiveThreadPool.cpp b/Foundation/src/ActiveThreadPool.cpp index 91a6099d89..857d44aea7 100644 --- a/Foundation/src/ActiveThreadPool.cpp +++ b/Foundation/src/ActiveThreadPool.cpp @@ -15,347 +15,490 @@ #include "Poco/ActiveThreadPool.h" #include "Poco/Runnable.h" #include "Poco/Thread.h" -#include "Poco/Event.h" #include "Poco/ThreadLocal.h" #include "Poco/ErrorHandler.h" -#include "Poco/NotificationQueue.h" +#include "Poco/Condition.h" +#include "Poco/RefCountedObject.h" +#include "Poco/AutoPtr.h" #include -#include +#include +#include +#include +#include namespace Poco { -class NewActionNotification: public Notification + +class RunnableList + /// A list of the same priority runnables { public: - using Ptr = AutoPtr; + RunnableList(Runnable& target, int priority): + _priority(priority) + { + push(target); + } - NewActionNotification(Thread::Priority priority, Runnable& runnable, const std::string& name) : - _priority(priority), - _runnable(runnable), - _name(name) + int priority() const { + return _priority; } - ~NewActionNotification() override = default; + void push(Runnable& r) + { + _runnables.push_back(std::ref(r)); + } - Runnable& runnable() const + Runnable& pop() { - return _runnable; + auto r = _runnables.front(); + _runnables.pop_front(); + return r; } - Thread::Priority priority() const + bool empty() const { - return _priority; + return _runnables.empty(); } - const std::string &threadName() const +private: + int _priority = 0; + std::list> _runnables; +}; + + +struct RunnablePriorityCompare +{ + // for make heap + bool operator()(const std::shared_ptr& left, const std::shared_ptr& right) const { - return _name; + return left->priority() < right->priority(); } +}; + - std::string threadFullName() const +class RunnablePriorityQueue + /// A priority queue of runnables +{ +public: + void push(Runnable& target, int priority) { - std::string fullName(_name); - if (_name.empty()) + for (auto& q : _queues) { - fullName = _name; + if (q->priority() == priority) + { + q->push(target); + return; + } } - else + auto q = std::make_shared(std::ref(target), priority); + _queues.push_back(q); + std::push_heap(_queues.begin(), _queues.end(), _comp); + } + + Runnable& pop() + { + auto q = _queues.front(); + auto& r = q->pop(); + if (q->empty()) { - fullName.append(" ("); - fullName.append(_name); - fullName.append(")"); + std::pop_heap(_queues.begin(), _queues.end(), _comp); + _queues.pop_back(); } - return fullName; + return r; + } + + bool empty() const + { + return _queues.empty(); } private: - std::atomic _priority; - Runnable& _runnable; - std::string _name; + std::vector> _queues; + RunnablePriorityCompare _comp; }; -class ActiveThread: public Runnable + +class ActivePooledThread: public Runnable, public RefCountedObject { public: - ActiveThread(const std::string& name, int stackSize = POCO_THREAD_STACK_SIZE); - ~ActiveThread() override = default; + using Ptr = Poco::AutoPtr; + + explicit ActivePooledThread(ActiveThreadPoolPrivate& pool); void start(); - void start(Thread::Priority priority, Runnable& target); - void start(Thread::Priority priority, Runnable& target, const std::string& name); void join(); - void release(); - void run() override; + bool isRunning() const; + + void setRunnable(Runnable& target); + void notifyRunnableReady(); + void registerThreadInactive(); + + virtual void run() override; private: - NotificationQueue _pTargetQueue; - std::string _name; - Thread _thread; - Event _targetCompleted; - FastMutex _mutex; - const long JOIN_TIMEOUT = 10000; - std::atomic _needToStop{false}; + ActiveThreadPoolPrivate& _pool; + std::optional> _target; + Condition _runnableReady; + Thread _thread; +}; + + +class ActiveThreadPoolPrivate +{ +public: + ActiveThreadPoolPrivate(int capacity, int stackSize); + ActiveThreadPoolPrivate(int capacity, int stackSize, const std::string& name); + ~ActiveThreadPoolPrivate(); + + bool tryStart(Runnable& target); + void enqueueTask(Runnable& target, int priority = 0); + void startThread(Runnable& target); + void joinAll(); + + int activeThreadCount() const; + +public: + mutable FastMutex mutex; + std::string name; + std::set allThreads; + std::list waitingThreads; + std::list expiredThreads; + RunnablePriorityQueue runnables; + Condition noActiveThreads; + + int expiryTimeout = 30000; + int maxThreadCount; + int stackSize; + int activeThreads = 0; + int serial = 0; }; -ActiveThread::ActiveThread(const std::string& name, int stackSize): - _name(name), - _thread(name), - _targetCompleted(Event::EVENT_MANUALRESET) +ActivePooledThread::ActivePooledThread(ActiveThreadPoolPrivate& pool): + _pool(pool) { - poco_assert_dbg (stackSize >= 0); - _thread.setStackSize(stackSize); + std::ostringstream name; + name << _pool.name << "[#" << ++_pool.serial << "]"; + _thread.setName(name.str()); + _thread.setStackSize(_pool.stackSize); } -void ActiveThread::start() + +void ActivePooledThread::start() { - _needToStop = false; _thread.start(*this); } -void ActiveThread::start(Thread::Priority priority, Runnable& target) +void ActivePooledThread::setRunnable(Runnable& target) { - _pTargetQueue.enqueueNotification(Poco::makeAuto(priority, target, _name)); + poco_assert(_target.has_value() == false); + _target = std::ref(target); } -void ActiveThread::start(Thread::Priority priority, Runnable& target, const std::string& name) +void ActivePooledThread::notifyRunnableReady() { - _pTargetQueue.enqueueNotification(Poco::makeAuto(priority, target, name)); + _runnableReady.signal(); } -void ActiveThread::join() -{ - _pTargetQueue.wakeUpAll(); - if (!_pTargetQueue.empty()) - { - _targetCompleted.wait(); - } +bool ActivePooledThread::isRunning() const +{ + return _thread.isRunning(); } -void ActiveThread::release() +void ActivePooledThread::join() { - // In case of a statically allocated thread pool (such - // as the default thread pool), Windows may have already - // terminated the thread before we got here. - if (_thread.isRunning()) - { - _needToStop = true; - _pTargetQueue.wakeUpAll(); - if (!_pTargetQueue.empty()) - _targetCompleted.wait(JOIN_TIMEOUT); - } - - if (_thread.tryJoin(JOIN_TIMEOUT)) - { - delete this; - } + _thread.join(); } -void ActiveThread::run() +void ActivePooledThread::run() { - do + FastMutex::ScopedLock lock(_pool.mutex); + for (;;) { - AutoPtr pN = _pTargetQueue.waitDequeueNotification(); - while (pN) + auto r = _target; + _target.reset(); + + do { - NewActionNotification::Ptr pNAN = pN.cast(); - Runnable& target = pNAN->runnable(); - _thread.setPriority(pNAN->priority()); - _thread.setName(pNAN->name()); - try + if (r.has_value()) { - target.run(); + _pool.mutex.unlock(); + try + { + r.value().get().run(); + } + catch (Exception& exc) + { + ErrorHandler::handle(exc); + } + catch (std::exception& exc) + { + ErrorHandler::handle(exc); + } + catch (...) + { + ErrorHandler::handle(); + } + ThreadLocalStorage::clear(); + _pool.mutex.lock(); } - catch (Exception& exc) - { - ErrorHandler::handle(exc); - } - catch (std::exception& exc) - { - ErrorHandler::handle(exc); - } - catch (...) + + if (_pool.runnables.empty()) { - ErrorHandler::handle(); + r.reset(); + break; } - _thread.setName(_name); - _thread.setPriority(Thread::PRIO_NORMAL); - ThreadLocalStorage::clear(); - pN = _pTargetQueue.waitDequeueNotification(1000); + + r = std::ref(_pool.runnables.pop()); + } while (true); + + _pool.waitingThreads.push_back(ActivePooledThread::Ptr{ this, true }); + registerThreadInactive(); + // wait for work, exiting after the expiry timeout is reached + _runnableReady.tryWait(_pool.mutex, _pool.expiryTimeout); + ++_pool.activeThreads; + + auto it = std::find(_pool.waitingThreads.begin(), _pool.waitingThreads.end(), ActivePooledThread::Ptr{ this, true }); + if (it != _pool.waitingThreads.end()) + { + _pool.waitingThreads.erase(it); + _pool.expiredThreads.push_back(ActivePooledThread::Ptr{ this, true }); + registerThreadInactive(); + break; + } + + if (!_pool.allThreads.count(ActivePooledThread::Ptr{ this, true })) + { + registerThreadInactive(); + break; } - _targetCompleted.set(); } - while (_needToStop == false); } -ActiveThreadPool::ActiveThreadPool(int capacity, int stackSize): - _capacity(capacity), - _serial(0), - _stackSize(stackSize), - _lastThreadIndex(0) +void ActivePooledThread::registerThreadInactive() { - poco_assert (_capacity >= 1); - - _threads.reserve(_capacity); - - for (int i = 0; i < _capacity; i++) + if (--_pool.activeThreads == 0) { - ActiveThread* pThread = createThread(); - _threads.push_back(pThread); - pThread->start(); + _pool.noActiveThreads.broadcast(); } } -ActiveThreadPool::ActiveThreadPool(std::string name, int capacity, int stackSize): - _name(std::move(name)), - _capacity(capacity), - _serial(0), - _stackSize(stackSize), - _lastThreadIndex(0) +ActiveThreadPoolPrivate::ActiveThreadPoolPrivate(int capacity, int stackSize_): + maxThreadCount(capacity), + stackSize(stackSize_) { - poco_assert (_capacity >= 1); +} - _threads.reserve(_capacity); - for (int i = 0; i < _capacity; i++) - { - ActiveThread* pThread = createThread(); - _threads.push_back(pThread); - pThread->start(); - } +ActiveThreadPoolPrivate::ActiveThreadPoolPrivate(int capacity, int stackSize_, const std::string& name_): + name(name_), + maxThreadCount(capacity), + stackSize(stackSize_) +{ } -ActiveThreadPool::~ActiveThreadPool() +ActiveThreadPoolPrivate::~ActiveThreadPoolPrivate() { - try + joinAll(); +} + + +bool ActiveThreadPoolPrivate::tryStart(Runnable& target) +{ + if (allThreads.empty()) { - stopAll(); + startThread(target); + return true; } - catch (...) + + if (activeThreadCount() >= maxThreadCount) { - poco_unexpected(); + return false; } -} + if (!waitingThreads.empty()) + { + // recycle an available thread + enqueueTask(target); + auto pThread = waitingThreads.front(); + waitingThreads.pop_front(); + pThread->notifyRunnableReady(); + return true; + } -int ActiveThreadPool::capacity() const -{ - return _capacity; -} + if (!expiredThreads.empty()) + { + // restart an expired thread + auto pThread = expiredThreads.front(); + expiredThreads.pop_front(); + ++activeThreads; -void ActiveThreadPool::start(Runnable& target) -{ - getThread()->start(Thread::PRIO_NORMAL, target); + // an expired thread must call join() before restart it, or it will cost thread leak + pThread->join(); + pThread->setRunnable(target); + pThread->start(); + return true; + } + + // start a new thread + startThread(target); + return true; } -void ActiveThreadPool::start(Runnable& target, const std::string& name) +void ActiveThreadPoolPrivate::enqueueTask(Runnable& target, int priority) { - getThread()->start(Thread::PRIO_NORMAL, target, name); + runnables.push(target, priority); } -void ActiveThreadPool::startWithPriority(Thread::Priority priority, Runnable& target) +int ActiveThreadPoolPrivate::activeThreadCount() const { - getThread()->start(priority, target); + std::size_t count = allThreads.size() - expiredThreads.size() - waitingThreads.size(); + return static_cast(count); } -void ActiveThreadPool::startWithPriority(Thread::Priority priority, Runnable& target, const std::string& name) +void ActiveThreadPoolPrivate::startThread(Runnable& target) { - getThread()->start(priority, target, name); + ActivePooledThread::Ptr pThread = new ActivePooledThread(*this); + allThreads.insert(pThread); + ++activeThreads; + pThread->setRunnable(target); + pThread->start(); } -void ActiveThreadPool::stopAll() +void ActiveThreadPoolPrivate::joinAll() { - FastMutex::ScopedLock lock(_mutex); + FastMutex::ScopedLock lock(mutex); + + do { + while (!runnables.empty() || activeThreads != 0) + { + noActiveThreads.wait(mutex); + } + + // move the contents of the set out so that we can iterate without the lock + std::set allThreadsCopy; + allThreadsCopy.swap(allThreads); + expiredThreads.clear(); + waitingThreads.clear(); + mutex.unlock(); + + for (auto pThread : allThreadsCopy) + { + if (pThread->isRunning()) + { + pThread->notifyRunnableReady(); + } + + // we must call join() before thread destruction, or it will cost thread leak + pThread->join(); + poco_assert(2 == pThread->referenceCount()); + } + + mutex.lock(); - for (auto pThread: _threads) + // More threads can be started during reset(), in that case continue + // waiting if we still have time left. + } while (!runnables.empty() || activeThreads != 0); + + while (!runnables.empty() || activeThreads != 0) { - pThread->release(); + noActiveThreads.wait(mutex); } - _threads.clear(); } -void ActiveThreadPool::joinAll() +ActiveThreadPool::ActiveThreadPool(int capacity, int stackSize): + _impl(new ActiveThreadPoolPrivate(capacity, stackSize)) { - FastMutex::ScopedLock lock(_mutex); - - for (auto pThread: _threads) - { - pThread->join(); - } +} - _threads.clear(); - _threads.reserve(_capacity); - for (int i = 0; i < _capacity; i++) - { - ActiveThread* pThread = createThread(); - _threads.push_back(pThread); - pThread->start(); - } +ActiveThreadPool::ActiveThreadPool(const std::string& name, int capacity, int stackSize): + _impl(new ActiveThreadPoolPrivate(capacity, stackSize, name)) +{ } -ActiveThread* ActiveThreadPool::getThread() + +ActiveThreadPool::~ActiveThreadPool() { - auto thrSize = _threads.size(); - auto i = (_lastThreadIndex++) % thrSize; - ActiveThread* pThread = _threads[i]; - return pThread; } -ActiveThread* ActiveThreadPool::createThread() +int ActiveThreadPool::capacity() const { - std::ostringstream name; - name << _name << "[#active-thread-" << ++_serial << "]"; - return new ActiveThread(name.str(), _stackSize); + return _impl->maxThreadCount; } -class ActiveThreadPoolSingletonHolder +void ActiveThreadPool::start(Runnable& target, int priority) { -public: - ActiveThreadPoolSingletonHolder() = default; - ~ActiveThreadPoolSingletonHolder() - { - delete _pPool; - } - ActiveThreadPool* pool() + FastMutex::ScopedLock lock(_impl->mutex); + + if (!_impl->tryStart(target)) { - FastMutex::ScopedLock lock(_mutex); + _impl->enqueueTask(target, priority); - if (!_pPool) + if (!_impl->waitingThreads.empty()) { - _pPool = new ActiveThreadPool("default-active"); + auto pThread = _impl->waitingThreads.front(); + _impl->waitingThreads.pop_front(); + pThread->notifyRunnableReady(); } - return _pPool; } +} -private: - ActiveThreadPool* _pPool{nullptr}; - FastMutex _mutex; -}; + +void ActiveThreadPool::joinAll() +{ + _impl->joinAll(); +} ActiveThreadPool& ActiveThreadPool::defaultPool() { - static ActiveThreadPoolSingletonHolder sh; - return *sh.pool(); + static ActiveThreadPool thePool; + return thePool; } +int ActiveThreadPool::getStackSize() const +{ + return _impl->stackSize; +} + + +int ActiveThreadPool::expiryTimeout() const +{ + return _impl->expiryTimeout; +} + + +void ActiveThreadPool::setExpiryTimeout(int expiryTimeout) +{ + if (_impl->expiryTimeout != expiryTimeout) + { + _impl->expiryTimeout = expiryTimeout; + } +} + + +const std::string& ActiveThreadPool::name() const +{ + return _impl->name; +} + } // namespace Poco diff --git a/Foundation/testsuite/src/ActiveThreadPoolTest.cpp b/Foundation/testsuite/src/ActiveThreadPoolTest.cpp index 1bef49d0bb..deffde56e4 100644 --- a/Foundation/testsuite/src/ActiveThreadPoolTest.cpp +++ b/Foundation/testsuite/src/ActiveThreadPoolTest.cpp @@ -16,12 +16,44 @@ #include "Poco/Exception.h" #include "Poco/Thread.h" #include "Poco/Environment.h" +#include "Poco/RefCountedObject.h" +#include "Poco/AutoPtr.h" using Poco::ActiveThreadPool; using Poco::RunnableAdapter; using Poco::Thread; using Poco::Environment; +using Poco::Runnable; +using Poco::RefCountedObject; +using Poco::AutoPtr; + + +namespace +{ + class TestPriorityRunnable: public Runnable, public RefCountedObject + { + public: + using Ptr = AutoPtr; + + TestPriorityRunnable(int n, Poco::FastMutex& mutex, std::vector& result): + _n(n), + _mutex(mutex), + _result(result) + {} + + virtual void run() override + { + Poco::FastMutex::ScopedLock lock(_mutex); + _result.push_back(_n); + } + + private: + int _n; + Poco::FastMutex& _mutex; + std::vector& _result; + }; +} ActiveThreadPoolTest::ActiveThreadPoolTest(const std::string& name): CppUnit::TestCase(name) @@ -34,14 +66,13 @@ ActiveThreadPoolTest::~ActiveThreadPoolTest() } -void ActiveThreadPoolTest::testActiveThreadPool() +void ActiveThreadPoolTest::testActiveThreadPool1() { ActiveThreadPool pool; - assertTrue (pool.capacity() == static_cast(Environment::processorCount()) + 1); - RunnableAdapter ra(*this, &ActiveThreadPoolTest::count); + _count = 0; try { for (int i = 0; i < 2000; ++i) @@ -53,9 +84,7 @@ void ActiveThreadPoolTest::testActiveThreadPool() { failmsg("wrong exception thrown"); } - pool.joinAll(); - assertTrue (_count == 2000); _count = 0; @@ -71,11 +100,86 @@ void ActiveThreadPoolTest::testActiveThreadPool() failmsg("wrong exception thrown"); } pool.joinAll(); - assertTrue (_count == 1000); } +void ActiveThreadPoolTest::testActiveThreadPool2() +{ + ActiveThreadPool pool; + RunnableAdapter ra(*this, &ActiveThreadPoolTest::count); + + pool.setExpiryTimeout(10); + assertTrue (pool.expiryTimeout() == 10); + + _count = 0; + try + { + for (int i = 0; i < pool.capacity(); ++i) + { + pool.start(ra); + } + } + catch (...) + { + failmsg("wrong exception thrown"); + } + + // wait for the threads to expire + Thread::sleep(pool.expiryTimeout() * pool.capacity()); + + try + { + for (int i = 0; i < pool.capacity(); ++i) + { + pool.start(ra); // reuse expired threads + } + } + catch (...) + { + failmsg("wrong exception thrown"); + } + + // wait for the threads to expire + Thread::sleep(pool.expiryTimeout() * pool.capacity()); + pool.joinAll(); // join with no active threads + assertTrue (_count == pool.capacity() * 2); +} + +void ActiveThreadPoolTest::testActiveThreadPool3() +{ + Poco::FastMutex mutex; + std::vector result; + ActiveThreadPool pool(1); + std::vector runnables; + + mutex.lock(); // lock, to make sure runnables are queued + try + { + for (int priority = 0; priority < 1000; ++priority) + { + TestPriorityRunnable::Ptr r = new TestPriorityRunnable(priority, mutex, result); + runnables.push_back(r); + pool.start(*r, priority); + } + } + catch (...) + { + failmsg("wrong exception thrown"); + } + mutex.unlock(); // unlock, to let runnables go + + pool.joinAll(); + std::vector mock; + mock.push_back(0); // 0 is the first result + for (int i = 999; i > 0; --i) + { + mock.push_back(i); // other results should sort by priority + } + + assertTrue (std::equal(result.begin(), result.end(), mock.begin(), mock.end())); +} + void ActiveThreadPoolTest::setUp() { _count = 0; @@ -97,7 +201,9 @@ CppUnit::Test* ActiveThreadPoolTest::suite() { CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("ActiveThreadPoolTest"); - CppUnit_addTest(pSuite, ActiveThreadPoolTest, testActiveThreadPool); + CppUnit_addTest(pSuite, ActiveThreadPoolTest, testActiveThreadPool1); + CppUnit_addTest(pSuite, ActiveThreadPoolTest, testActiveThreadPool2); + CppUnit_addTest(pSuite, ActiveThreadPoolTest, testActiveThreadPool3); return pSuite; } diff --git a/Foundation/testsuite/src/ActiveThreadPoolTest.h b/Foundation/testsuite/src/ActiveThreadPoolTest.h index 51df837355..dec9e85df8 100644 --- a/Foundation/testsuite/src/ActiveThreadPoolTest.h +++ b/Foundation/testsuite/src/ActiveThreadPoolTest.h @@ -26,7 +26,9 @@ class ActiveThreadPoolTest: public CppUnit::TestCase ActiveThreadPoolTest(const std::string& name); ~ActiveThreadPoolTest(); - void testActiveThreadPool(); + void testActiveThreadPool1(); + void testActiveThreadPool2(); + void testActiveThreadPool3(); void setUp(); void tearDown(); diff --git a/build/config/Darwin-clang-libc++ b/build/config/Darwin-clang-libc++ index bfd94fb376..07f22f2b1c 100644 --- a/build/config/Darwin-clang-libc++ +++ b/build/config/Darwin-clang-libc++ @@ -1,7 +1,7 @@ # # Darwin-clang-libc++ # -# Build settings for Mac OS X 10.11 and later (clang, libc++, x86_64/arm64) +# Build settings for Mac OS X 10.13 and later (clang, libc++, x86_64/arm64) # The build settings defined in this file are compatible # with XCode C++ projects. # @@ -13,7 +13,7 @@ LINKMODE ?= SHARED ARCHFLAGS ?= -arch $(POCO_HOST_OSARCH) SANITIZEFLAGS ?= -OSFLAGS ?= -mmacosx-version-min=10.11 -isysroot $(shell xcrun --show-sdk-path) +OSFLAGS ?= -mmacosx-version-min=10.15 -isysroot $(shell xcrun --show-sdk-path) ifeq ($(POCO_HOST_OSARCH),arm64) OPENSSL_DIR ?= /opt/homebrew/opt/openssl diff --git a/cmake/DefinePlatformSpecifc.cmake b/cmake/DefinePlatformSpecifc.cmake index 1b09993b01..380c03e571 100644 --- a/cmake/DefinePlatformSpecifc.cmake +++ b/cmake/DefinePlatformSpecifc.cmake @@ -77,6 +77,8 @@ else(BUILD_SHARED_LIBS) set(CMAKE_RELWITHDEBINFO_POSTFIX "${STATIC_POSTFIX}" CACHE STRING "Set RelWithDebInfo library postfix" FORCE) endif() +# MacOS version that has full support for C++17 +set(CMAKE_OSX_DEPLOYMENT_TARGET, 10.15) # OS Detection include(CheckTypeSize)