[geos-commits] [SCM] GEOS branch main updated. 790f14e27d734b8ed253c1238de5f3155299afcc
git at osgeo.org
git at osgeo.org
Fri Jul 11 10:31:04 PDT 2025
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GEOS".
The branch, main has been updated
via 790f14e27d734b8ed253c1238de5f3155299afcc (commit)
from b094c692d908c41a0dd1298b44557c8de24de939 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 790f14e27d734b8ed253c1238de5f3155299afcc
Author: Daniel Baston <dbaston at gmail.com>
Date: Fri Jul 11 13:30:41 2025 -0400
Add functions to interrupt processing in a specific thread/context (#803)
* Add functions to interrupt processing in a specific thread/context
- Add CurrentThreadInterrupt::interrupt() (C API: GEOS_interruptThread) to
interrupt the current thread only.
- Add CurrentThreadInterrupt::registerCallback to register an interruption
callback for the current thread only.
- Add GEOSContext_setInterruptCallback_r to associate an interruption
callback with a context handle. The callback will be registered for
the current thread each time an _r function is called with the
specified handle.
* Update context-specific interruption with @macdrevx feedback
diff --git a/capi/geos_c.h.in b/capi/geos_c.h.in
index 39671cc69..5e4a72b3c 100644
--- a/capi/geos_c.h.in
+++ b/capi/geos_c.h.in
@@ -333,10 +333,9 @@ typedef int (*GEOSTransformXYZCallback)(
/* ========== Interruption ========== */
-
/**
-* Callback function for use in interruption. The callback will be invoked _before_ checking for
-* interruption, so can be used to request it.
+* Callback function for use in interruption. The callback will be invoked at each
+* possible interruption point and can be used to request interruption.
*
* \see GEOS_interruptRegisterCallback
* \see GEOS_interruptRequest
@@ -345,21 +344,59 @@ typedef int (*GEOSTransformXYZCallback)(
typedef void (GEOSInterruptCallback)(void);
/**
-* Register a function to be called when processing is interrupted.
+* Callback function for use in interruption. The callback will be invoked at each
+* possible interruption point and can be used to request interruption by returning
+* a non-zero value.
+*
+* \see GEOSContext_setInterruptCallback_r
+* \since 3.14
+*/
+typedef int (GEOSContextInterruptCallback)(void*);
+
+/**
+* Register a function to be called when a possible interruption point is reached
+* on any thread. The function may be used to request interruption.
+*
* \param cb Callback function to invoke
-* \return the previously configured callback
+* \return the previously registered callback, or NULL
* \see GEOSInterruptCallback
+* \see GEOSContext_setInterruptCallback_r
* \since 3.4
*/
extern GEOSInterruptCallback GEOS_DLL *GEOS_interruptRegisterCallback(
GEOSInterruptCallback* cb);
/**
-* Request safe interruption of operations
+* Register a function to be called when a possible interruption point is reached
+* in code executed in the specified context. The function can interrupt the
+* thread if desired by returning True.
+*
+* \param extHandle the context returned by \ref GEOS_init_r.
+* \param cb Callback function to invoke
+* \param userData optional data to be pe provided as argument to callback
+* \return the previously registered callback, or NULL
+* \see GEOSContextInterruptCallback
+* \since 3.14
+*/
+extern GEOSContextInterruptCallback GEOS_DLL *GEOSContext_setInterruptCallback_r(
+ GEOSContextHandle_t extHandle,
+ GEOSContextInterruptCallback* cb,
+ void* userData);
+
+/**
+* Request safe interruption of operations. The next thread to check for an
+* interrupt will be interrupted. To request interruption of a specific thread,
+* instead call GEOS_interruptThread() from a callback executed by that thread.
* \since 3.4
*/
extern void GEOS_DLL GEOS_interruptRequest(void);
+/**
+* Interrupt the current thread.
+* \since 3.14
+*/
+extern void GEOS_DLL GEOS_interruptThread(void);
+
/**
* Cancel a pending interruption request
* \since 3.4
diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp
index 27ee5b812..bf6cebebc 100644
--- a/capi/geos_ts_c.cpp
+++ b/capi/geos_ts_c.cpp
@@ -242,6 +242,8 @@ typedef struct GEOSContextHandle_HS {
void* noticeData;
GEOSMessageHandler errorMessageOld;
GEOSMessageHandler_r errorMessageNew;
+ GEOSContextInterruptCallback* interrupt_cb;
+ void* interrupt_cb_data;
void* errorData;
uint8_t WKBOutputDims;
int WKBByteOrder;
@@ -256,6 +258,8 @@ typedef struct GEOSContextHandle_HS {
noticeData(nullptr),
errorMessageOld(nullptr),
errorMessageNew(nullptr),
+ interrupt_cb(nullptr),
+ interrupt_cb_data(nullptr),
errorData(nullptr),
point2d(nullptr)
{
@@ -313,6 +317,15 @@ typedef struct GEOSContextHandle_HS {
return f;
}
+ GEOSContextInterruptCallback*
+ setInterruptHandler(GEOSContextInterruptCallback* cb, void* userData)
+ {
+ auto old = interrupt_cb;
+ interrupt_cb = cb;
+ interrupt_cb_data = userData;
+ return old;
+ }
+
void
NOTICE_MESSAGE(GEOS_PRINTF_FORMAT const char *fmt, ...) GEOS_PRINTF_FORMAT_ATTR(2, 3)
{
@@ -427,12 +440,37 @@ gstrdup(std::string const& str)
return gstrdup_s(str.c_str(), str.size());
}
+struct InterruptManager {
+ InterruptManager(GEOSContextHandle_t handle) :
+ cb(handle->interrupt_cb),
+ cb_data(handle->interrupt_cb_data) {
+ if (cb) {
+ geos::util::CurrentThreadInterrupt::registerCallback(cb, cb_data);
+ }
+ }
+
+ ~InterruptManager() {
+ if (cb != nullptr) {
+ geos::util::CurrentThreadInterrupt::registerCallback(nullptr, nullptr);
+ }
+ }
+
+ GEOSContextInterruptCallback* cb;
+ void* cb_data;
+};
+
+struct NotInterruptible {
+ NotInterruptible(GEOSContextHandle_t handle) {
+ (void) handle;
+ }
+};
+
} // namespace anonymous
// Execute a lambda, using the given context handle to process errors.
// Return errval on error.
// Errval should be of the type returned by f, unless f returns a bool in which case we promote to char.
-template<typename F>
+template<typename InterruptManagerType=InterruptManager, typename F>
inline auto execute(
GEOSContextHandle_t extHandle,
typename std::conditional<std::is_same<decltype(std::declval<F>()()),bool>::value,
@@ -448,6 +486,8 @@ inline auto execute(
return errval;
}
+ InterruptManagerType ic(handle);
+
try {
return f();
} catch (const std::exception& e) {
@@ -461,7 +501,7 @@ inline auto execute(
// Execute a lambda, using the given context handle to process errors.
// Return nullptr on error.
-template<typename F, typename std::enable_if<!std::is_void<decltype(std::declval<F>()())>::value, std::nullptr_t>::type = nullptr>
+template<typename InterruptManagerType=InterruptManager, typename F, typename std::enable_if<!std::is_void<decltype(std::declval<F>()())>::value, std::nullptr_t>::type = nullptr>
inline auto execute(GEOSContextHandle_t extHandle, F&& f) -> decltype(f()) {
if (extHandle == nullptr) {
throw std::runtime_error("context handle is uninitialized, call initGEOS");
@@ -472,6 +512,8 @@ inline auto execute(GEOSContextHandle_t extHandle, F&& f) -> decltype(f()) {
return nullptr;
}
+ InterruptManagerType ic(handle);
+
try {
return f();
} catch (const std::exception& e) {
@@ -485,9 +527,14 @@ inline auto execute(GEOSContextHandle_t extHandle, F&& f) -> decltype(f()) {
// Execute a lambda, using the given context handle to process errors.
// No return value.
-template<typename F, typename std::enable_if<std::is_void<decltype(std::declval<F>()())>::value, std::nullptr_t>::type = nullptr>
+template<typename InterruptManagerType=InterruptManager, typename F, typename std::enable_if<std::is_void<decltype(std::declval<F>()())>::value, std::nullptr_t>::type = nullptr>
inline void execute(GEOSContextHandle_t extHandle, F&& f) {
GEOSContextHandleInternal_t* handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
+
+ if (handle != nullptr) {
+ InterruptManagerType ic(handle);
+ }
+
try {
f();
} catch (const std::exception& e) {
@@ -566,6 +613,17 @@ extern "C" {
return handle->setErrorHandler(ef, userData);
}
+ GEOSContextInterruptCallback*
+ GEOSContext_setInterruptCallback_r(GEOSContextHandle_t extHandle, GEOSContextInterruptCallback* cb, void* userData)
+ {
+ GEOSContextHandleInternal_t* handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
+ if(0 == handle->initialized) {
+ return nullptr;
+ }
+
+ return handle->setInterruptHandler(cb, userData);
+ }
+
void
finishGEOS_r(GEOSContextHandle_t extHandle)
{
@@ -939,7 +997,7 @@ extern "C" {
int
GEOSArea_r(GEOSContextHandle_t extHandle, const Geometry* g, double* area)
{
- return execute(extHandle, 0, [&]() {
+ return execute<NotInterruptible>(extHandle, 0, [&]() {
*area = g->getArea();
return 1;
});
@@ -948,7 +1006,7 @@ extern "C" {
int
GEOSLength_r(GEOSContextHandle_t extHandle, const Geometry* g, double* length)
{
- return execute(extHandle, 0, [&]() {
+ return execute<NotInterruptible>(extHandle, 0, [&]() {
*length = g->getLength();
return 1;
});
@@ -1877,7 +1935,7 @@ extern "C" {
int
GEOSGetNumInteriorRings_r(GEOSContextHandle_t extHandle, const Geometry* g1)
{
- return execute(extHandle, -1, [&]() {
+ return execute<NotInterruptible>(extHandle, -1, [&]() {
const Surface* p = dynamic_cast<const Surface*>(g1);
if(!p) {
throw IllegalArgumentException("Argument is not a Surface");
@@ -1891,7 +1949,7 @@ extern "C" {
int
GEOSGetNumGeometries_r(GEOSContextHandle_t extHandle, const Geometry* g1)
{
- return execute(extHandle, -1, [&]() {
+ return execute<NotInterruptible>(extHandle, -1, [&]() {
return static_cast<int>(g1->getNumGeometries());
});
}
@@ -1904,7 +1962,7 @@ extern "C" {
const Geometry*
GEOSGetGeometryN_r(GEOSContextHandle_t extHandle, const Geometry* g1, int n)
{
- return execute(extHandle, [&]() {
+ return execute<NotInterruptible>(extHandle, [&]() {
if(n < 0) {
throw IllegalArgumentException("Index must be non-negative.");
}
@@ -2096,7 +2154,7 @@ extern "C" {
const Geometry*
GEOSGetExteriorRing_r(GEOSContextHandle_t extHandle, const Geometry* g1)
{
- return execute(extHandle, [&]() {
+ return execute<NotInterruptible>(extHandle, [&]() {
const Surface* p = dynamic_cast<const Surface*>(g1);
if(!p) {
throw IllegalArgumentException("Invalid argument (must be a Surface)");
@@ -2112,7 +2170,7 @@ extern "C" {
const Geometry*
GEOSGetInteriorRingN_r(GEOSContextHandle_t extHandle, const Geometry* g1, int n)
{
- return execute(extHandle, [&]() {
+ return execute<NotInterruptible>(extHandle, [&]() {
const Surface* p = dynamic_cast<const Surface*>(g1);
if(!p) {
throw IllegalArgumentException("Invalid argument (must be a Surface)");
@@ -2881,7 +2939,7 @@ extern "C" {
GEOSCoordSeq_setOrdinate_r(GEOSContextHandle_t extHandle, CoordinateSequence* cs,
unsigned int idx, unsigned int dim, double val)
{
- return execute(extHandle, 0, [&]() {
+ return execute<NotInterruptible>(extHandle, 0, [&]() {
cs->setOrdinate(idx, dim, val);
return 1;
});
@@ -2914,7 +2972,7 @@ extern "C" {
int
GEOSCoordSeq_setXY_r(GEOSContextHandle_t extHandle, CoordinateSequence* cs, unsigned int idx, double x, double y)
{
- return execute(extHandle, 0, [&]() {
+ return execute<NotInterruptible>(extHandle, 0, [&]() {
cs->setAt(CoordinateXY{x, y}, idx);
return 1;
});
@@ -2923,7 +2981,7 @@ extern "C" {
int
GEOSCoordSeq_setXYZ_r(GEOSContextHandle_t extHandle, CoordinateSequence* cs, unsigned int idx, double x, double y, double z)
{
- return execute(extHandle, 0, [&]() {
+ return execute<NotInterruptible>(extHandle, 0, [&]() {
cs->setAt(Coordinate{x, y, z}, idx);
return 1;
});
@@ -2941,7 +2999,7 @@ extern "C" {
GEOSCoordSeq_getOrdinate_r(GEOSContextHandle_t extHandle, const CoordinateSequence* cs,
unsigned int idx, unsigned int dim, double* val)
{
- return execute(extHandle, 0, [&]() {
+ return execute<NotInterruptible>(extHandle, 0, [&]() {
*val = cs->getOrdinate(idx, dim);
return 1;
});
@@ -2974,7 +3032,7 @@ extern "C" {
int
GEOSCoordSeq_getXY_r(GEOSContextHandle_t extHandle, const CoordinateSequence* cs, unsigned int idx, double* x, double* y)
{
- return execute(extHandle, 0, [&]() {
+ return execute<NotInterruptible>(extHandle, 0, [&]() {
auto& c = cs->getAt<CoordinateXY>(idx);
*x = c.x;
*y = c.y;
@@ -2985,7 +3043,7 @@ extern "C" {
int
GEOSCoordSeq_getXYZ_r(GEOSContextHandle_t extHandle, const CoordinateSequence* cs, unsigned int idx, double* x, double* y, double* z)
{
- return execute(extHandle, 0, [&]() {
+ return execute<NotInterruptible>(extHandle, 0, [&]() {
auto& c = cs->getAt(idx);
*x = c.x;
*y = c.y;
@@ -2997,7 +3055,7 @@ extern "C" {
int
GEOSCoordSeq_getSize_r(GEOSContextHandle_t extHandle, const CoordinateSequence* cs, unsigned int* size)
{
- return execute(extHandle, 0, [&]() {
+ return execute<NotInterruptible>(extHandle, 0, [&]() {
const std::size_t sz = cs->getSize();
*size = static_cast<unsigned int>(sz);
return 1;
@@ -3007,7 +3065,7 @@ extern "C" {
int
GEOSCoordSeq_getDimensions_r(GEOSContextHandle_t extHandle, const CoordinateSequence* cs, unsigned int* dims)
{
- return execute(extHandle, 0, [&]() {
+ return execute<NotInterruptible>(extHandle, 0, [&]() {
const std::size_t dim = cs->getDimension();
*dims = static_cast<unsigned int>(dim);
diff --git a/include/geos/util/Interrupt.h b/include/geos/util/Interrupt.h
index e52386d41..5c8da22d2 100644
--- a/include/geos/util/Interrupt.h
+++ b/include/geos/util/Interrupt.h
@@ -19,8 +19,6 @@
namespace geos {
namespace util { // geos::util
-#define GEOS_CHECK_FOR_INTERRUPTS() geos::util::Interrupt::process()
-
/** \brief Used to manage interruption requests and callbacks. */
class GEOS_DLL Interrupt {
@@ -32,7 +30,8 @@ public:
* Request interruption of operations
*
* Operations will be terminated by a GEOSInterrupt
- * exception at first occasion.
+ * exception at first occasion, by the first thread
+ * to check for an interrupt request.
*/
static void request();
@@ -43,14 +42,14 @@ public:
static bool check();
/** \brief
- * Register a callback that will be invoked
+ * Register a callback that will be invoked by all threads
* before checking for interruption requests.
*
* NOTE that interruption request checking may happen
- * frequently so any callback would better be quick.
+ * frequently so the callback should execute quickly.
*
* The callback can be used to call Interrupt::request()
- *
+ * or Interrupt::requestForCurrentThread().
*/
static Callback* registerCallback(Callback* cb);
@@ -65,7 +64,26 @@ public:
};
+class GEOS_DLL CurrentThreadInterrupt {
+public:
+ typedef int (ThreadCallback)(void*);
+
+ /** \brief
+ * Register a callback that will be invoked by the current thread
+ * to check if it should be interrupted. If the callback returns
+ * True, the thread will be interrupted. The previously registered
+ * callback, if any, will be returned.
+ */
+ static ThreadCallback* registerCallback(ThreadCallback* cb, void* data);
+
+ static void process();
+
+ static void interrupt();
+};
+
} // namespace geos::util
} // namespace geos
+
+inline void GEOS_CHECK_FOR_INTERRUPTS() { geos::util::Interrupt::process(); geos::util::CurrentThreadInterrupt::process(); }
diff --git a/src/util/Interrupt.cpp b/src/util/Interrupt.cpp
index 0bc988221..a56fd8229 100644
--- a/src/util/Interrupt.cpp
+++ b/src/util/Interrupt.cpp
@@ -16,10 +16,15 @@
#include <geos/util/GEOSException.h> // for inheritance
namespace {
-/* Could these be portably stored in thread-specific space ? */
+
+// Callback and request status for interruption of any single thread
+geos::util::Interrupt::Callback* callback = nullptr;
bool requested = false;
-geos::util::Interrupt::Callback* callback = nullptr;
+// Callback for interruption of the current thread
+thread_local geos::util::CurrentThreadInterrupt::ThreadCallback* callback_thread = nullptr;
+thread_local void* callback_thread_data = nullptr;
+
}
namespace geos {
@@ -63,8 +68,7 @@ Interrupt::process()
if(callback) {
(*callback)();
}
- if(requested) {
- requested = false;
+ if(check()) {
interrupt();
}
}
@@ -77,6 +81,25 @@ Interrupt::interrupt()
throw InterruptedException();
}
+CurrentThreadInterrupt::ThreadCallback*
+CurrentThreadInterrupt::registerCallback(ThreadCallback *cb, void *data) {
+ auto* prev = callback_thread;
+ callback_thread = cb;
+ callback_thread_data = data;
+ return prev;
+}
+
+void
+CurrentThreadInterrupt::process() {
+ if (callback_thread && (*callback_thread)(callback_thread_data)) {
+ interrupt();
+ }
+}
+
+void
+CurrentThreadInterrupt::interrupt() {
+ throw InterruptedException();
+}
} // namespace geos::util
} // namespace geos
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
index d54d82b9c..b28795cbc 100644
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -31,6 +31,7 @@ foreach(_testfile ${_testfiles})
string(CONCAT _testname "geos::" ${_testname})
endif()
add_test(NAME unit-${_cmake_testname} COMMAND test_geos_unit ${_testname})
+ set_tests_properties(unit-${_cmake_testname} PROPERTIES TIMEOUT 30)
endforeach()
# Run all the unit tests in one go, for faster memory checking
diff --git a/tests/unit/capi/GEOSInterruptTest.cpp b/tests/unit/capi/GEOSInterruptTest.cpp
index 0843d1bea..facb017cc 100644
--- a/tests/unit/capi/GEOSInterruptTest.cpp
+++ b/tests/unit/capi/GEOSInterruptTest.cpp
@@ -1,9 +1,11 @@
-//
-// Test Suite for C-API custom allocators
+// Test Suite for C-API interrupt functions
#include <tut/tut.hpp>
// geos
#include <geos_c.h>
+#include <geos/util/Interrupt.h>
+// std
+#include <thread>
#include "capi_test_utils.h"
@@ -15,6 +17,7 @@ namespace tut {
// Common data used in test cases.
struct test_capiinterrupt_data : public capitest::utility {
static int numcalls;
+ static int maxcalls;
static GEOSInterruptCallback* nextcb;
static void
@@ -32,9 +35,16 @@ struct test_capiinterrupt_data : public capitest::utility {
}
}
+ static int
+ interruptAfterMaxCalls(void* data)
+ {
+ return ++*static_cast<int*>(data) >= maxcalls;
+ }
+
};
int test_capiinterrupt_data::numcalls = 0;
+int test_capiinterrupt_data::maxcalls = 0;
GEOSInterruptCallback* test_capiinterrupt_data::nextcb = nullptr;
typedef test_group<test_capiinterrupt_data> group;
@@ -66,7 +76,7 @@ void object::test<1>
GEOSGeometry* geom2 = GEOSBuffer(geom1, 1, 8);
- ensure("GEOSBufferWithStyle failed", nullptr != geom2);
+ ensure("GEOSBuffer failed", nullptr != geom2);
ensure("interrupt callback never called", numcalls > 0);
@@ -79,7 +89,7 @@ void object::test<1>
finishGEOS();
}
-/// Test interrupt callback being called XXX
+/// Test interrupt callback being called
template<>
template<>
void object::test<2>
@@ -197,5 +207,55 @@ void object::test<5>
}
+// Test callback is thread-local
+template<>
+template<>
+void object::test<6>
+()
+{
+ using geos::util::CurrentThreadInterrupt;
+
+ maxcalls = 3;
+ int calls_1 = 0;
+ int calls_2 = 0;
+
+ GEOSContextHandle_t h1 = initGEOS_r(notice, notice);
+ GEOSContextHandle_t h2 = initGEOS_r(notice, notice);
+
+ GEOSContext_setInterruptCallback_r(h1, interruptAfterMaxCalls, &calls_1);
+ GEOSContext_setInterruptCallback_r(h2, interruptAfterMaxCalls, &calls_2);
+
+ // get previously registered callback and verify there was none
+ // (the context registered its callback only when invoking a function)
+ ensure(CurrentThreadInterrupt::registerCallback(nullptr, nullptr) == nullptr);
+
+ auto buffer = [](GEOSContextHandle_t handle) {
+ GEOSWKTReader* reader = GEOSWKTReader_create_r(handle);
+ GEOSGeometry* geom1 = GEOSWKTReader_read_r(handle, reader, "LINESTRING (0 0, 1 0)");
+ GEOSGeometry* geom2 = GEOSBuffer_r(handle, geom1, 1, 8);
+
+ GEOSGeom_destroy_r(handle, geom2);
+ GEOSGeom_destroy_r(handle, geom1);
+ GEOSWKTReader_destroy_r(handle, reader);
+ };
+
+ std::thread t1(buffer, h1);
+ std::thread t2(buffer, h2);
+
+ t1.join();
+ t2.join();
+
+ ensure_equals(calls_1, maxcalls);
+ ensure_equals(calls_2, maxcalls);
+
+ // get previously registered callback and verify there was none
+ // (context unregistered its callback after completing GEOSBuffer)
+ ensure(CurrentThreadInterrupt::registerCallback(nullptr, nullptr) == nullptr);
+
+ finishGEOS_r(h1);
+ finishGEOS_r(h2);
+}
+
+
} // namespace tut
diff --git a/tests/unit/util/InterruptTest.cpp b/tests/unit/util/InterruptTest.cpp
new file mode 100644
index 000000000..caeb96581
--- /dev/null
+++ b/tests/unit/util/InterruptTest.cpp
@@ -0,0 +1,150 @@
+// tut
+#include <tut/tut.hpp>
+// geos
+#include <geos/util/Interrupt.h>
+// std
+#include <chrono>
+#include <functional>
+#include <thread>
+
+using geos::util::Interrupt;
+using geos::util::CurrentThreadInterrupt;
+
+namespace tut {
+//
+// Test Group
+//
+
+// Common data used in test cases.
+struct test_interrupt_data {
+ static void workForever() {
+ try {
+ //std::cerr << "Started " << std::this_thread::get_id() << "." << std::endl;
+ while (true) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ GEOS_CHECK_FOR_INTERRUPTS();
+ }
+ } catch (const std::exception&) {
+ //std::cerr << "Interrupted " << std::this_thread::get_id() << "." << std::endl;
+ }
+ }
+
+ static void interruptNow() {
+ Interrupt::request();
+ }
+
+ static std::map<std::thread::id, bool>* toInterrupt;
+
+ static void interruptIfRequested() {
+ if (toInterrupt == nullptr) {
+ return;
+ }
+
+ auto it = toInterrupt->find(std::this_thread::get_id());
+ if (it != toInterrupt->end() && it->second) {
+ it->second = false;
+ CurrentThreadInterrupt::interrupt();
+ }
+ }
+
+ static bool interruptCurrentThreadIfRequested() {
+ if (toInterrupt == nullptr) {
+ return false;
+ }
+
+ auto it = toInterrupt->find(std::this_thread::get_id());
+ if (it != toInterrupt->end() && it->second) {
+ it->second = false;
+ return true;
+ }
+
+ return false;
+ }
+};
+
+std::map<std::thread::id, bool>* test_interrupt_data::toInterrupt = nullptr;
+
+typedef test_group<test_interrupt_data> group;
+typedef group::object object;
+
+group test_interrupt_group("geos::util::Interrupt");
+
+//
+// Test Cases
+//
+
+
+// Interrupt worker thread via global request from main thead
+template<>
+template<>
+void object::test<1>
+()
+{
+ std::thread t(workForever);
+ Interrupt::request();
+
+ t.join();
+}
+
+// Interrupt worker thread via thread-specific request from worker thread using a callback
+template<>
+template<>
+void object::test<2>
+()
+{
+ Interrupt::registerCallback(interruptIfRequested);
+
+ std::thread t1(workForever);
+ std::thread t2(workForever);
+
+ // Create map and add entries before exposing it to the interrupt
+ // callback that will be accessed from multiple threads. It's OK
+ // for multiple threads to modify entries in the map but not for
+ // multiple threads to create entries.
+ std::map<std::thread::id, bool> shouldInterrupt;
+ shouldInterrupt[t1.get_id()] = false;
+ shouldInterrupt[t2.get_id()] = false;
+ toInterrupt = &shouldInterrupt;
+
+ shouldInterrupt[t2.get_id()] = true;
+ t2.join();
+
+ shouldInterrupt[t1.get_id()] = true;
+ t1.join();
+}
+
+// Register separate callbacks for each thread. Each callback will
+// request interruption of itself only.
+template<>
+template<>
+void object::test<3>
+()
+{
+ bool interrupt1 = false;
+ int numCalls2 = 0;
+
+ auto cb1 = ([](void* data) -> int {
+ return *static_cast<bool*>(data);
+ });
+
+ auto cb2 = ([](void* data) -> int {
+ return ++*static_cast<int*>(data) > 5;
+ });
+
+
+ std::thread t1([&cb1, &interrupt1]() {
+ CurrentThreadInterrupt::registerCallback(cb1, &interrupt1);
+ });
+
+ std::thread t2([&cb2, &numCalls2]() {
+ CurrentThreadInterrupt::registerCallback(cb2, &numCalls2);
+ });
+
+ t2.join();
+
+ interrupt1 = true;
+ t1.join();
+}
+
+} // namespace tut
+
-----------------------------------------------------------------------
Summary of changes:
capi/geos_c.h.in | 49 +++++++++--
capi/geos_ts_c.cpp | 94 +++++++++++++++++----
include/geos/util/Interrupt.h | 30 +++++--
src/util/Interrupt.cpp | 31 ++++++-
tests/unit/CMakeLists.txt | 1 +
tests/unit/capi/GEOSInterruptTest.cpp | 68 ++++++++++++++-
tests/unit/util/InterruptTest.cpp | 150 ++++++++++++++++++++++++++++++++++
7 files changed, 385 insertions(+), 38 deletions(-)
create mode 100644 tests/unit/util/InterruptTest.cpp
hooks/post-receive
--
GEOS
More information about the geos-commits
mailing list