Skip to content

Commit a31dbe6

Browse files
authored
fix(Thread): fix Thread reuse error, add thread interrupt feature (#4942)
* enh(Thread): add thread interrupt feature * fix(Thread): fix Thread reuse error
1 parent e6f661d commit a31dbe6

8 files changed

Lines changed: 162 additions & 2 deletions

File tree

Foundation/include/Poco/Exception.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ POCO_DECLARE_EXCEPTION(Foundation_API, RegularExpressionException, RuntimeExcept
230230
POCO_DECLARE_EXCEPTION(Foundation_API, LibraryLoadException, RuntimeException)
231231
POCO_DECLARE_EXCEPTION(Foundation_API, LibraryAlreadyLoadedException, RuntimeException)
232232
POCO_DECLARE_EXCEPTION(Foundation_API, NoThreadAvailableException, RuntimeException)
233+
POCO_DECLARE_EXCEPTION(Foundation_API, ThreadInterruptedException, RuntimeException)
233234
POCO_DECLARE_EXCEPTION(Foundation_API, PropertyNotSupportedException, RuntimeException)
234235
POCO_DECLARE_EXCEPTION(Foundation_API, PoolOverflowException, RuntimeException)
235236
POCO_DECLARE_EXCEPTION(Foundation_API, NoPermissionException, RuntimeException)

Foundation/include/Poco/Thread.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,35 @@ class Foundation_API Thread: private ThreadImpl
256256
/// Negative value means the thread has
257257
/// no CPU core affinity.
258258

259+
bool isInterrupted();
260+
/// Tests whether current thread has been interrupted.
261+
/// Return true if the task running on this thread should be stopped.
262+
/// An interruption can be requested by interrupt().
263+
///
264+
/// This function can be used to make long running tasks cleanly interruptible.
265+
/// Never checking or acting on the value returned by this function is safe,
266+
/// however it is advisable do so regularly in long running functions.
267+
/// Take care not to call it too often, to keep the overhead low.
268+
///
269+
/// See also checkInterrupted().
270+
271+
void checkInterrupted();
272+
/// Tests whether current thread has been interrupted.
273+
/// Throws Poco::ThreadInterruptedException if isInterrupted() return true.
274+
///
275+
/// Note: The interrupted status of the thread is cleared by this method.
276+
277+
void interrupt();
278+
/// Interrupts this thread.
279+
///
280+
/// This function does not stop any event loop running on the thread and
281+
/// does not terminate it in any way.
282+
///
283+
/// See also isInterrupted().
284+
285+
void clearInterrupt();
286+
/// Clear the the interrupted status.
287+
259288
protected:
260289
ThreadLocalStorage& tls();
261290
/// Returns a reference to the thread's local storage.
@@ -301,6 +330,7 @@ class Foundation_API Thread: private ThreadImpl
301330
int _id;
302331
ThreadLocalStorage* _pTLS;
303332
Event _event;
333+
std::atomic_bool _interruptionRequested;
304334

305335
friend class ThreadLocalStorage;
306336
friend class PooledThread;

Foundation/src/Exception.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ POCO_IMPLEMENT_EXCEPTION(RegularExpressionException, RuntimeException, "Error in
176176
POCO_IMPLEMENT_EXCEPTION(LibraryLoadException, RuntimeException, "Cannot load library")
177177
POCO_IMPLEMENT_EXCEPTION(LibraryAlreadyLoadedException, RuntimeException, "Library already loaded")
178178
POCO_IMPLEMENT_EXCEPTION(NoThreadAvailableException, RuntimeException, "No thread available")
179+
POCO_IMPLEMENT_EXCEPTION(ThreadInterruptedException, RuntimeException, "Thread interrupted")
179180
POCO_IMPLEMENT_EXCEPTION(PropertyNotSupportedException, RuntimeException, "Property not supported")
180181
POCO_IMPLEMENT_EXCEPTION(PoolOverflowException, RuntimeException, "Pool overflow")
181182
POCO_IMPLEMENT_EXCEPTION(NoPermissionException, RuntimeException, "No permission")

Foundation/src/Thread.cpp

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ class CallableHolder: public Runnable
8585
Thread::Thread(uint32_t sigMask):
8686
_id(uniqueId()),
8787
_pTLS(nullptr),
88-
_event(Event::EVENT_AUTORESET)
88+
_event(Event::EVENT_AUTORESET),
89+
_interruptionRequested(false)
8990
{
9091
setNameImpl(makeName());
9192
#if defined(POCO_OS_FAMILY_UNIX)
@@ -97,7 +98,8 @@ Thread::Thread(uint32_t sigMask):
9798
Thread::Thread(const std::string& name, uint32_t sigMask):
9899
_id(uniqueId()),
99100
_pTLS(nullptr),
100-
_event(Event::EVENT_AUTORESET)
101+
_event(Event::EVENT_AUTORESET),
102+
_interruptionRequested(false)
101103
{
102104
setNameImpl(name);
103105
#if defined(POCO_OS_FAMILY_UNIX)
@@ -175,6 +177,35 @@ void Thread::wakeUp()
175177
}
176178

177179

180+
bool Thread::isInterrupted()
181+
{
182+
return _interruptionRequested.load(std::memory_order_relaxed);
183+
}
184+
185+
186+
void Thread::checkInterrupted()
187+
{
188+
bool expected = true;
189+
if (_interruptionRequested.compare_exchange_strong(expected, false))
190+
{
191+
throw Poco::ThreadInterruptedException("Thread interrupted");
192+
}
193+
}
194+
195+
196+
void Thread::interrupt()
197+
{
198+
_interruptionRequested.store(true, std::memory_order_relaxed);
199+
wakeUp();
200+
}
201+
202+
203+
void Thread::clearInterrupt()
204+
{
205+
_interruptionRequested.store(false, std::memory_order_relaxed);
206+
}
207+
208+
178209
ThreadLocalStorage& Thread::tls()
179210
{
180211
if (!_pTLS)

Foundation/src/Thread_POSIX.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ void ThreadImpl::startImpl(SharedPtr<Runnable> pTarget)
326326
{
327327
FastMutex::ScopedLock l(_pData->mutex);
328328
_pData->pRunnableTarget = pTarget;
329+
_pData->done.reset();
329330
int errorCode;
330331
if ((errorCode = pthread_create(&_pData->thread, &attributes, runnableEntry, this)))
331332
{

Foundation/src/Thread_VX.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ void ThreadImpl::startImpl(Runnable& target)
123123
throw SystemException("thread already running");
124124

125125
_pData->pRunnableTarget = &target;
126+
_pData->done.reset();
126127

127128
int stackSize = _pData->stackSize == 0 ? DEFAULT_THREAD_STACK_SIZE : _pData->stackSize;
128129
int id = taskSpawn(NULL, _pData->osPrio, VX_FP_TASK, stackSize, reinterpret_cast<FUNCPTR>(runnableEntry), reinterpret_cast<int>(this), 0, 0, 0, 0, 0, 0, 0, 0, 0);
@@ -143,6 +144,7 @@ void ThreadImpl::startImpl(Callable target, void* pData)
143144

144145
_pData->pCallbackTarget->callback = target;
145146
_pData->pCallbackTarget->pData = pData;
147+
_pData->done.reset();
146148

147149
int stackSize = _pData->stackSize == 0 ? DEFAULT_THREAD_STACK_SIZE : _pData->stackSize;
148150
int id = taskSpawn(NULL, _pData->osPrio, VX_FP_TASK, stackSize, reinterpret_cast<FUNCPTR>(callableEntry), reinterpret_cast<int>(this), 0, 0, 0, 0, 0, 0, 0, 0, 0);

Foundation/testsuite/src/ThreadTest.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,63 @@ class TrySleepRunnable : public Runnable
192192
};
193193

194194

195+
class InterruptionRunnable : public Runnable
196+
{
197+
public:
198+
virtual void run() override
199+
{
200+
_sleep = !Thread::trySleep(300000);
201+
_interrupted = Thread::current()->isInterrupted();
202+
203+
try
204+
{
205+
Thread::current()->checkInterrupted();
206+
}
207+
catch (const Poco::ThreadInterruptedException&)
208+
{
209+
_exception = true;
210+
}
211+
212+
// interrupt state should be cleared
213+
if (!Thread::current()->isInterrupted())
214+
{
215+
_interruptCleared = true;
216+
}
217+
218+
// interrupt state should be cleared
219+
try
220+
{
221+
Thread::current()->checkInterrupted();
222+
_exceptionCleared = true;
223+
}
224+
catch (const Poco::ThreadInterruptedException&)
225+
{
226+
_exceptionCleared = false;
227+
}
228+
}
229+
230+
bool isTestOK() const
231+
{
232+
if (_sleep &&
233+
_interrupted &&
234+
_exception &&
235+
_interruptCleared &&
236+
_exceptionCleared)
237+
{
238+
return true;
239+
}
240+
return false;
241+
}
242+
243+
private:
244+
bool _sleep = false;
245+
bool _interrupted = false;
246+
bool _exception = false;
247+
bool _interruptCleared = false;
248+
bool _exceptionCleared = false;
249+
};
250+
251+
195252
ThreadTest::ThreadTest(const std::string& name): CppUnit::TestCase(name)
196253
{
197254
}
@@ -553,6 +610,41 @@ void ThreadTest::testAffinity()
553610
}
554611

555612

613+
void ThreadTest::testInterrupt()
614+
{
615+
Thread thread;
616+
617+
for (int i = 0; i < 2; i++)
618+
{
619+
InterruptionRunnable r;
620+
621+
thread.start(r);
622+
Thread::sleep(200);
623+
assertTrue (thread.isRunning());
624+
assertTrue (!thread.tryJoin(100));
625+
626+
// interrupt
627+
thread.interrupt();
628+
thread.join();
629+
630+
// clear the interrupt state to re-use the thread
631+
thread.clearInterrupt();
632+
assertTrue (!thread.isInterrupted());
633+
634+
try
635+
{
636+
thread.checkInterrupted();
637+
}
638+
catch (const std::exception&)
639+
{
640+
assertTrue (false);
641+
}
642+
643+
assertTrue (r.isTestOK());
644+
}
645+
}
646+
647+
556648
void ThreadTest::setUp()
557649
{
558650
}
@@ -583,6 +675,7 @@ CppUnit::Test* ThreadTest::suite()
583675
CppUnit_addTest(pSuite, ThreadTest, testThreadStackSize);
584676
CppUnit_addTest(pSuite, ThreadTest, testSleep);
585677
CppUnit_addTest(pSuite, ThreadTest, testAffinity);
678+
CppUnit_addTest(pSuite, ThreadTest, testInterrupt);
586679

587680
return pSuite;
588681
}

Foundation/testsuite/src/ThreadTest.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class ThreadTest: public CppUnit::TestCase
4040
void testThreadStackSize();
4141
void testSleep();
4242
void testAffinity();
43+
void testInterrupt();
4344

4445
void setUp();
4546
void tearDown();

0 commit comments

Comments
 (0)