diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f4f5c8c56..e560572729 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,12 +124,14 @@ option(GECODE_ENABLE_FLATZINC "Build FlatZinc module" ON) option(GECODE_ENABLE_MPFR "Enable MPFR support" ON) option(GECODE_ENABLE_ALLOCATOR "Enable default allocator" ON) option(GECODE_ENABLE_AUDIT "Enable audit code" OFF) +option(GECODE_ENABLE_FAULT_INJECTION "Enable deterministic test failpoints" OFF) option(GECODE_ENABLE_GCC_VISIBILITY "Enable GCC visibility attributes" ON) option(GECODE_ENABLE_OSX_UNFAIR_MUTEX "Enable macOS unfair mutexes" ON) option(GECODE_REGENERATE_VARIMP "Regenerate checked-in var-imp headers during build" OFF) option(GECODE_DOC_DOT "Enable Graphviz dot graphs in Doxygen documentation" ON) option(GECODE_DOC_SEARCH "Enable the Doxygen documentation search engine" OFF) option(GECODE_DOC_TAGFILE "Generate the Doxygen tag file" ON) +set(GECODE_SANITIZER "" CACHE STRING "Sanitizer instrumentation: empty, address, undefined, address-undefined, or thread") set(GECODE_FREELIST32_SIZE_MAX "" CACHE STRING "Max freelist size on 32-bit platforms") set(GECODE_FREELIST64_SIZE_MAX "" CACHE STRING "Max freelist size on 64-bit platforms") set(GECODE_WITH_VIS "" CACHE STRING "Additional .vis files (comma-separated)") @@ -199,6 +201,37 @@ function(gecode_force_option_on option reason) endif() endfunction() +string(TOLOWER "${GECODE_SANITIZER}" GECODE_SANITIZER_NORMALIZED) +set(GECODE_EFFECTIVE_ENABLE_GCC_VISIBILITY ${GECODE_ENABLE_GCC_VISIBILITY}) +set(GECODE_SANITIZER_COMPILE_OPTIONS) +set(GECODE_SANITIZER_LINK_OPTIONS) +if(GECODE_SANITIZER_NORMALIZED) + if(GECODE_SANITIZER_NORMALIZED STREQUAL "address") + list(APPEND GECODE_SANITIZER_COMPILE_OPTIONS -fsanitize=address -fno-omit-frame-pointer) + list(APPEND GECODE_SANITIZER_LINK_OPTIONS -fsanitize=address) + elseif(GECODE_SANITIZER_NORMALIZED STREQUAL "undefined") + list(APPEND GECODE_SANITIZER_COMPILE_OPTIONS -fsanitize=undefined -fno-omit-frame-pointer) + list(APPEND GECODE_SANITIZER_LINK_OPTIONS -fsanitize=undefined) + elseif(GECODE_SANITIZER_NORMALIZED STREQUAL "address-undefined") + list(APPEND GECODE_SANITIZER_COMPILE_OPTIONS -fsanitize=address,undefined -fno-omit-frame-pointer) + list(APPEND GECODE_SANITIZER_LINK_OPTIONS -fsanitize=address,undefined) + elseif(GECODE_SANITIZER_NORMALIZED STREQUAL "thread") + list(APPEND GECODE_SANITIZER_COMPILE_OPTIONS -fsanitize=thread -fno-omit-frame-pointer) + list(APPEND GECODE_SANITIZER_LINK_OPTIONS -fsanitize=thread) + else() + message(FATAL_ERROR "Unsupported GECODE_SANITIZER='${GECODE_SANITIZER}'. Expected empty, address, undefined, address-undefined, or thread.") + endif() + + add_compile_options(${GECODE_SANITIZER_COMPILE_OPTIONS}) + add_link_options(${GECODE_SANITIZER_LINK_OPTIONS}) + message(STATUS "Gecode sanitizer instrumentation: ${GECODE_SANITIZER_NORMALIZED}") + + if(GECODE_BUILD_SHARED AND GECODE_SANITIZER_NORMALIZED MATCHES "undefined") + set(GECODE_EFFECTIVE_ENABLE_GCC_VISIBILITY OFF) + message(STATUS "Disabling hidden visibility for UBSan shared-library instrumentation") + endif() +endif() + # Keep dependency closure behavior similar to configure. if(GECODE_ENABLE_SET_VARS) gecode_force_option_on(GECODE_ENABLE_INT_VARS "Set variables require int variables") @@ -234,7 +267,7 @@ endif() include(CheckCXXCompilerFlag) set(GECODE_VISIBILITY_COMPILE_OPTION) -if(GECODE_ENABLE_GCC_VISIBILITY) +if(GECODE_EFFECTIVE_ENABLE_GCC_VISIBILITY) check_cxx_compiler_flag(-fvisibility=hidden HAVE_VISIBILITY_HIDDEN_FLAG) if(HAVE_VISIBILITY_HIDDEN_FLAG) set(GECODE_VISIBILITY_COMPILE_OPTION -fvisibility=hidden) @@ -477,6 +510,9 @@ endif() if(GECODE_ENABLE_AUDIT) set(GECODE_AUDIT "/**/") endif() +if(GECODE_ENABLE_FAULT_INJECTION) + set(GECODE_HAS_FAULT_INJECTION 1) +endif() if(GECODE_ENABLE_CPPROFILER) set(GECODE_HAS_CPPROFILER "/**/") endif() @@ -486,7 +522,7 @@ endif() if(GECODE_ENABLE_MPFR AND MPFR_FOUND AND GECODE_ENABLE_FLOAT_VARS) set(GECODE_HAS_MPFR "/**/") endif() -if(HAVE_VISIBILITY_HIDDEN_FLAG) +if(GECODE_EFFECTIVE_ENABLE_GCC_VISIBILITY AND HAVE_VISIBILITY_HIDDEN_FLAG) set(GECODE_GCC_HAS_CLASS_VISIBILITY "/**/") endif() if(GECODE_ENABLE_THREAD) @@ -569,6 +605,10 @@ ${CONFIG_OUT}") # Native source inventory for CMake (decoupled from Makefile.in). # --------------------------------------------------------------------------- include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/GecodeSources.cmake) +if(GECODE_ENABLE_FAULT_INJECTION) + list(APPEND GECODE_SUPPORT_SOURCES gecode/support/failpoint.cpp) + list(APPEND GECODE_TEST_SOURCES test/fault.cpp) +endif() # --------------------------------------------------------------------------- # Generated variable implementations (in-source, matching Makefile behavior) diff --git a/changelog.in b/changelog.in index 3daea2feb3..654285e075 100755 --- a/changelog.in +++ b/changelog.in @@ -75,6 +75,82 @@ This release modernizes the Gecode build infrastructure, adds a first-class CMake package for downstream consumers, refreshes the autoconf build path, and updates CI coverage for current platforms. +[ENTRY] +Module: kernel +What: bug +Rank: major +Issue: 211 +Thanks: Kris Coester and Alexander Shepil +[DESCRIPTION] +Make failed space cloning transactional. If copying actors, advisors, +local objects, variable implementations, or clone disposal metadata +throws, Gecode now restores the source space and releases the partial +clone without leaving forwarding links, actor lists, or dispose notices +in an inconsistent state. + +[ENTRY] +Module: kernel +What: bug +Rank: minor +Issue: 211 +Thanks: Alexander Shepil +[DESCRIPTION] +Harden dispose-notice registration against allocation failure. Actors +are no longer disposed by the space when an AP_DISPOSE registration +fails before ownership has been transferred to the dispose-notice list. + +[ENTRY] +Module: int +What: bug +Rank: minor +Issue: 211 +Thanks: Alexander Shepil +[DESCRIPTION] +Fix exception safety in IntSet object allocation. Partially constructed +IntSet objects now initialize their cleanup state before allocating +range storage and release the object if range allocation fails. + +[ENTRY] +Module: int +What: bug +Rank: minor +[DESCRIPTION] +Avoid signed integer overflow when computing integer-domain widths and +holes. Range width calculations now use unsigned arithmetic after +conversion, including full-domain integer ranges. + +[ENTRY] +Module: minimodel +What: bug +Rank: minor +Issue: 211 +Thanks: Alexander Shepil +[DESCRIPTION] +Fix MiniModel expression ownership during allocation failure. Boolean +misc-expression nodes and non-linear integer expression nodes now clean +up partially transferred objects and internal expression arrays while +preserving the public default LinIntExpr zero-expression invariant. + +[ENTRY] +Module: search +What: bug +Rank: major +[DESCRIPTION] +Fix sanitizer-detected search issues, including branch-and-bound +recomputation undefined behavior, parallel search command and stop-state +races, PBS slave lifetime hazards, restart-stop state races, stop-object +limit races, and shared no-good accounting races. + +[ENTRY] +Module: test +What: new +Rank: minor +[DESCRIPTION] +Add opt-in deterministic fault injection and sanitizer build support. +The new fault-injection tests exercise clone, dispose-notice, IntSet, +MiniModel, and heap-allocation failure paths that are otherwise hard to +reproduce reliably. + [ENTRY] Module: other What: change diff --git a/gecode/int.hh b/gecode/int.hh index 2c172305f1..301eb0ddc4 100755 --- a/gecode/int.hh +++ b/gecode/int.hh @@ -12,7 +12,7 @@ * * Copyright: * Stefano Gualandi, 2013 - * Mikael Zayenz Lagerkvist, 2006 + * Mikael Zayenz Lagerkvist, 2006, 2026 * David Rijsman, 2009 * Christian Schulte, 2002 * Guido Tack, 2004 @@ -49,6 +49,10 @@ #include #include +#ifdef GECODE_HAS_FAULT_INJECTION +#include +#endif + #include #include #include @@ -188,6 +192,14 @@ namespace Gecode { int n; /// Array of ranges Range* r; +#ifdef GECODE_HAS_FAULT_INJECTION + /// Number of live objects for fault-injection tests + static std::atomic fault_live_objects; + /// Memory management for fault-injection accounting + static void* operator new(size_t s); + /// Memory management for fault-injection accounting + static void operator delete(void* p); +#endif /// Allocate object with \a m elements GECODE_INT_EXPORT static IntSetObject* allocate(int m); /// Check whether \a n is included in the set @@ -208,6 +220,12 @@ namespace Gecode { /// Initialize with \a n ranges from array \a r GECODE_INT_EXPORT void init(const int r[][2], int n); public: +#ifdef GECODE_HAS_FAULT_INJECTION + /// Reset live object accounting for fault-injection tests + GECODE_INT_EXPORT static void fault_reset_allocations(void); + /// Return live object accounting for fault-injection tests + GECODE_INT_EXPORT static int fault_live_allocations(void); +#endif /// \name Constructors and initialization //@{ /// Initialize as empty set diff --git a/gecode/int/int-set-1.hpp b/gecode/int/int-set-1.hpp index 637253066c..5aac66ec8a 100755 --- a/gecode/int/int-set-1.hpp +++ b/gecode/int/int-set-1.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2003 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -164,7 +168,8 @@ namespace Gecode { IntSet::width(int i) const { assert(object() != nullptr); IntSetObject* o = static_cast(object()); - return static_cast(o->r[i].max-o->r[i].min)+1; + return static_cast(o->r[i].max) - + static_cast(o->r[i].min) + 1U; } forceinline int @@ -203,7 +208,8 @@ namespace Gecode { forceinline unsigned int IntSet::width(void) const { IntSetObject* o = static_cast(object()); - return (o == nullptr) ? 0U : static_cast(max()-min()+1); + return (o == nullptr) ? 0U : + static_cast(max()) - static_cast(min()) + 1U; } forceinline bool @@ -265,7 +271,8 @@ namespace Gecode { } forceinline unsigned int IntSetRanges::width(void) const { - return static_cast(i->max - i->min) + 1; + return static_cast(i->max) - + static_cast(i->min) + 1U; } /* @@ -311,4 +318,3 @@ namespace Gecode { } // STATISTICS: int-var - diff --git a/gecode/int/int-set.cpp b/gecode/int/int-set.cpp index 708e91035c..9dc4d92fef 100755 --- a/gecode/int/int-set.cpp +++ b/gecode/int/int-set.cpp @@ -3,8 +3,14 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Alexander Shepil + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2003 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -35,11 +41,49 @@ namespace Gecode { +#ifdef GECODE_HAS_FAULT_INJECTION + std::atomic IntSet::IntSetObject::fault_live_objects{0}; + + void* + IntSet::IntSetObject::operator new(size_t s) { + void* p = ::operator new(s); + fault_live_objects.fetch_add(1, std::memory_order_relaxed); + return p; + } + + void + IntSet::IntSetObject::operator delete(void* p) { + fault_live_objects.fetch_sub(1, std::memory_order_relaxed); + ::operator delete(p); + } + + void + IntSet::fault_reset_allocations(void) { + IntSetObject::fault_live_objects.store(0, std::memory_order_relaxed); + } + + int + IntSet::fault_live_allocations(void) { + return IntSetObject::fault_live_objects.load(std::memory_order_relaxed); + } +#endif + IntSet::IntSetObject* IntSet::IntSetObject::allocate(int n) { IntSetObject* o = new IntSetObject; + o->size = 0U; + o->n = 0; + o->r = nullptr; + try { +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::IntSet); +#endif + o->r = heap.alloc(n); + } catch (...) { + delete o; + throw; + } o->n = n; - o->r = heap.alloc(n); return o; } @@ -73,7 +117,8 @@ namespace Gecode { } IntSet::IntSetObject::~IntSetObject(void) { - heap.free(r,n); + if (r != nullptr) + heap.free(r,n); } /// Sort ranges according to increasing minimum @@ -115,7 +160,8 @@ namespace Gecode { IntSetObject* o = IntSetObject::allocate(n); unsigned int s = 0; for (int i=0; i(r[i].max-r[i].min+1); + s += static_cast(r[i].max) - + static_cast(r[i].min) + 1U; o->r[i]=r[i]; } o->size = s; @@ -178,7 +224,7 @@ namespace Gecode { if (n <= m) { IntSetObject* o = IntSetObject::allocate(1); o->r[0].min = n; o->r[0].max = m; - o->size = static_cast(m - n + 1); + o->size = static_cast(m) - static_cast(n) + 1U; object(o); } } @@ -188,4 +234,3 @@ namespace Gecode { } // STATISTICS: int-var - diff --git a/gecode/int/var-imp/delta.hpp b/gecode/int/var-imp/delta.hpp index e0b41c8956..980108b684 100644 --- a/gecode/int/var-imp/delta.hpp +++ b/gecode/int/var-imp/delta.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2007 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -52,7 +56,7 @@ namespace Gecode { namespace Int { } forceinline unsigned int IntDelta::width(void) const { - return static_cast(_max - _min + 1); + return static_cast(_max) - static_cast(_min) + 1U; } forceinline bool IntDelta::any(void) const { diff --git a/gecode/int/var-imp/int.cpp b/gecode/int/var-imp/int.cpp index a96e23ca4c..bce2b3ad49 100644 --- a/gecode/int/var-imp/int.cpp +++ b/gecode/int/var-imp/int.cpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2002 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -37,8 +41,10 @@ namespace Gecode { namespace Int { forceinline bool IntVarImp::closer_min(int n) const { - unsigned int l = static_cast(n - dom.min()); - unsigned int r = static_cast(dom.max() - n); + unsigned int l = static_cast(n) - + static_cast(dom.min()); + unsigned int r = static_cast(dom.max()) - + static_cast(n); return l < r; } @@ -102,7 +108,8 @@ namespace Gecode { namespace Int { unsigned int h = 0; while (m < c->min()) { RangeList* p = c->prev(n); c->fix(n); - h += (c->min() - p->max() - 1); + h += static_cast(c->min()) - + static_cast(p->max()) - 1U; n=c; c=p; } holes -= h; @@ -136,7 +143,8 @@ namespace Gecode { namespace Int { unsigned int h = 0; while (m > c->max()) { RangeList* n = c->next(p); c->fix(n); - h += (n->min() - c->max() - 1); + h += static_cast(n->min()) - + static_cast(c->max()) - 1U; p=c; c=n; } holes -= h; @@ -212,7 +220,8 @@ namespace Gecode { namespace Int { } else { // Remains non-range f_next->prev(fst(),nullptr); fst()->dispose(home); fst(f_next); - holes -= dom.min() - f_min - 1; + holes -= static_cast(dom.min()) - + static_cast(f_min) - 1U; me = ME_INT_BND; } } else if (m == f_min) { @@ -243,7 +252,8 @@ namespace Gecode { namespace Int { } else { // Remains non-range l_prev->next(lst(),nullptr); lst()->dispose(home); lst(l_prev); - holes -= l_max - dom.max() - 1; + holes -= static_cast(l_max) - + static_cast(dom.max()) - 1U; me = ME_INT_BND; } } else if (m == l_max) { diff --git a/gecode/int/var-imp/int.hpp b/gecode/int/var-imp/int.hpp index 06f2cffe02..dcadd00d48 100644 --- a/gecode/int/var-imp/int.hpp +++ b/gecode/int/var-imp/int.hpp @@ -5,10 +5,12 @@ * * Contributing authors: * Guido Tack + * Mikael Zayenz Lagerkvist * * Copyright: * Christian Schulte, 2003 * Guido Tack, 2004 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -45,6 +47,12 @@ namespace Gecode { namespace Int { #define GECODE_INT_RL2PD(r) reinterpret_cast(r) #define GECODE_INT_PD2RL(p) reinterpret_cast(p) + forceinline unsigned int + range_width(int min, int max) { + return static_cast(max) - + static_cast(min) + 1U; + } + forceinline IntVarImp::RangeList::RangeList(void) {} @@ -104,7 +112,7 @@ namespace Gecode { namespace Int { } forceinline unsigned int IntVarImp::RangeList::width(void) const { - return static_cast(_max - _min) + 1; + return range_width(_min,_max); } @@ -196,7 +204,7 @@ namespace Gecode { namespace Int { assert(n >= 2); RangeList* r = home.alloc(n); fst(r); lst(r+n-1); - unsigned int h = static_cast(d.max()-d.min())+1; + unsigned int h = range_width(d.min(),d.max()); h -= d.width(0); r[0].min(d.min(0)); r[0].max(d.max(0)); r[0].prevnext(nullptr,&r[1]); @@ -259,7 +267,8 @@ namespace Gecode { namespace Int { if (fst() == nullptr) { return (dom.min() == dom.max()) ? 0U : 1U; } else if (dom.min() == fst()->max()) { - return static_cast(fst()->next(nullptr)->min()-dom.min()); + return static_cast(fst()->next(nullptr)->min()) - + static_cast(dom.min()); } else { return 1U; } @@ -269,7 +278,8 @@ namespace Gecode { namespace Int { if (fst() == nullptr) { return (dom.min() == dom.max()) ? 0U : 1U; } else if (dom.max() == lst()->min()) { - return static_cast(dom.max()-lst()->prev(nullptr)->max()); + return static_cast(dom.max()) - + static_cast(lst()->prev(nullptr)->max()); } else { return 1U; } @@ -531,7 +541,7 @@ namespace Gecode { namespace Int { // Construct new rangelist RangeList* f = new (home) RangeList(min0,max0,nullptr,nullptr); RangeList* l = f; - unsigned int s = static_cast(max0-min0+1); + unsigned int s = range_width(min0,max0); do { RangeList* n = new (home) RangeList(ri.min(),ri.max(),l,nullptr); l->next(nullptr,n); @@ -604,8 +614,9 @@ namespace Gecode { namespace Int { min0=ri.min(); max0=ri.max(); ++ri; if (max0 > end) break; - assert(h > static_cast(max0-min0+1)); - h -= max0-min0+1; + unsigned int w = range_width(min0,max0); + assert(h > w); + h -= w; RangeList* n = new (home) RangeList(min0,max0,p,r); p->next(r,n); r->prev(p,n); p=n; @@ -642,8 +653,10 @@ namespace Gecode { namespace Int { // Unlink sentinel ranges fn->prev(&f,nullptr); ln->next(&l,nullptr); // How many values where removed at the bounds - unsigned int b = (static_cast(fn->min()-dom.min()) + - static_cast(dom.max()-ln->max())); + unsigned int b = (static_cast(fn->min()) - + static_cast(dom.min()) + + static_cast(dom.max()) - + static_cast(ln->max())); // Set new first and last ranges fst(fn); lst(ln); @@ -747,7 +760,7 @@ namespace Gecode { namespace Int { break; } else if ((i_min > r->min()) && (i_max < r->max())) { // i is included in r: create new range before the current one - h += static_cast(i_max - i_min) + 1; + h += range_width(i_min,i_max); RangeList* n = new (home) RangeList(r->min(),i_min-1,p,r); r->min(i_max+1); p->next(r,n); r->prev(p,n); @@ -760,7 +773,7 @@ namespace Gecode { namespace Int { } else if (i_max < r->max()) { assert(i_min <= r->min()); // i ends before r: adjust minimum of r - h += i_max-r->min()+1; + h += range_width(r->min(),i_max); r->min(i_max+1); if (!i()) break; @@ -770,7 +783,7 @@ namespace Gecode { namespace Int { } else { assert((i_max >= r->max()) && (r->min() < i_min)); // r ends before i: adjust maximum of r - h += r->max()-i_min+1; + h += range_width(i_min,r->max()); r->max(i_min-1); RangeList* n=r->next(p); p=r; r=n; if (r == &l) @@ -807,8 +820,10 @@ namespace Gecode { namespace Int { // Unlink sentinel ranges fn->prev(&f,nullptr); ln->next(&l,nullptr); // How many values where removed at the bounds - b = (static_cast(fn->min()-dom.min()) + - static_cast(dom.max()-ln->max())); + b = (static_cast(fn->min()) - + static_cast(dom.min()) + + static_cast(dom.max()) - + static_cast(ln->max())); // Set new first and last ranges fst(fn); lst(ln); @@ -961,8 +976,10 @@ namespace Gecode { namespace Int { // Unlink sentinel ranges fn->prev(&f,nullptr); ln->next(&l,nullptr); // How many values where removed at the bounds - unsigned int b = (static_cast(fn->min()-dom.min()) + - static_cast(dom.max()-ln->max())); + unsigned int b = (static_cast(fn->min()) - + static_cast(dom.min()) + + static_cast(dom.max()) - + static_cast(ln->max())); // Set new first and last ranges fst(fn); lst(ln); diff --git a/gecode/kernel/core.cpp b/gecode/kernel/core.cpp index c83d70a8a4..c49471676f 100644 --- a/gecode/kernel/core.cpp +++ b/gecode/kernel/core.cpp @@ -4,10 +4,16 @@ * Christian Schulte * * Contributing authors: + * Kris Coester + * Alexander Shepil + * Mikael Zayenz Lagerkvist * Samuel Gagnon * * Copyright: * Christian Schulte, 2002 + * Kris Coester, 2024 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * Samuel Gagnon, 2018 * * This file is part of Gecode, the generic constraint @@ -148,14 +154,28 @@ namespace Gecode { // Resize if (d_fst == nullptr) { // Create new array - d_fst = alloc(4); + try { +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::SpaceDisposeNoticeArray); +#endif + d_fst = alloc(4); + } catch (...) { + throw; + } d_cur = d_fst; d_lst = d_fst+4; } else { // Resize existing array unsigned int n = static_cast(d_lst - d_fst); assert(n != 0); - d_fst = realloc(d_fst,n,2*n); + try { +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::SpaceDisposeNoticeArray); +#endif + d_fst = realloc(d_fst,n,2*n); + } catch (...) { + throw; + } d_cur = d_fst+n; d_lst = d_fst+2*n; } @@ -166,6 +186,9 @@ namespace Gecode { void Space::ap_ignore_dispose(Actor* a, bool duplicate) { // Note that a might be a marked pointer! + if (is_partial_clone()) + return; + assert(d_fst != nullptr); Actor** f = d_fst; if (duplicate) { @@ -184,6 +207,13 @@ namespace Gecode { } Space::~Space(void) { + if (is_partial_clone()) { + if (pc.c.source != nullptr) { + recover(*pc.c.source); + pc.c.source = nullptr; + } + d_fst = d_cur = d_lst = nullptr; + } // Mark space as failed fail(); // Delete actors that must be deleted @@ -697,53 +727,64 @@ namespace Gecode { #ifdef GECODE_HAS_CBS var_id_counter(s.var_id_counter), #endif - d_fst(&Actor::sentinel) { + d_fst(&Actor::sentinel),d_cur(nullptr),d_lst(nullptr) { #ifdef GECODE_HAS_VAR_DISPOSE for (int i=0; inext(); a != e; a = a->next()) { - Actor* c = Actor::cast(a)->copy(*this); - // Link copied actor - p->next(ActorLink::cast(c)); ActorLink::cast(c)->prev(p); - // Note that forwarding is done in the constructors - p = c; + try { + for (int i=0; inext(); a != e; a = a->next()) { + Actor* c = Actor::cast(a)->copy(*this); + // Link copied actor + p->next(ActorLink::cast(c)); ActorLink::cast(c)->prev(p); + // Note that forwarding is done in the constructors + p = c; + } + // Link last actor + p->next(&pl); pl.prev(p); } - // Link last actor - p->next(&pl); pl.prev(p); - } - // Copy all branchers - { - ActorLink* p = &bl; - ActorLink* e = &s.bl; - for (ActorLink* a = e->next(); a != e; a = a->next()) { - Actor* c = Actor::cast(a)->copy(*this); - // Link copied actor - p->next(ActorLink::cast(c)); ActorLink::cast(c)->prev(p); - // Note that forwarding is done in the constructors - p = c; + // Copy all branchers + { + ActorLink* p = &bl; + ActorLink* e = &s.bl; + for (ActorLink* a = e->next(); a != e; a = a->next()) { + Actor* c = Actor::cast(a)->copy(*this); + // Link copied actor + p->next(ActorLink::cast(c)); ActorLink::cast(c)->prev(p); + // Note that forwarding is done in the constructors + p = c; + } + // Link last actor + p->next(&bl); bl.prev(p); } - // Link last actor - p->next(&bl); bl.prev(p); - } - // Setup brancher pointers - if (s.b_status == &s.bl) { - b_status = Brancher::cast(&bl); - } else { - b_status = Brancher::cast(s.b_status->prev()); - } - if (s.b_commit == &s.bl) { - b_commit = Brancher::cast(&bl); - } else { - b_commit = Brancher::cast(s.b_commit->prev()); + // Setup brancher pointers + if (s.b_status == &s.bl) { + b_status = Brancher::cast(&bl); + } else { + b_status = Brancher::cast(s.b_status->prev()); + } + if (s.b_commit == &s.bl) { + b_commit = Brancher::cast(&bl); + } else { + b_commit = Brancher::cast(s.b_commit->prev()); + } + } catch (...) { + recover(s); + pc.c.source = nullptr; + mm.release(ssd.data().sm); + throw; } } @@ -768,15 +809,26 @@ namespace Gecode { c->d_fst = c->d_cur = c->d_lst = nullptr; } else { // Leave one entry free - c->d_fst = c->alloc(n+1); - c->d_cur = c->d_fst; - c->d_lst = c->d_fst+n+1; - for (Actor** d_fst_iter = d_fst; d_fst_iter != d_cur; d_fst_iter++) { - ptrdiff_t m; - Actor* a = static_cast(Support::ptrsplit(*d_fst_iter,m)); - if (a->prev()) - *(c->d_cur++) = Actor::cast(static_cast - (Support::ptrjoin(a->prev(),m))); + try { +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::SpaceDisposalArray); +#endif + c->d_fst = c->alloc(n+1); + c->d_cur = c->d_fst; + c->d_lst = c->d_fst+n+1; + for (Actor** d_fst_iter = d_fst; d_fst_iter != d_cur; d_fst_iter++) { + ptrdiff_t m; + Actor* a = static_cast(Support::ptrsplit(*d_fst_iter,m)); + if (a->prev()) + *(c->d_cur++) = Actor::cast(static_cast + (Support::ptrjoin(a->prev(),m))); + } + } + catch (...) { + c->recover(*this); + c->d_fst = c->d_cur = c->d_lst = nullptr; + delete c; + throw; } } } diff --git a/gecode/kernel/core.hpp b/gecode/kernel/core.hpp index affa266743..4e275489b5 100755 --- a/gecode/kernel/core.hpp +++ b/gecode/kernel/core.hpp @@ -7,12 +7,16 @@ * * Contributing authors: * Filip Konvicka + * Kris Coester + * Alexander Shepil * Samuel Gagnon * * Copyright: * Christian Schulte, 2002 * Guido Tack, 2003 - * Mikael Zayenz Lagerkvist, 2006 + * Mikael Zayenz Lagerkvist, 2006, 2026 + * Kris Coester, 2024 + * Alexander Shepil, 2024 * LOGIS, s.r.o., 2009 * Samuel Gagnon, 2018 * @@ -44,6 +48,7 @@ * */ +#include #include namespace Gecode { @@ -298,17 +303,19 @@ namespace Gecode { * to be stored. */ static void update(Space& home, ActorLink**& sub); + /// Recover copied variables after failed clone construction + static void recover(Space& home, ActorLink**& sub); /// Enter propagator to subscription array void enter(Space& home, Propagator* p, PropCond pc); - /// Enter advisor to subscription array - void enter(Space& home, Advisor* a); + /// Enter possibly marked advisor to subscription array + void enter(Space& home, ActorLink* a); /// Resize subscription array void resize(Space& home); /// Remove propagator from subscription array void remove(Space& home, Propagator* p, PropCond pc); - /// Remove advisor from subscription array - void remove(Space& home, Advisor* a); + /// Remove possibly marked advisor from subscription array + void remove(Space& home, ActorLink* a); protected: @@ -1303,6 +1310,8 @@ namespace Gecode { static Advisor* cast(ActorLink* al); /// Static cast static const Advisor* cast(const ActorLink* al); + /// Cast to actor link + static ActorLink* link(Advisor& a); protected: /// Return the advisor's propagator Propagator& propagator(void) const; @@ -1590,7 +1599,7 @@ namespace Gecode { class GECODE_VTABLE_EXPORT NoGoods { protected: /// Number of no-goods - unsigned long int n; + std::atomic n; public: /// Initialize NoGoods(void); @@ -1870,6 +1879,8 @@ namespace Gecode { VarImpBase* vars_noidx; /// Linked list of local objects LocalObject* local; + /// Source space during clone construction + Space* source; } c; } pc; /// Put propagator \a p into right queue @@ -1892,6 +1903,15 @@ namespace Gecode { void update(ActorLink** sub); //@} + /// Recover variables without indexing structure + void recover_noidx(void); + + /// Recover after failed clone construction + void recover(Space& source); + + /// Test whether clone construction has not reached a disposable state + bool is_partial_clone(void) const; + /// First actor for forced disposal Actor** d_fst; /// Current actor for forced disposal @@ -1899,11 +1919,11 @@ namespace Gecode { /// Last actor for forced disposal Actor** d_lst; - /// Used for default argument + /// Legacy unused status statistics GECODE_KERNEL_EXPORT static StatusStatistics unused_status; - /// Used for default argument + /// Legacy unused clone statistics GECODE_KERNEL_EXPORT static CloneStatistics unused_clone; - /// Used for default argument + /// Legacy unused commit statistics GECODE_KERNEL_EXPORT static CommitStatistics unused_commit; /** @@ -2132,7 +2152,9 @@ namespace Gecode { * \ingroup TaskSearch */ GECODE_KERNEL_EXPORT - SpaceStatus status(StatusStatistics& stat=unused_status); + SpaceStatus status(StatusStatistics& stat); + /// Query space status without collecting statistics + SpaceStatus status(void); /** * \brief Create new choice for current brancher @@ -2195,7 +2217,10 @@ namespace Gecode { * * \ingroup TaskSearch */ - Space* clone(CloneStatistics& stat=unused_clone) const; + /// Clone space without collecting statistics + Space* clone(void) const; + /// Clone space + Space* clone(CloneStatistics& stat) const; /** * \brief Commit choice \a c for alternative \a a @@ -2231,8 +2256,11 @@ namespace Gecode { * * \ingroup TaskSearch */ + /// Commit choice \a c for alternative \a a without collecting statistics + void commit(const Choice& c, unsigned int a); + /// Commit choice \a c for alternative \a a void commit(const Choice& c, unsigned int a, - CommitStatistics& stat=unused_commit); + CommitStatistics& stat); /** * \brief If possible, commit choice \a c for alternative \a a * @@ -2265,8 +2293,11 @@ namespace Gecode { * * \ingroup TaskSearch */ + /// If possible, commit choice \a c for alternative \a a without collecting statistics + void trycommit(const Choice& c, unsigned int a); + /// If possible, commit choice \a c for alternative \a a void trycommit(const Choice& c, unsigned int a, - CommitStatistics& stat=unused_commit); + CommitStatistics& stat); /** * \brief Create no-good literal for choice \a c and alternative \a a * @@ -3030,6 +3061,11 @@ namespace Gecode { } #endif + forceinline bool + Space::is_partial_clone(void) const { + return d_fst == &Actor::sentinel; + } + // Space allocated entities: Actors, variable implementations, and advisors forceinline void Actor::operator delete(void*) {} @@ -3082,11 +3118,11 @@ namespace Gecode { : n(0) {} forceinline unsigned long int NoGoods::ng(void) const { - return n; + return n.load(std::memory_order_acquire); } forceinline void NoGoods::ng(unsigned long int n0) { - n=n0; + n.store(n0, std::memory_order_release); } forceinline NoGoods::~NoGoods(void) {} @@ -3254,6 +3290,18 @@ namespace Gecode { s.notice(a,p,duplicate); } + forceinline SpaceStatus + Space::status(void) { + StatusStatistics stat; + return status(stat); + } + + forceinline Space* + Space::clone(void) const { + CloneStatistics stat; + return clone(stat); + } + forceinline Space* Space::clone(CloneStatistics&) const { // Clone is only const for search engines. During cloning, several data @@ -3262,11 +3310,23 @@ namespace Gecode { return const_cast(this)->_clone(); } + forceinline void + Space::commit(const Choice& c, unsigned int a) { + CommitStatistics stat; + commit(c,a,stat); + } + forceinline void Space::commit(const Choice& c, unsigned int a, CommitStatistics&) { _commit(c,a); } + forceinline void + Space::trycommit(const Choice& c, unsigned int a) { + CommitStatistics stat; + trycommit(c,a,stat); + } + forceinline void Space::trycommit(const Choice& c, unsigned int a, CommitStatistics&) { _trycommit(c,a); @@ -3902,6 +3962,11 @@ namespace Gecode { return static_cast(al); } + forceinline ActorLink* + Advisor::link(Advisor& a) { + return static_cast(&a); + } + forceinline Propagator& Advisor::propagator(void) const { assert(!disposed()); @@ -3990,26 +4055,37 @@ namespace Gecode { ActorLink** a_f = &c.advisors; // Advisors in to-space A* a_t = nullptr; - while (*a_f != nullptr) { - if (static_cast(*a_f)->disposed()) { - *a_f = (*a_f)->next(); - } else { - // Run specific copying part - A* a = new (home) A(home,*static_cast(*a_f)); - // Set propagator pointer - a->prev(p_t); - // Set forwarding pointer - (*a_f)->prev(a); - // Link - a->next(a_t); - a_t = a; - a_f = (*a_f)->next_ref(); + // Enter advisor link early so failed clone recovery can restore links. + assert(p_f->u.advisors == nullptr); + p_f->u.advisors = c.advisors; + try { + while (*a_f != nullptr) { + if (static_cast(*a_f)->disposed()) { + *a_f = (*a_f)->next(); + } else { + // Run specific copying part + A* a = new (home) A(home,*static_cast(*a_f)); + // Set propagator pointer + a->prev(p_t); + // Set forwarding pointer + (*a_f)->prev(a); + // Link + a->next(a_t); + a_t = a; + advisors = a_t; + a_f = (*a_f)->next_ref(); + } } + } catch (...) { + dispose(home); + advisors = nullptr; + for (ActorLink* a = c.advisors; a != nullptr; a = a->next()) + if (!static_cast(a)->disposed()) + a->prev(p_f); + p_f->u.advisors = nullptr; + throw; } advisors = a_t; - // Enter advisor link for reset - assert(p_f->u.advisors == nullptr); - p_f->u.advisors = c.advisors; } else { advisors = nullptr; } @@ -4351,10 +4427,14 @@ namespace Gecode { template forceinline void VarImp::schedule(Space& home, PropCond pc1, PropCond pc2, ModEvent me) { - ActorLink** b = actor(pc1); - ActorLink** p = actorNonZero(pc2+1); - while (p-- > b) - schedule(home,*Propagator::cast(*p),me); + if (b.base == nullptr) + return; + ActorLink** begin = actor(pc1); + ActorLink** end = actorNonZero(pc2+1); + while (end > begin) { + end--; + schedule(home,*Propagator::cast(*end),me); + } } template @@ -4421,7 +4501,7 @@ namespace Gecode { template forceinline void - VarImp::enter(Space& home, Advisor* a) { + VarImp::enter(Space& home, ActorLink* a) { // Note that a might be a marked pointer // Count one new subscription home.pc.p.n_sub += 1; @@ -4454,7 +4534,8 @@ namespace Gecode { forceinline void VarImp::subscribe(Space& home, Advisor& a, bool assigned, bool fail) { if (!assigned) { - Advisor* ma = static_cast(Support::ptrjoin(&a,fail ? 1 : 0)); + ActorLink* ma = static_cast + (Support::ptrjoin(Advisor::link(a),fail ? 1 : 0)); enter(home,ma); } } @@ -4503,13 +4584,13 @@ namespace Gecode { template forceinline void VarImp::cancel(Space& home, Propagator& p, PropCond pc) { - if (b.base != nullptr) + if ((b.base != nullptr) && !home.is_partial_clone()) remove(home,&p,pc); } template void - VarImp::remove(Space& home, Advisor* a) { + VarImp::remove(Space& home, ActorLink* a) { // Note that a might be a marked pointer // Find actor in dependency array ActorLink** f = actorNonZero(pc_max+1); @@ -4533,8 +4614,9 @@ namespace Gecode { template forceinline void VarImp::cancel(Space& home, Advisor& a, bool fail) { - if (b.base != nullptr) { - Advisor* ma = static_cast(Support::ptrjoin(&a,fail ? 1 : 0)); + if ((b.base != nullptr) && !home.is_partial_clone()) { + ActorLink* ma = static_cast + (Support::ptrjoin(Advisor::link(a),fail ? 1 : 0)); remove(home,ma); } } @@ -4723,6 +4805,25 @@ namespace Gecode { } } + template + forceinline void + VarImp::recover(Space& home, ActorLink**&) { + VarImp* x = static_cast*>(home.pc.c.vars_u[idx_c]); + while (x != nullptr) { + if (x->copied()) { + VarImp* n = x->next(); + VarImp* copy = x->forward(); + x->b.base = copy->b.base; + x->u.idx[0] = copy->u.idx[0]; + if (pc_max > 0 && sizeof(ActorLink**) > sizeof(unsigned int)) + x->u.idx[1] = copy->u.idx[1]; + x = n; + } else { + break; + } + } + } + /* diff --git a/gecode/kernel/var-imp.hpp b/gecode/kernel/var-imp.hpp index a6cba54e1d..8f4e120524 100644 --- a/gecode/kernel/var-imp.hpp +++ b/gecode/kernel/var-imp.hpp @@ -13,8 +13,16 @@ * Main author: * Christian Schulte * + * Contributing authors: + * Kris Coester + * Alexander Shepil + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2007 + * Kris Coester, 2024 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * * The generated code fragments are part of Gecode, the generic * constraint development environment: @@ -478,6 +486,19 @@ namespace Gecode { namespace Float { #endif namespace Gecode { + forceinline void + Space::recover_noidx(void) { + VarImp* x = + static_cast*>(pc.c.vars_noidx); + while (x != nullptr) { + VarImp* n = x->next(); + x->b.base = nullptr; x->u.idx[0] = 0; + if (sizeof(ActorLink**) > sizeof(unsigned int)) + *(1+&x->u.idx[0]) = 0; + x = n; + } + } + forceinline void Space::update(ActorLink** sub) { #ifdef GECODE_HAS_INT_VARS @@ -491,6 +512,70 @@ namespace Gecode { #endif #ifdef GECODE_HAS_FLOAT_VARS Gecode::VarImp::update(*this,sub); +#endif + } + + forceinline void + Space::recover(Space& source) { + ActorLink* clone_lists[2] = {&pl, &bl}; + for (int i=0; i<2; i++) { + ActorLink* l = clone_lists[i]; + ActorLink* c = l->next(); + while (c != l) { + ActorLink* n = c->next(); + (void) Actor::cast(c)->dispose(*this); + c = n; + } + l->init(); + } + while (pc.c.local != nullptr) { + LocalObject* l = pc.c.local; + ActorLink* n = l->next(); + pc.c.local = (n == nullptr) ? nullptr : LocalObject::cast(n); + ActorLink* fwd = l->prev(); + if (fwd != nullptr) { + (void) Actor::cast(fwd)->dispose(*this); + l->prev(nullptr); + } + } + + ActorLink* lists[2] = {&source.pl, &source.bl}; + for (int i=0; i<2; i++) { + ActorLink* l = lists[i]; + ActorLink* p_a = l; + ActorLink* c_a = p_a->next(); + while (c_a != l) { + if (i == 0) { + Propagator* p = Propagator::cast(c_a); + if (p->u.advisors != nullptr) { + ActorLink* a = p->u.advisors; + p->u.advisors = nullptr; + do { + a->prev(p); a = a->next(); + } while (a != nullptr); + } + } + ActorLink* fwd = c_a->prev(); + if (fwd != p_a) { + c_a->prev(p_a); + } + p_a = c_a; c_a = c_a->next(); + } + } + + ActorLink** sub = static_cast(mm.subscriptions()); + recover_noidx(); +#ifdef GECODE_HAS_INT_VARS + Gecode::VarImp::recover(*this,sub); +#endif +#ifdef GECODE_HAS_INT_VARS + Gecode::VarImp::recover(*this,sub); +#endif +#ifdef GECODE_HAS_SET_VARS + Gecode::VarImp::recover(*this,sub); +#endif +#ifdef GECODE_HAS_FLOAT_VARS + Gecode::VarImp::recover(*this,sub); #endif } } diff --git a/gecode/kernel/var-type.hpp b/gecode/kernel/var-type.hpp index 497095e0bb..5768ea5ea8 100644 --- a/gecode/kernel/var-type.hpp +++ b/gecode/kernel/var-type.hpp @@ -13,8 +13,16 @@ * Main author: * Christian Schulte * + * Contributing authors: + * Kris Coester + * Alexander Shepil + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2007 + * Kris Coester, 2024 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * * The generated code fragments are part of Gecode, the generic * constraint development environment: diff --git a/gecode/minimodel.hh b/gecode/minimodel.hh index 223aa53b9c..8c73825d7c 100755 --- a/gecode/minimodel.hh +++ b/gecode/minimodel.hh @@ -7,11 +7,15 @@ * Mikael Zayenz Lagerkvist * Vincent Barichard * + * Contributing authors: + * Alexander Shepil + * * Copyright: * Christian Schulte, 2004 * Fraunhofer ITWM, 2017 * Guido Tack, 2004 - * Mikael Zayenz Lagerkvist, 2005 + * Mikael Zayenz Lagerkvist, 2005, 2026 + * Alexander Shepil, 2024 * Vincent Barichard, 2012 * * This file is part of Gecode, the generic constraint @@ -51,6 +55,10 @@ #include #endif +#ifdef GECODE_HAS_FAULT_INJECTION +#include +#endif + #include /* @@ -241,9 +249,14 @@ namespace Gecode { namespace Gecode { + namespace MiniModel { + class ArithNonLinIntExpr; + } + /// Linear expressions over integer variables class LinIntExpr { friend class LinIntRel; + friend class MiniModel::ArithNonLinIntExpr; #ifdef GECODE_HAS_SET_VARS friend class SetExpr; #endif @@ -264,14 +277,24 @@ namespace Gecode { NT_MUL ///< Multiplication by coefficient }; private: + /// Tag for internal expression slots without a public zero node + struct NoNode {}; /// Nodes for linear expressions class Node; /// The actual node Node* n; + /// Create internal expression slot without a node + LinIntExpr(NoNode); public: /// Default constructor GECODE_MINIMODEL_EXPORT LinIntExpr(void); +#ifdef GECODE_HAS_FAULT_INJECTION + /// Reset live node accounting for fault-injection tests + GECODE_MINIMODEL_EXPORT static void fault_reset_allocations(void); + /// Return live node accounting for fault-injection tests + GECODE_MINIMODEL_EXPORT static int fault_live_allocations(void); +#endif /// Create expression for constant \a c GECODE_MINIMODEL_EXPORT LinIntExpr(int c); diff --git a/gecode/minimodel/bool-expr.cpp b/gecode/minimodel/bool-expr.cpp index ce8e814c61..9a6195910e 100755 --- a/gecode/minimodel/bool-expr.cpp +++ b/gecode/minimodel/bool-expr.cpp @@ -5,9 +5,15 @@ * Christian Schulte * Vincent Barichard * + * Contributing authors: + * Alexander Shepil + * Mikael Zayenz Lagerkvist + * * Copyright: * Guido Tack, 2004 * Christian Schulte, 2004 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * Vincent Barichard, 2012 * * This file is part of Gecode, the generic constraint @@ -92,6 +98,9 @@ namespace Gecode { void* BoolExpr::Node::operator new(size_t size) { +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::MiniModel); +#endif return heap.ralloc(size); } void @@ -194,7 +203,13 @@ namespace Gecode { #endif BoolExpr::BoolExpr(BoolExpr::Misc* m) - : n(new Node) { + : n(nullptr) { + try { + n = new Node; + } catch (...) { + delete m; + throw; + } n->same = 1; n->t = NT_MISC; n->l = nullptr; diff --git a/gecode/minimodel/int-arith.cpp b/gecode/minimodel/int-arith.cpp index 4251410b91..989e4d4084 100755 --- a/gecode/minimodel/int-arith.cpp +++ b/gecode/minimodel/int-arith.cpp @@ -3,8 +3,14 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Alexander Shepil + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2006 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -53,26 +59,37 @@ namespace Gecode { namespace MiniModel { ANLE_ELMNT, ///< Element expression ANLE_ITE ///< If-then-else expression } t; - /// Expressions - LinIntExpr* a; + /// Boolean expression argument (used in ite for example) + BoolExpr b; /// Size of variable array int n; /// Integer argument (used in nroot for example) int aInt; - /// Boolean expression argument (used in ite for example) - BoolExpr b; + /// Expressions + LinIntExpr* a; + /// Allocate internal expression slots without public default nodes + static LinIntExpr* allocate(int n) { + if (n == 0) + return nullptr; + LinIntExpr* a = static_cast + (heap.ralloc(sizeof(LinIntExpr)*n)); + for (int i=0; i(n0)), n(n0) {} + : t(t0), b(), n(n0), aInt(0), a(allocate(n0)) {} /// Constructor ArithNonLinIntExpr(ArithNonLinIntExprType t0, int n0, int a0) - : t(t0), a(heap.alloc(n0)), n(n0), aInt(a0) {} + : t(t0), b(), n(n0), aInt(a0), a(allocate(n0)) {} /// Constructor ArithNonLinIntExpr(ArithNonLinIntExprType t0, int n0, const BoolExpr& b0) - : t(t0), a(heap.alloc(n0)), n(n0), b(b0) {} + : t(t0), b(b0), n(n0), aInt(0), a(allocate(n0)) {} /// Destructor ~ArithNonLinIntExpr(void) { - heap.free(a,n); + if (a != nullptr) + heap.free(a,n); } /// Post expression virtual IntVar post(Home home, IntVar* ret, @@ -292,6 +309,19 @@ namespace Gecode { namespace MiniModel { dynamic_cast(e.nle())->t == t; } + class ArithNonLinIntExprGuard { + private: + ArithNonLinIntExpr* e; + public: + ArithNonLinIntExprGuard(ArithNonLinIntExpr* e0) : e(e0) {} + ~ArithNonLinIntExprGuard(void) { + delete e; + } + void release(void) { + e = nullptr; + } + }; + }} namespace Gecode { @@ -303,8 +333,11 @@ namespace Gecode { return e; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_ABS,1); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } LinIntExpr @@ -321,6 +354,7 @@ namespace Gecode { n += 1; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_MIN,n); + ArithNonLinIntExprGuard g(ae); int i=0; if (hasType(e0, ArithNonLinIntExpr::ANLE_MIN)) { ArithNonLinIntExpr* e0e = static_cast(e0.nle()); @@ -337,7 +371,9 @@ namespace Gecode { } else { ae->a[i++] = e1; } - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } LinIntExpr @@ -354,6 +390,7 @@ namespace Gecode { n += 1; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_MAX,n); + ArithNonLinIntExprGuard g(ae); int i=0; if (hasType(e0, ArithNonLinIntExpr::ANLE_MAX)) { ArithNonLinIntExpr* e0e = static_cast(e0.nle()); @@ -370,7 +407,9 @@ namespace Gecode { } else { ae->a[i++] = e1; } - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } LinIntExpr @@ -378,9 +417,12 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_MIN,x.size()); + ArithNonLinIntExprGuard g(ae); for (int i=x.size(); i--;) ae->a[i] = x[i]; - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } LinIntExpr @@ -388,9 +430,12 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_MAX,x.size()); + ArithNonLinIntExprGuard g(ae); for (int i=x.size(); i--;) ae->a[i] = x[i]; - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } LinIntExpr @@ -398,9 +443,12 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_MULT,2); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e0; ae->a[1] = e1; - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } LinIntExpr @@ -408,8 +456,11 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_SQR,1); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } LinIntExpr @@ -417,8 +468,11 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_SQRT,1); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } LinIntExpr @@ -426,8 +480,11 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_POW,1,n); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } LinIntExpr @@ -435,8 +492,11 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_NROOT,1,n); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e; - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } LinIntExpr @@ -444,9 +504,12 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_DIV,2); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e0; ae->a[1] = e1; - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } LinIntExpr @@ -454,9 +517,12 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_MOD,2); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e0; ae->a[1] = e1; - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } LinIntExpr @@ -464,10 +530,13 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_ELMNT,x.size()+1); + ArithNonLinIntExprGuard g(ae); for (int i=x.size(); i--;) ae->a[i] = x[i]; ae->a[x.size()] = e; - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } LinIntExpr @@ -475,10 +544,13 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_ELMNT,x.size()+1); + ArithNonLinIntExprGuard g(ae); for (int i=x.size(); i--;) ae->a[i] = x[i]; ae->a[x.size()] = e; - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } LinIntExpr @@ -486,9 +558,12 @@ namespace Gecode { using namespace MiniModel; ArithNonLinIntExpr* ae = new ArithNonLinIntExpr(ArithNonLinIntExpr::ANLE_ITE,2,b); + ArithNonLinIntExprGuard g(ae); ae->a[0] = e0; ae->a[1] = e1; - return LinIntExpr(ae); + g.release(); + LinIntExpr r(ae); + return r; } } diff --git a/gecode/minimodel/int-expr.cpp b/gecode/minimodel/int-expr.cpp index b499d8d3d2..9f5786887a 100755 --- a/gecode/minimodel/int-expr.cpp +++ b/gecode/minimodel/int-expr.cpp @@ -3,8 +3,14 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Alexander Shepil + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2010 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -83,6 +89,10 @@ namespace Gecode { static void* operator new(size_t size); /// Memory management static void operator delete(void* p,size_t size); +#ifdef GECODE_HAS_FAULT_INJECTION + /// Number of live nodes for fault-injection tests + static std::atomic fault_live_nodes; +#endif }; /* @@ -93,6 +103,20 @@ namespace Gecode { LinIntExpr::Node::Node(void) : use(1) { } +#ifdef GECODE_HAS_FAULT_INJECTION + std::atomic LinIntExpr::Node::fault_live_nodes{0}; + + void + LinIntExpr::fault_reset_allocations(void) { + Node::fault_live_nodes.store(0, std::memory_order_relaxed); + } + + int + LinIntExpr::fault_live_allocations(void) { + return Node::fault_live_nodes.load(std::memory_order_relaxed); + } +#endif + forceinline LinIntExpr::Node::~Node(void) { switch (t) { @@ -113,11 +137,21 @@ namespace Gecode { forceinline void* LinIntExpr::Node::operator new(size_t size) { - return heap.ralloc(size); +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::MiniModel); +#endif + void* p = heap.ralloc(size); +#ifdef GECODE_HAS_FAULT_INJECTION + fault_live_nodes.fetch_add(1, std::memory_order_relaxed); +#endif + return p; } forceinline void LinIntExpr::Node::operator delete(void* p, size_t) { +#ifdef GECODE_HAS_FAULT_INJECTION + fault_live_nodes.fetch_sub(1, std::memory_order_relaxed); +#endif heap.rfree(p); } bool @@ -139,7 +173,8 @@ namespace Gecode { LinIntExpr::LinIntExpr(const LinIntExpr& e) : n(e.n) { - n->use++; + if (n != nullptr) + n->use++; } int @@ -357,15 +392,19 @@ namespace Gecode { NonLinIntExpr* LinIntExpr::nle(void) const { - return n->t == NT_NONLIN ? n->sum.ne : nullptr; + return ((n != nullptr) && (n->t == NT_NONLIN)) ? n->sum.ne : nullptr; } + LinIntExpr::LinIntExpr(NoNode) : + n(nullptr) {} + LinIntExpr::LinIntExpr(void) : n(new Node) { n->n_int = n->n_bool = 0; - n->t = NT_VAR_INT; + n->t = NT_CONST; n->l = n->r = nullptr; n->a = 0; + n->c = 0; } LinIntExpr::LinIntExpr(int c) : @@ -400,65 +439,91 @@ namespace Gecode { LinIntExpr::LinIntExpr(const IntVarArgs& x) : n(new Node) { - n->n_int = x.size(); + n->n_int = 0; n->n_bool = 0; n->t = NT_SUM_INT; n->l = n->r = nullptr; - if (x.size() > 0) { - n->sum.ti = heap.alloc >(x.size()); - for (int i=x.size(); i--; ) { - n->sum.ti[i].x = x[i]; - n->sum.ti[i].a = 1; + try { + if (x.size() > 0) { + n->sum.ti = heap.alloc >(x.size()); + n->n_int = x.size(); + for (int i=x.size(); i--; ) { + n->sum.ti[i].x = x[i]; + n->sum.ti[i].a = 1; + } } + } catch (...) { + delete n; + throw; } } LinIntExpr::LinIntExpr(const IntArgs& a, const IntVarArgs& x) : - n(new Node) { + n(nullptr) { if (a.size() != x.size()) throw Int::ArgumentSizeMismatch("MiniModel::LinIntExpr"); - n->n_int = x.size(); + n = new Node; + n->n_int = 0; n->n_bool = 0; n->t = NT_SUM_INT; n->l = n->r = nullptr; - if (x.size() > 0) { - n->sum.ti = heap.alloc >(x.size()); - for (int i=x.size(); i--; ) { - n->sum.ti[i].x = x[i]; - n->sum.ti[i].a = a[i]; + try { + if (x.size() > 0) { + n->sum.ti = heap.alloc >(x.size()); + n->n_int = x.size(); + for (int i=x.size(); i--; ) { + n->sum.ti[i].x = x[i]; + n->sum.ti[i].a = a[i]; + } } + } catch (...) { + delete n; + throw; } } LinIntExpr::LinIntExpr(const BoolVarArgs& x) : n(new Node) { n->n_int = 0; - n->n_bool = x.size(); + n->n_bool = 0; n->t = NT_SUM_BOOL; n->l = n->r = nullptr; - if (x.size() > 0) { - n->sum.tb = heap.alloc >(x.size()); - for (int i=x.size(); i--; ) { - n->sum.tb[i].x = x[i]; - n->sum.tb[i].a = 1; + try { + if (x.size() > 0) { + n->sum.tb = heap.alloc >(x.size()); + n->n_bool = x.size(); + for (int i=x.size(); i--; ) { + n->sum.tb[i].x = x[i]; + n->sum.tb[i].a = 1; + } } + } catch (...) { + delete n; + throw; } } LinIntExpr::LinIntExpr(const IntArgs& a, const BoolVarArgs& x) : - n(new Node) { + n(nullptr) { if (a.size() != x.size()) throw Int::ArgumentSizeMismatch("MiniModel::LinIntExpr"); + n = new Node; n->n_int = 0; - n->n_bool = x.size(); + n->n_bool = 0; n->t = NT_SUM_BOOL; n->l = n->r = nullptr; - if (x.size() > 0) { - n->sum.tb = heap.alloc >(x.size()); - for (int i=x.size(); i--; ) { - n->sum.tb[i].x = x[i]; - n->sum.tb[i].a = a[i]; + try { + if (x.size() > 0) { + n->sum.tb = heap.alloc >(x.size()); + n->n_bool = x.size(); + for (int i=x.size(); i--; ) { + n->sum.tb[i].x = x[i]; + n->sum.tb[i].a = a[i]; + } } + } catch (...) { + delete n; + throw; } } @@ -492,7 +557,13 @@ namespace Gecode { } LinIntExpr::LinIntExpr(NonLinIntExpr* e) : - n(new Node) { + n(nullptr) { + try { + n = new Node; + } catch (...) { + delete e; + throw; + } n->n_int = 1; n->n_bool = 0; n->t = NT_NONLIN; @@ -504,15 +575,17 @@ namespace Gecode { const LinIntExpr& LinIntExpr::operator =(const LinIntExpr& e) { if (this != &e) { - if (n->decrement()) + if ((n != nullptr) && n->decrement()) delete n; - n = e.n; n->use++; + n = e.n; + if (n != nullptr) + n->use++; } return *this; } LinIntExpr::~LinIntExpr(void) { - if (n->decrement()) + if ((n != nullptr) && n->decrement()) delete n; } diff --git a/gecode/search.hh b/gecode/search.hh index efd5168fbd..2602fde680 100755 --- a/gecode/search.hh +++ b/gecode/search.hh @@ -6,11 +6,13 @@ * * Contributing authors: * Kevin Leo + * Mikael Zayenz Lagerkvist * Maxim Shishmarev * * Copyright: * Kevin Leo, 2017 * Christian Schulte, 2002 + * Mikael Zayenz Lagerkvist, 2026 * Maxim Shishmarev, 2017 * Guido Tack, 2004 * @@ -43,6 +45,7 @@ #define GECODE_SEARCH_HH #include +#include #include @@ -832,7 +835,7 @@ namespace Gecode { namespace Search { class GECODE_SEARCH_EXPORT NodeStop : public Stop { protected: /// Node limit - unsigned long long int l; + std::atomic l; public: /// Stop if node limit \a l is exceeded NodeStop(unsigned long long int l); @@ -855,7 +858,7 @@ namespace Gecode { namespace Search { class GECODE_SEARCH_EXPORT FailStop : public Stop { protected: /// Failure limit - unsigned long long int l; + std::atomic l; public: /// Stop if failure limit \a l is exceeded FailStop(unsigned long long int l); @@ -876,7 +879,7 @@ namespace Gecode { namespace Search { /// Time when execution should stop Support::Timer t; /// Current limit in milliseconds - double l; + std::atomic l; public: /// Stop if search exceeds \a l milliseconds (from creation of this object) TimeStop(double l); @@ -897,7 +900,7 @@ namespace Gecode { namespace Search { class GECODE_SEARCH_EXPORT RestartStop : public Stop { protected: /// Restart limit - unsigned long long int l; + std::atomic l; public: /// Stop if restart limit \a l is exceeded RestartStop(unsigned long long int l); diff --git a/gecode/search/par/bab.hpp b/gecode/search/par/bab.hpp index b3dd78430e..8e6d3f047d 100755 --- a/gecode/search/par/bab.hpp +++ b/gecode/search/par/bab.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2009 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -331,7 +335,7 @@ namespace Gecode { namespace Search { namespace Par { } } } else if (!path.empty()) { - cur = path.recompute(d,engine().opt().a_d,*this,*best,mark,tracer); + cur = path.recompute(d,engine().opt().a_d,*this,best,mark,tracer); if (cur == nullptr) path.next(); m.release(); diff --git a/gecode/search/par/engine.hh b/gecode/search/par/engine.hh index 7a0bb608d0..e3b59077be 100644 --- a/gecode/search/par/engine.hh +++ b/gecode/search/par/engine.hh @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2009 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -38,6 +42,7 @@ #include #include #include +#include namespace Gecode { namespace Search { namespace Par { @@ -98,7 +103,7 @@ namespace Gecode { namespace Search { namespace Par { }; protected: /// The current command - volatile Cmd _cmd; + std::atomic _cmd; /// Mutex for forcing workers to wait Support::Mutex _m_wait; public: @@ -172,7 +177,7 @@ namespace Gecode { namespace Search { namespace Par { /// Number of busy workers volatile unsigned int n_busy; /// Whether a worker had been stopped - volatile bool has_stopped; + std::atomic has_stopped; /// Whether search state changed such that signal is needed bool signal(void) const; public: diff --git a/gecode/search/par/engine.hpp b/gecode/search/par/engine.hpp index 62d978f3f3..8ff5ed66e9 100644 --- a/gecode/search/par/engine.hpp +++ b/gecode/search/par/engine.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2009 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -55,7 +59,7 @@ namespace Gecode { namespace Search { namespace Par { template forceinline bool Engine::stopped(void) const { - return has_stopped; + return has_stopped.load(std::memory_order_acquire); } @@ -66,18 +70,18 @@ namespace Gecode { namespace Search { namespace Par { template forceinline typename Engine::Cmd Engine::cmd(void) const { - return _cmd; + return _cmd.load(std::memory_order_acquire); } template forceinline void Engine::block(void) { - _cmd = C_WAIT; + _cmd.store(C_WAIT, std::memory_order_release); Support::Thread::acquireGlobalMutex(&_m_wait); } template forceinline void Engine::release(Cmd c) { - _cmd = c; + _cmd.store(c, std::memory_order_release); Support::Thread::releaseGlobalMutex(&_m_wait); } template @@ -114,13 +118,13 @@ namespace Gecode { namespace Search { namespace Par { template forceinline Engine::Engine(const Options& o) - : _opt(o), solutions(heap) { + : _opt(o), _cmd(C_WAIT), solutions(heap) { // Initialize termination information _n_term_not_ack = workers(); _n_not_terminated = workers(); // Initialize search information n_busy = workers(); - has_stopped = false; + has_stopped.store(false, std::memory_order_release); // Initialize reset information _n_reset_not_ack = workers(); } @@ -145,7 +149,8 @@ namespace Gecode { namespace Search { namespace Par { template forceinline bool Engine::signal(void) const { - return solutions.empty() && (n_busy > 0) && !has_stopped; + return solutions.empty() && (n_busy > 0) && + !has_stopped.load(std::memory_order_acquire); } template forceinline void @@ -172,7 +177,7 @@ namespace Gecode { namespace Search { namespace Par { Engine::stop(void) { m_search.acquire(); bool bs = signal(); - has_stopped = true; + has_stopped.store(true, std::memory_order_release); if (bs) e_search.signal(); m_search.release(); @@ -270,10 +275,8 @@ namespace Gecode { namespace Search { namespace Par { * If that is not true any longer, the worker will be asked * again eventually. */ - if (!path.steal()) - return nullptr; m.acquire(); - Space* s = path.steal(*this,d,myt,ot); + Space* s = path.steal() ? path.steal(*this,d,myt,ot) : nullptr; m.release(); // Tell that there will be one more busy worker if (s != nullptr) @@ -305,7 +308,7 @@ namespace Gecode { namespace Search { namespace Par { return s; } // We ignore stopped (it will be reported again if needed) - has_stopped = false; + has_stopped.store(false, std::memory_order_release); // No more solutions? if (n_busy == 0) { m_search.release(); @@ -333,7 +336,7 @@ namespace Gecode { namespace Search { namespace Par { return s; } // No more solutions or stopped? - if ((n_busy == 0) || has_stopped) { + if ((n_busy == 0) || has_stopped.load(std::memory_order_acquire)) { m_search.release(); // Make workers wait again block(); diff --git a/gecode/search/par/path.hh b/gecode/search/par/path.hh index c9017777f8..476825fd84 100644 --- a/gecode/search/par/path.hh +++ b/gecode/search/par/path.hh @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2003 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -143,7 +147,7 @@ namespace Gecode { namespace Search { namespace Par { Tracer& t); /// Recompute space according to path Space* recompute(unsigned int& d, unsigned int a_d, Worker& s, - const Space& best, int& mark, + const Space* best, int& mark, Tracer& t); /// Return number of entries on stack int entries(void) const; diff --git a/gecode/search/par/path.hpp b/gecode/search/par/path.hpp index a9b321b46a..d027e2feb4 100644 --- a/gecode/search/par/path.hpp +++ b/gecode/search/par/path.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2003 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -353,7 +357,7 @@ namespace Gecode { namespace Search { namespace Par { template forceinline Space* Path::recompute(unsigned int& d, unsigned int a_d, Worker& stat, - const Space& best, int& mark, + const Space* best, int& mark, Tracer& t) { assert(!ds.empty()); // Recompute space according to path @@ -364,9 +368,9 @@ namespace Gecode { namespace Search { namespace Par { Space* s = ds.top().space(); s->commit(*ds.top().choice(),ds.top().alt()); assert(ds.entries()-1 == lc()); - if (mark > ds.entries()-1) { + if ((best != nullptr) && (mark > ds.entries()-1)) { mark = ds.entries()-1; - s->constrain(best); + s->constrain(*best); } ds.top().space(nullptr); // Mark as reusable @@ -383,9 +387,9 @@ namespace Gecode { namespace Search { namespace Par { Space* s = ds[l].space(); // Last clone - if (l < mark) { + if ((best != nullptr) && (l < mark)) { mark = l; - s->constrain(best); + s->constrain(*best); // The space on the stack could be failed now as an additional // constraint might have been added. if (s->status(stat) == SS_FAILED) { diff --git a/gecode/search/par/pbs.cpp b/gecode/search/par/pbs.cpp index 2c46677a39..a05deeeda1 100644 --- a/gecode/search/par/pbs.cpp +++ b/gecode/search/par/pbs.cpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2015 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -37,7 +41,8 @@ namespace Gecode { namespace Search { namespace Par { bool PortfolioStop::stop(const Statistics& s, const Options& o) { - return *tostop || ((so != nullptr) && so->stop(s,o)); + return tostop->load(std::memory_order_acquire) || + ((so != nullptr) && so->stop(s,o)); } }}} diff --git a/gecode/search/par/pbs.hh b/gecode/search/par/pbs.hh index d8238513e5..561969bbe6 100644 --- a/gecode/search/par/pbs.hh +++ b/gecode/search/par/pbs.hh @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2015 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -35,6 +39,7 @@ #define GECODE_SEARCH_PAR_PBS_HH #include +#include namespace Gecode { namespace Search { namespace Par { @@ -44,12 +49,12 @@ namespace Gecode { namespace Search { namespace Par { /// The stop object for the slaves Stop* so; /// Whether search must be stopped - volatile bool* tostop; + std::atomic* tostop; public: /// Initialize PortfolioStop(Stop* so); /// Set pointer to shared \a tostop variable - void share(volatile bool* ts); + void share(std::atomic* ts); /// Return true if portfolio engine must be stopped virtual bool stop(const Statistics& s, const Options& o); /// Signal whether search must be stopped @@ -147,9 +152,9 @@ namespace Gecode { namespace Search { namespace Par { /// Number of active slave engines unsigned int n_active; /// Whether a slave has been stopped - bool slave_stop; + std::atomic slave_stop; /// Shared stop flag - volatile bool tostop; + std::atomic tostop; /// Collect solutions in this Collect solutions; /// Mutex for synchronization diff --git a/gecode/search/par/pbs.hpp b/gecode/search/par/pbs.hpp index 39e3628ec1..39c18d44a2 100755 --- a/gecode/search/par/pbs.hpp +++ b/gecode/search/par/pbs.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2015 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -118,7 +122,7 @@ namespace Gecode { namespace Search { namespace Par { : so(so0), tostop(nullptr) {} forceinline void - PortfolioStop::share(volatile bool* ts) { + PortfolioStop::share(std::atomic* ts) { tostop = ts; } @@ -174,10 +178,10 @@ namespace Gecode { namespace Search { namespace Par { if (s != nullptr) { b = solutions.add(s,slave); if (b) - tostop = true; + tostop.store(true, std::memory_order_release); } else if (slave->stopped()) { - if (!tostop) - slave_stop = true; + if (!tostop.load(std::memory_order_acquire)) + slave_stop.store(true, std::memory_order_release); } else { // Move slave to inactive, as it has exhausted its engine unsigned int i=0; @@ -186,7 +190,7 @@ namespace Gecode { namespace Search { namespace Par { assert(i < n_active); assert(n_active > 0); std::swap(slaves[i],slaves[--n_active]); - tostop = true; + tostop.store(true, std::memory_order_release); } if (b) { if (--n_busy == 0) @@ -211,12 +215,12 @@ namespace Gecode { namespace Search { namespace Par { m.acquire(); if (solutions.empty()) { // Clear all - tostop = false; - slave_stop = false; + tostop.store(false, std::memory_order_release); + slave_stop.store(false, std::memory_order_release); // Invariant: all slaves are idle! assert(n_busy == 0); - assert(!tostop); + assert(!tostop.load(std::memory_order_acquire)); if (n_active > 0) { // Run all active slaves @@ -254,7 +258,7 @@ namespace Gecode { namespace Search { namespace Par { template bool PBS::stopped(void) const { - return slave_stop; + return slave_stop.load(std::memory_order_acquire); } template @@ -283,6 +287,8 @@ namespace Gecode { namespace Search { namespace Par { template PBS::~PBS(void) { assert(n_busy == 0); + for (unsigned int i=0U; i*>(slaves,n_slaves); } diff --git a/gecode/search/seq/bab.hpp b/gecode/search/seq/bab.hpp index b744685024..3138fd371e 100644 --- a/gecode/search/seq/bab.hpp +++ b/gecode/search/seq/bab.hpp @@ -5,10 +5,12 @@ * * Contributing authors: * Guido Tack + * Mikael Zayenz Lagerkvist * * Copyright: * Christian Schulte, 2004 * Guido Tack, 2004 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -83,7 +85,7 @@ namespace Gecode { namespace Search { namespace Seq { while (cur == nullptr) { if (path.empty()) return nullptr; - cur = path.recompute(d,opt.a_d,*this,*best,mark,tracer); + cur = path.recompute(d,opt.a_d,*this,best,mark,tracer); if (cur != nullptr) break; path.next(); diff --git a/gecode/search/seq/path.hh b/gecode/search/seq/path.hh index fcc46088cf..18c21022ea 100644 --- a/gecode/search/seq/path.hh +++ b/gecode/search/seq/path.hh @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2003 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -137,7 +141,7 @@ namespace Gecode { namespace Search { namespace Seq { Tracer& t); /// Recompute space according to path Space* recompute(unsigned int& d, unsigned int a_d, Worker& s, - const Space& best, int& mark, + const Space* best, int& mark, Tracer& t); /// Return number of entries on stack int entries(void) const; diff --git a/gecode/search/seq/path.hpp b/gecode/search/seq/path.hpp index efbc7a783b..4a4cddd157 100644 --- a/gecode/search/seq/path.hpp +++ b/gecode/search/seq/path.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2003 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -289,7 +293,7 @@ namespace Gecode { namespace Search { namespace Seq { template forceinline Space* Path::recompute(unsigned int& d, unsigned int a_d, Worker& stat, - const Space& best, int& mark, + const Space* best, int& mark, Tracer& t) { assert(!ds.empty()); // Recompute space according to path @@ -300,9 +304,9 @@ namespace Gecode { namespace Search { namespace Seq { Space* s = ds.top().space(); s->commit(*ds.top().choice(),ds.top().alt()); assert(ds.entries()-1 == lc()); - if (mark > ds.entries()-1) { + if ((best != nullptr) && (mark > ds.entries()-1)) { mark = ds.entries()-1; - s->constrain(best); + s->constrain(*best); } ds.top().space(nullptr); // Mark as reusable @@ -319,9 +323,9 @@ namespace Gecode { namespace Search { namespace Seq { Space* s = ds[l].space(); // Last clone - if (l < mark) { + if ((best != nullptr) && (l < mark)) { mark = l; - s->constrain(best); + s->constrain(*best); // The space on the stack could be failed now as an additional // constraint might have been added. if (s->status(stat) == SS_FAILED) { diff --git a/gecode/search/seq/rbs.cpp b/gecode/search/seq/rbs.cpp index 98007145db..033c525b1d 100755 --- a/gecode/search/seq/rbs.cpp +++ b/gecode/search/seq/rbs.cpp @@ -3,8 +3,12 @@ * Main authors: * Guido Tack * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Guido Tack, 2012 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -38,10 +42,12 @@ namespace Gecode { namespace Search { namespace Seq { bool RestartStop::stop(const Statistics& s, const Options& o) { + Support::Lock lock(m); // Stop if the fail limit for the engine says so if (s.fail > l) { + if (!e_stopped) + m_stat.restart++; e_stopped = true; - m_stat.restart++; return true; } // Stop if the stop object for the meta engine says so @@ -60,10 +66,10 @@ namespace Gecode { namespace Search { namespace Seq { NoGoods& ng = e->nogoods(); // Reset number of no-goods found ng.ng(0); - MetaInfo mi(stop->m_stat.restart,MetaInfo::RR_SOL,sslr,e->statistics().fail,last,ng); + MetaInfo mi(stop->restarts(),MetaInfo::RR_SOL,sslr,e->statistics().fail,last,ng); bool r = master->master(mi); - stop->m_stat.nogood += ng.ng(); - if (master->status(stop->m_stat) == SS_FAILED) { + stop->nogood(ng.ng()); + if (stop->status(master) == SS_FAILED) { stop->update(e->statistics()); delete master; master = nullptr; @@ -76,7 +82,7 @@ namespace Gecode { namespace Search { namespace Seq { complete = slave->slave(mi); e->reset(slave); sslr = 0; - stop->m_stat.restart++; + stop->restart(); } } while (true) { @@ -92,16 +98,16 @@ namespace Gecode { namespace Search { namespace Seq { // The engine must perform a true restart // The number of the restart has been incremented in the stop object if (!complete && !e->stopped()) - stop->m_stat.restart++; + stop->restart(); sslr = 0; NoGoods& ng = e->nogoods(); ng.ng(0); - MetaInfo mi(stop->m_stat.restart,e->stopped() ? MetaInfo::RR_LIM : MetaInfo::RR_CMPL,sslr,e->statistics().fail,last,ng); + MetaInfo mi(stop->restarts(),e->stopped() ? MetaInfo::RR_LIM : MetaInfo::RR_CMPL,sslr,e->statistics().fail,last,ng); (void) master->master(mi); - stop->m_stat.nogood += ng.ng(); + stop->nogood(ng.ng()); unsigned long long int nl = ++(*co); stop->limit(e->statistics(),nl); - if (master->status(stop->m_stat) == SS_FAILED) + if (stop->status(master) == SS_FAILED) return nullptr; Space* slave = master; master = master->clone(); diff --git a/gecode/search/seq/rbs.hh b/gecode/search/seq/rbs.hh index 1828c2f33f..6b506200de 100755 --- a/gecode/search/seq/rbs.hh +++ b/gecode/search/seq/rbs.hh @@ -3,8 +3,12 @@ * Main authors: * Guido Tack * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Guido Tack, 2012 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -45,6 +49,8 @@ namespace Gecode { namespace Search { namespace Seq { templateclass> friend class ::Gecode::RBS; friend class ::Gecode::Search::Seq::RBS; private: + /// Mutex protecting restart stop state + mutable Support::Mutex m; /// The failure limit for the engine unsigned long long int l; /// The stop object for the meta engine @@ -58,6 +64,14 @@ namespace Gecode { namespace Search { namespace Seq { RestartStop(Stop* s); /// Return true if meta engine must be stopped virtual bool stop(const Statistics& s, const Options& o); + /// Return current restart count + unsigned long int restarts(void) const; + /// Increment current restart count + void restart(void); + /// Add no-goods to meta statistics + void nogood(unsigned long int n); + /// Test master status with meta statistics + SpaceStatus status(Space* s); /// Set current limit for the engine to \a l fails void limit(const Statistics& s, unsigned long long int l); /// Update statistics diff --git a/gecode/search/seq/rbs.hpp b/gecode/search/seq/rbs.hpp index 179a5c1f84..c55e4e28b5 100755 --- a/gecode/search/seq/rbs.hpp +++ b/gecode/search/seq/rbs.hpp @@ -3,8 +3,12 @@ * Main authors: * Guido Tack * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Guido Tack, 2012 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -38,8 +42,33 @@ namespace Gecode { namespace Search { namespace Seq { RestartStop::RestartStop(Stop* s) : l(0U), m_stop(s), e_stopped(false) {} + forceinline unsigned long int + RestartStop::restarts(void) const { + Support::Lock lock(m); + return m_stat.restart; + } + + forceinline void + RestartStop::restart(void) { + Support::Lock lock(m); + m_stat.restart++; + } + + forceinline void + RestartStop::nogood(unsigned long int n) { + Support::Lock lock(m); + m_stat.nogood += n; + } + + forceinline SpaceStatus + RestartStop::status(Space* s) { + Support::Lock lock(m); + return s->status(m_stat); + } + forceinline void RestartStop::limit(const Search::Statistics& s, unsigned long long int l0) { + Support::Lock lock(m); l = l0; m_stat += s; e_stopped = false; @@ -47,16 +76,19 @@ namespace Gecode { namespace Search { namespace Seq { forceinline void RestartStop::update(const Search::Statistics& s) { + Support::Lock lock(m); m_stat += s; } forceinline bool RestartStop::enginestopped(void) const { + Support::Lock lock(m); return e_stopped; } forceinline Statistics RestartStop::metastatistics(void) const { + Support::Lock lock(m); return m_stat; } diff --git a/gecode/search/stop.cpp b/gecode/search/stop.cpp index d21cca77da..859e62ae72 100755 --- a/gecode/search/stop.cpp +++ b/gecode/search/stop.cpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2006 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -63,7 +67,7 @@ namespace Gecode { namespace Search { */ bool NodeStop::stop(const Statistics& s, const Options&) { - return s.node > l; + return s.node > l.load(std::memory_order_acquire); } @@ -73,7 +77,7 @@ namespace Gecode { namespace Search { */ bool FailStop::stop(const Statistics& s, const Options&) { - return s.fail > l; + return s.fail > l.load(std::memory_order_acquire); } @@ -83,7 +87,7 @@ namespace Gecode { namespace Search { */ bool TimeStop::stop(const Statistics&, const Options&) { - return t.stop() > l; + return t.stop() > l.load(std::memory_order_acquire); } /* @@ -92,7 +96,7 @@ namespace Gecode { namespace Search { */ bool RestartStop::stop(const Statistics& s, const Options&) { - return s.restart > l; + return s.restart > l.load(std::memory_order_acquire); } }} diff --git a/gecode/search/stop.hpp b/gecode/search/stop.hpp index 3f62484ddc..12c1170fa0 100755 --- a/gecode/search/stop.hpp +++ b/gecode/search/stop.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2006 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -53,12 +57,12 @@ namespace Gecode { namespace Search { forceinline unsigned long long int NodeStop::limit(void) const { - return l; + return l.load(std::memory_order_acquire); } forceinline void NodeStop::limit(unsigned long long int l0) { - l=l0; + l.store(l0, std::memory_order_release); } @@ -72,12 +76,12 @@ namespace Gecode { namespace Search { forceinline unsigned long long int FailStop::limit(void) const { - return l; + return l.load(std::memory_order_acquire); } forceinline void FailStop::limit(unsigned long long int l0) { - l=l0; + l.store(l0, std::memory_order_release); } @@ -94,12 +98,12 @@ namespace Gecode { namespace Search { forceinline double TimeStop::limit(void) const { - return l; + return l.load(std::memory_order_acquire); } forceinline void TimeStop::limit(double l0) { - l=l0; + l.store(l0, std::memory_order_release); } forceinline void @@ -117,12 +121,12 @@ namespace Gecode { namespace Search { forceinline unsigned long long int RestartStop::limit(void) const { - return l; + return l.load(std::memory_order_acquire); } forceinline void RestartStop::limit(unsigned long long int l0) { - l=l0; + l.store(l0, std::memory_order_release); } }} diff --git a/gecode/support.hh b/gecode/support.hh index d78117a33f..79f4894608 100644 --- a/gecode/support.hh +++ b/gecode/support.hh @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2007 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -87,6 +91,9 @@ #include #include +#ifdef GECODE_HAS_FAULT_INJECTION +#include +#endif #include #include #include diff --git a/gecode/support/config.hpp.in b/gecode/support/config.hpp.in index bc78cb03c0..1298f113f9 100644 --- a/gecode/support/config.hpp.in +++ b/gecode/support/config.hpp.in @@ -2,8 +2,12 @@ * Main authors: * Guido Tack * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Guido Tack, 2008 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -108,6 +112,9 @@ /* Whether we are compiling with thread support */ #undef GECODE_HAS_THREADS +/* Whether deterministic test failpoints are enabled */ +#undef GECODE_HAS_FAULT_INJECTION + /* Whether to use unfair mutexes on macOS */ #undef GECODE_USE_OSX_UNFAIR_MUTEX diff --git a/gecode/support/failpoint.cpp b/gecode/support/failpoint.cpp new file mode 100644 index 0000000000..2a5ac355c0 --- /dev/null +++ b/gecode/support/failpoint.cpp @@ -0,0 +1,76 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include + +#ifdef GECODE_HAS_FAULT_INJECTION + +namespace Gecode { namespace Support { namespace FailPoint { + + namespace { + std::atomic fail_after_count; + std::atomic seen_count; + std::atomic active_phase; + } + + void reset(void) { + active_phase.store(static_cast(Phase::Disabled), + std::memory_order_release); + fail_after_count.store(0, std::memory_order_release); + seen_count.store(0, std::memory_order_release); + } + + void fail_after(Phase p, unsigned long long n) { + seen_count.store(0, std::memory_order_release); + fail_after_count.store(n, std::memory_order_release); + active_phase.store(static_cast(p), std::memory_order_release); + } + + void check(Phase p) { + if (active_phase.load(std::memory_order_acquire) != static_cast(p)) + return; + unsigned long long seen = + seen_count.fetch_add(1, std::memory_order_acq_rel); + if (seen >= fail_after_count.load(std::memory_order_acquire)) + throw MemoryExhausted(); + } + + unsigned long long count(void) { + return seen_count.load(std::memory_order_acquire); + } + +}}} + +#endif + +// STATISTICS: support-any diff --git a/gecode/support/failpoint.hpp b/gecode/support/failpoint.hpp new file mode 100644 index 0000000000..f4ab33bb54 --- /dev/null +++ b/gecode/support/failpoint.hpp @@ -0,0 +1,69 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef GECODE_SUPPORT_FAILPOINT_HPP +#define GECODE_SUPPORT_FAILPOINT_HPP + +#include + +namespace Gecode { namespace Support { namespace FailPoint { + + /// Named phase for deterministic test failure injection + enum class Phase { + Disabled, + Heap, + PropagatorCopy, + BrancherCopy, + AdvisorCopy, + DerivedSpaceCopy, + LocalObjectCopy, + SpaceDisposeNoticeArray, + SpaceDisposalArray, + IntSet, + MiniModel + }; + + /// Reset all failpoint state + GECODE_SUPPORT_EXPORT void reset(void); + /// Fail after \a n successful checks for phase \a p + GECODE_SUPPORT_EXPORT void fail_after(Phase p, unsigned long long n); + /// Check failpoint for phase \a p + GECODE_SUPPORT_EXPORT void check(Phase p); + /// Number of checks observed for the configured phase + GECODE_SUPPORT_EXPORT unsigned long long count(void); + +}}} + +#endif + +// STATISTICS: support-any diff --git a/gecode/support/heap.hpp b/gecode/support/heap.hpp index b8a33c4b6a..10721f19e4 100644 --- a/gecode/support/heap.hpp +++ b/gecode/support/heap.hpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2008 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -355,6 +359,9 @@ namespace Gecode { */ forceinline void* Heap::ralloc(size_t s) { +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::Heap); +#endif void* p = Support::allocator.alloc(s); #ifdef GECODE_PEAKHEAP _m.acquire(); @@ -389,6 +396,9 @@ namespace Gecode { forceinline void* Heap::rrealloc(void* p, size_t s) { +#ifdef GECODE_HAS_FAULT_INJECTION + Support::FailPoint::check(Support::FailPoint::Phase::Heap); +#endif #ifdef GECODE_PEAKHEAP _m.acquire(); _cur -= GECODE_MSIZE(p); diff --git a/gecode/support/thread/thread.cpp b/gecode/support/thread/thread.cpp index 7428a1b270..2869aaa20e 100644 --- a/gecode/support/thread/thread.cpp +++ b/gecode/support/thread/thread.cpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2009 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -54,9 +58,10 @@ namespace Gecode { namespace Support { GECODE_ASSUME(r != nullptr); Runnable* e = r.exchange(nullptr); assert(e != nullptr); + const bool delete_after_run = e->todelete(); + Terminator* t = delete_after_run ? e->terminator() : nullptr; e->run(); - if (e->todelete()) { - Terminator* t = e->terminator(); + if (delete_after_run) { delete e; if (t) t->terminated(); diff --git a/misc/genvarimp.py b/misc/genvarimp.py index e32c1ad5f9..a26e607638 100644 --- a/misc/genvarimp.py +++ b/misc/genvarimp.py @@ -23,8 +23,16 @@ * Main author: * Christian Schulte * + * Contributing authors: + * Kris Coester + * Alexander Shepil + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2007 + * Kris Coester, 2024 + * Alexander Shepil, 2024 + * Mikael Zayenz Lagerkvist, 2026 * * The generated code fragments are part of Gecode, the generic * constraint development environment: @@ -855,6 +863,19 @@ def generate_header(files: List[Dict], out: List[str]) -> None: out.append( """namespace Gecode { + forceinline void + Space::recover_noidx(void) { + VarImp* x = + static_cast*>(pc.c.vars_noidx); + while (x != nullptr) { + VarImp* n = x->next(); + x->b.base = nullptr; x->u.idx[0] = 0; + if (sizeof(ActorLink**) > sizeof(unsigned int)) + *(1+&x->u.idx[0]) = 0; + x = n; + } + } + forceinline void Space::update(ActorLink** sub) { """ @@ -867,6 +888,67 @@ def generate_header(files: List[Dict], out: List[str]) -> None: out.append( """ } + + forceinline void + Space::recover(Space& source) { + ActorLink* clone_lists[2] = {&pl, &bl}; + for (int i=0; i<2; i++) { + ActorLink* l = clone_lists[i]; + ActorLink* c = l->next(); + while (c != l) { + ActorLink* n = c->next(); + (void) Actor::cast(c)->dispose(*this); + c = n; + } + l->init(); + } + while (pc.c.local != nullptr) { + LocalObject* l = pc.c.local; + ActorLink* n = l->next(); + pc.c.local = (n == nullptr) ? nullptr : LocalObject::cast(n); + ActorLink* fwd = l->prev(); + if (fwd != nullptr) { + (void) Actor::cast(fwd)->dispose(*this); + l->prev(nullptr); + } + } + + ActorLink* lists[2] = {&source.pl, &source.bl}; + for (int i=0; i<2; i++) { + ActorLink* l = lists[i]; + ActorLink* p_a = l; + ActorLink* c_a = p_a->next(); + while (c_a != l) { + if (i == 0) { + Propagator* p = Propagator::cast(c_a); + if (p->u.advisors != nullptr) { + ActorLink* a = p->u.advisors; + p->u.advisors = nullptr; + do { + a->prev(p); a = a->next(); + } while (a != nullptr); + } + } + ActorLink* fwd = c_a->prev(); + if (fwd != p_a) { + c_a->prev(p_a); + } + p_a = c_a; c_a = c_a->next(); + } + } + + ActorLink** sub = static_cast(mm.subscriptions()); + recover_noidx(); +""" + ) + + for d in files: + out.append(d["ifdef"]) + out.append(f" {d['base']}::recover(*this,sub);\n") + out.append(d["endif"]) + + out.append( + """ } } """ ) diff --git a/test/fault.cpp b/test/fault.cpp new file mode 100644 index 0000000000..6adad8f6b6 --- /dev/null +++ b/test/fault.cpp @@ -0,0 +1,1203 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include + +#include "test/test.hh" + +namespace Test { namespace Fault { + + using namespace Gecode; + using Gecode::Support::FailPoint::Phase; + + Support::Mutex fault_mutex; + + class FaultScope { + private: + Support::Lock lock; + public: + FaultScope(void) : lock(fault_mutex) { + Support::FailPoint::reset(); + } + ~FaultScope(void) { + Support::FailPoint::reset(); + } + }; + + class ThrowingPropagator : public Propagator { + protected: + ThrowingPropagator(Home home) : Propagator(home) {} + ThrowingPropagator(Space& home, ThrowingPropagator& p) + : Propagator(home,p) { + Support::FailPoint::check(Phase::PropagatorCopy); + } + public: + static void post(Home home) { + (void) new (home) ThrowingPropagator(home); + } + virtual Actor* copy(Space& home) { + return new (home) ThrowingPropagator(home,*this); + } + virtual PropCost cost(const Space&, const ModEventDelta&) const { + return PropCost::unary(PropCost::LO); + } + virtual void reschedule(Space&) {} + virtual ExecStatus propagate(Space& home, const ModEventDelta&) { + return home.ES_SUBSUMED(*this); + } + virtual size_t dispose(Space& home) { + (void) Propagator::dispose(home); + return sizeof(*this); + } + }; + + class AdvisorCopyPropagator; + + class ThrowingAdvisor : public Advisor { + protected: + int id; + public: + ThrowingAdvisor(Space& home, Propagator& p, + Council& c, int id0) + : Advisor(home,p,c), id(id0) {} + ThrowingAdvisor(Space& home, ThrowingAdvisor& a) + : Advisor(home,a), id(a.id) { + Support::FailPoint::check(Phase::AdvisorCopy); + } + Propagator* owner(void) const { + return &propagator(); + } + void dispose(Space& home, Council& c) { + Advisor::dispose(home,c); + } + }; + + class AdvisorCopyPropagator : public Propagator { + protected: + Council c; + AdvisorCopyPropagator(Home home) : Propagator(home), c(home) { + (void) new (home) ThrowingAdvisor(home,*this,c,0); + (void) new (home) ThrowingAdvisor(home,*this,c,1); + } + AdvisorCopyPropagator(Space& home, AdvisorCopyPropagator& p) + : Propagator(home,p) { + c.update(home,p.c); + } + public: + static AdvisorCopyPropagator* post(Home home) { + return new (home) AdvisorCopyPropagator(home); + } + bool advisors_point_to_self(void) const { + Advisors as(c); + int n = 0; + while (as()) { + if (as.advisor().owner() != this) + return false; + ++n; ++as; + } + return n == 2; + } + virtual Actor* copy(Space& home) { + return new (home) AdvisorCopyPropagator(home,*this); + } + virtual PropCost cost(const Space&, const ModEventDelta&) const { + return PropCost::unary(PropCost::LO); + } + virtual void reschedule(Space&) {} + virtual ExecStatus propagate(Space&, const ModEventDelta&) { + return ES_FIX; + } + virtual size_t dispose(Space& home) { + c.dispose(home); + (void) Propagator::dispose(home); + return sizeof(*this); + } + }; + + class ThrowingChoice : public Choice { + public: + ThrowingChoice(const Brancher& b) : Choice(b,1) {} + virtual void archive(Archive& e) const { + Choice::archive(e); + } + }; + + class ThrowingBrancher : public Brancher { + protected: + bool done; + ThrowingBrancher(Home home) : Brancher(home), done(false) {} + ThrowingBrancher(Space& home, ThrowingBrancher& b) + : Brancher(home,b), done(b.done) { + Support::FailPoint::check(Phase::BrancherCopy); + } + public: + static void post(Home home) { + (void) new (home) ThrowingBrancher(home); + } + virtual bool status(const Space&) const { + return !done; + } + virtual const Choice* choice(Space&) { + return new ThrowingChoice(*this); + } + virtual const Choice* choice(const Space&, Archive&) { + return new ThrowingChoice(*this); + } + virtual ExecStatus commit(Space&, const Choice&, unsigned int) { + done = true; + return ES_OK; + } + virtual void print(const Space&, const Choice&, + unsigned int, std::ostream&) const {} + virtual Actor* copy(Space& home) { + return new (home) ThrowingBrancher(home,*this); + } + virtual size_t dispose(Space&) { + return sizeof(*this); + } + }; + + class CloneCopySpace : public Space { + public: + CloneCopySpace(void) { + ThrowingPropagator::post(*this); + ThrowingBrancher::post(*this); + } + CloneCopySpace(CloneCopySpace& s) : Space(s) {} + virtual Space* copy(void) { + return new CloneCopySpace(*this); + } + }; + + class DisposeSpace : public Space { + public: + IntVarArray x; + DisposeSpace(void) : x(*this,3,0,2) { + branch(*this,x,INT_VAR_ACTION_SIZE_MAX(),INT_VAL_MIN()); + } + DisposeSpace(DisposeSpace& s) : Space(s) { + x.update(*this,s.x); + } + virtual Space* copy(void) { + return new DisposeSpace(*this); + } + }; + + class NoticeDisposeActor : public Actor { + public: + static int disposed; + + NoticeDisposeActor(Home home) : Actor() { + home.notice(*this,AP_DISPOSE); + } + static void post(Home home) { + (void) new (home) NoticeDisposeActor(home); + } + virtual Actor* copy(Space& home) { + return new (home) NoticeDisposeActor(home); + } + virtual size_t dispose(Space& home) { + home.ignore(*this,AP_DISPOSE); + disposed++; + return sizeof(*this); + } + }; + + int NoticeDisposeActor::disposed = 0; + + class NoticeDisposeSpace : public Space { + public: + NoticeDisposeSpace(int registered = 0, bool fail_next = false) { + for (int i=0; i(object())->value; + } + }; + + class LocalCopySpace : public Space { + public: + FaultLocalHandle h; + LocalCopySpace(void) + : h(new (*this) FaultLocalObject(*this,42)) {} + LocalCopySpace(LocalCopySpace& s) : Space(s) { + h.update(*this,s.h); + } + virtual Space* copy(void) { + return new LocalCopySpace(*this); + } + }; + + class AdvisorCopySpace : public Space { + public: + AdvisorCopyPropagator* p; + AdvisorCopySpace(void) + : p(AdvisorCopyPropagator::post(*this)) {} + AdvisorCopySpace(AdvisorCopySpace& s) : Space(s), p(nullptr) {} + virtual Space* copy(void) { + return new AdvisorCopySpace(*this); + } + bool advisors_point_to_source(void) const { + return (p != nullptr) && p->advisors_point_to_self(); + } + }; + + class BranchPostSpace : public Space { + public: + IntVarArray x; + BranchPostSpace(void) : x(*this,4,0,3) { + rel(*this,x[0] != x[1]); + } + BranchPostSpace(BranchPostSpace& s) : Space(s) { + x.update(*this,s.x); + } + virtual Space* copy(void) { + return new BranchPostSpace(*this); + } + void post_action_branch(void) { + branch(*this,x,INT_VAR_ACTION_SIZE_MAX(),INT_VAL_MIN()); + } + void post_chb_branch(void) { + branch(*this,x,INT_VAR_CHB_SIZE_MAX(),INT_VAL_MIN()); + } + void post_plain_branch(void) { + branch(*this,x,INT_VAR_SIZE_MIN(),INT_VAL_MIN()); + } + }; + + bool clone_after_failed_copy(Phase p) { + FaultScope scope; + CloneCopySpace s; + if (s.status() == SS_FAILED) + return false; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(p,0); + try { + Space* c = s.clone(); + delete c; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + } catch (...) { + Support::FailPoint::reset(); + return false; + } + Space* c = s.clone(); + delete c; + const Choice* ch = s.choice(); + bool ok = ch != nullptr; + if (ch != nullptr) { + s.commit(*ch,0); + delete ch; + } + return ok && (s.status() != SS_FAILED); + } + + bool expect_memory_exhausted(Phase p, unsigned long long n) { + FaultScope scope; + DerivedCopySpace s; + if (s.status() == SS_FAILED) + return false; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(p,n); + try { + Space* c = s.clone(); + delete c; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + Space* c = s.clone(); + delete c; + const Choice* ch = s.choice(); + bool ok = ch != nullptr; + if (ch != nullptr) { + s.commit(*ch,0); + delete ch; + } + return ok && (s.status() != SS_FAILED); + } catch (...) { + Support::FailPoint::reset(); + return false; + } + Support::FailPoint::reset(); + return false; + } + + bool clone_after_failed_local_object_copy(void) { + FaultScope scope; + LocalCopySpace s; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::LocalObjectCopy,0); + try { + Space* c = s.clone(); + delete c; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + if (s.h.value() != 42) + return false; + LocalCopySpace* c = static_cast(s.clone()); + bool ok = (c != nullptr) && (c->h.value() == 42); + delete c; + return ok; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + Support::FailPoint::reset(); + return false; + } + + bool clone_after_failed_advisor_copy(void) { + FaultScope scope; + AdvisorCopySpace s; + if (!s.advisors_point_to_source()) + return false; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::AdvisorCopy,1); + try { + Space* c = s.clone(); + delete c; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + if (!s.advisors_point_to_source()) + return false; + AdvisorCopySpace* c = static_cast(s.clone()); + bool ok = (c != nullptr); + delete c; + return ok && s.advisors_point_to_source(); + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + + bool clone_after_failed_disposal_array(void) { + FaultScope scope; + DisposeSpace s; + if (s.status() == SS_FAILED) + return false; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::SpaceDisposalArray,0); + try { + Space* c = s.clone(); + delete c; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + } catch (...) { + Support::FailPoint::reset(); + return false; + } + Space* c = s.clone(); + delete c; + DisposeSpace* root = static_cast(s.clone()); + DFS e(root); + Space* sol = e.next(); + bool ok = (sol != nullptr); + delete sol; + return ok; + } + + bool derived_update_space_is_usable(DerivedUpdateSpace& s) { + Space* c = s.clone(); + delete c; + DerivedUpdateSpace* root = static_cast(s.clone()); + DFS e(root); + Space* sol = e.next(); + bool ok = (sol != nullptr); + delete sol; + return ok; + } + + bool clone_after_failed_derived_update(void) { + FaultScope scope; + DerivedUpdateSpace s; + if (s.status() == SS_FAILED) + return false; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::DerivedSpaceCopy,0); + try { + Space* c = s.clone(); + delete c; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + } catch (...) { + Support::FailPoint::reset(); + return false; + } + return derived_update_space_is_usable(s); + } + + bool heap_allocation_failpoint_throws(void) { + FaultScope scope; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::Heap,0); + try { + int* x = heap.alloc(1); + heap.free(x,1); + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + return true; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + + bool heap_reallocation_failpoint_throws(void) { + FaultScope scope; + int* x = heap.alloc(1); + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::Heap,0); + try { + int* y = heap.realloc(x,1,2); + Support::FailPoint::reset(); + heap.free(y,2); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + heap.free(x,1); + return true; + } catch (...) { + Support::FailPoint::reset(); + heap.free(x,1); + return false; + } + } + + bool clone_heap_failures_recover_source(void) { + FaultScope scope; + DerivedUpdateSpace s; + if (s.status() == SS_FAILED) + return false; + bool saw_failure = false; + for (unsigned long long budget = 0; budget < 32; budget++) { + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::Heap,budget); + try { + Space* c = s.clone(); + unsigned long long checks = Support::FailPoint::count(); + Support::FailPoint::reset(); + delete c; + return saw_failure && (checks > 0) && derived_update_space_is_usable(s); + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + saw_failure = true; + if (!derived_update_space_is_usable(s)) + return false; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + return false; + } + + bool branch_post_heap_failures_are_recoverable(bool chb) { + FaultScope scope; + bool saw_failure = false; + for (unsigned long long budget = 0; budget < 32; budget++) { + BranchPostSpace s; + if (s.status() == SS_FAILED) + return false; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::Heap,budget); + try { + if (chb) + s.post_chb_branch(); + else + s.post_action_branch(); + unsigned long long checks = Support::FailPoint::count(); + Support::FailPoint::reset(); + BranchPostSpace* root = static_cast(s.clone()); + DFS e(root); + Space* sol = e.next(); + bool ok = (sol != nullptr); + delete sol; + return saw_failure && (checks > 0) && ok; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + saw_failure = true; + s.post_plain_branch(); + BranchPostSpace* root = static_cast(s.clone()); + DFS e(root); + Space* sol = e.next(); + bool ok = (sol != nullptr); + delete sol; + if (!ok) + return false; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + return false; + } + + bool int_set_heap_failures_release_object(void) { + FaultScope scope; + int ranges[3][2] = {{0, 0}, {2, 2}, {4, 4}}; + bool saw_failure = false; + for (unsigned long long budget = 0; budget < 8; budget++) { + IntSet::fault_reset_allocations(); + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::Heap,budget); + try { + unsigned long long checks; + { + IntSet s(ranges,3); + checks = Support::FailPoint::count(); + } + Support::FailPoint::reset(); + return saw_failure && (checks > 0) && + (IntSet::fault_live_allocations() == 0); + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + saw_failure = true; + if (IntSet::fault_live_allocations() != 0) + return false; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + return false; + } + + bool minimodel_heap_failures_release_nodes(void) { + FaultScope scope; + bool saw_failure = false; + for (unsigned long long budget = 0; budget < 32; budget++) { + LinIntExpr::fault_reset_allocations(); + Support::FailPoint::reset(); + try { + MiniModelSpace home; + IntVarArgs x(home,4,0,3); + Support::FailPoint::fail_after(Phase::Heap,budget); + { + LinIntExpr e = min(x); + (void) e; + } + unsigned long long checks = Support::FailPoint::count(); + Support::FailPoint::reset(); + return saw_failure && (checks > 0) && + (LinIntExpr::fault_live_allocations() == 0); + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + saw_failure = true; + if (LinIntExpr::fault_live_allocations() != 0) + return false; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + return false; + } + + bool linear_sum_constructor_heap_failures_release_nodes(int kind) { + FaultScope scope; + bool saw_failure = false; + for (unsigned long long budget = 0; budget < 8; budget++) { + LinIntExpr::fault_reset_allocations(); + Support::FailPoint::reset(); + try { + MiniModelSpace home; + IntVarArgs x(home,4,0,3); + BoolVarArgs b(home,4,0,1); + IntArgs a({1,2,3,4}); + Support::FailPoint::fail_after(Phase::Heap,budget); + switch (kind) { + case 0: + { + LinIntExpr e(x); + (void) e; + } + break; + case 1: + { + LinIntExpr e(a,x); + (void) e; + } + break; + case 2: + { + LinIntExpr e(b); + (void) e; + } + break; + case 3: + { + LinIntExpr e(a,b); + (void) e; + } + break; + default: + return false; + } + unsigned long long checks = Support::FailPoint::count(); + Support::FailPoint::reset(); + return saw_failure && (checks > 0) && + (LinIntExpr::fault_live_allocations() == 0); + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + saw_failure = true; + if (LinIntExpr::fault_live_allocations() != 0) + return false; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + return false; + } + + bool minimodel_linear_sum_heap_failures_release_nodes(void) { + for (int kind = 0; kind < 4; kind++) + if (!linear_sum_constructor_heap_failures_release_nodes(kind)) + return false; + return true; + } + +#ifdef GECODE_HAS_FLOAT_VARS + bool minimodel_float_heap_failures_are_recoverable(void) { + FaultScope scope; + bool saw_failure = false; + for (unsigned long long budget = 0; budget < 32; budget++) { + Support::FailPoint::reset(); + try { + MiniModelSpace home; + FloatVarArgs x(home,4,0.0,3.0); + Support::FailPoint::fail_after(Phase::Heap,budget); + { + LinFloatExpr e = min(x); + (void) e; + } + unsigned long long checks = Support::FailPoint::count(); + Support::FailPoint::reset(); + return saw_failure && (checks > 0); + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + saw_failure = true; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + return false; + } +#endif + +#ifdef GECODE_HAS_SET_VARS + bool minimodel_set_heap_failures_are_recoverable(void) { + FaultScope scope; + bool saw_failure = false; + for (unsigned long long budget = 0; budget < 32; budget++) { + Support::FailPoint::reset(); + try { + MiniModelSpace home; + SetVarArgs x(home,4,IntSet::empty,1,1); + Support::FailPoint::fail_after(Phase::Heap,budget); + { + SetExpr e = setunion(x); + (void) e; + } + unsigned long long checks = Support::FailPoint::count(); + Support::FailPoint::reset(); + return saw_failure && (checks > 0); + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + saw_failure = true; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + return false; + } +#endif + + bool dispose_notice_initial_failure_does_not_dispose_actor(void) { + FaultScope scope; + NoticeDisposeActor::disposed = 0; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::SpaceDisposeNoticeArray,0); + try { + NoticeDisposeSpace s; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + return NoticeDisposeActor::disposed == 0; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + + bool dispose_notice_resize_failure_does_not_dispose_unregistered_actor(void) { + FaultScope scope; + NoticeDisposeActor::disposed = 0; + Support::FailPoint::reset(); + try { + NoticeDisposeSpace s(4,true); + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + return NoticeDisposeActor::disposed == 4; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + + bool int_set_failure_releases_object(void) { + FaultScope scope; + int ranges[2][2] = {{0, 0}, {2, 2}}; + IntSet::fault_reset_allocations(); + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::IntSet,0); + try { + IntSet s(ranges,2); + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + return IntSet::fault_live_allocations() == 0; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + + bool minimodel_failure_releases_default_nodes(void) { + FaultScope scope; + LinIntExpr::fault_reset_allocations(); + Support::FailPoint::reset(); + try { + MiniModelSpace home; + IntVarArgs x(home,2,0,1); + Support::FailPoint::fail_after(Phase::MiniModel,1); + LinIntExpr e = min(x); + (void) e; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + return LinIntExpr::fault_live_allocations() == 0; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + + class FaultBoolMisc : public BoolExpr::Misc { + public: + static int disposed; + + FaultBoolMisc(void) {} + virtual ~FaultBoolMisc(void) { + disposed++; + } + virtual void post(Home, BoolVar, bool, const IntPropLevels&) {} + }; + + int FaultBoolMisc::disposed = 0; + + bool minimodel_failure_releases_bool_misc(void) { + FaultScope scope; + FaultBoolMisc::disposed = 0; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::MiniModel,0); + try { + BoolExpr b(new FaultBoolMisc); + (void) b; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + return FaultBoolMisc::disposed == 1; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + + bool minimodel_heap_failure_releases_bool_misc(void) { + FaultScope scope; + FaultBoolMisc::disposed = 0; + FaultBoolMisc* m = new FaultBoolMisc; + Support::FailPoint::reset(); + Support::FailPoint::fail_after(Phase::Heap,0); + try { + BoolExpr b(m); + (void) b; + Support::FailPoint::reset(); + return false; + } catch (const MemoryExhausted&) { + Support::FailPoint::reset(); + return FaultBoolMisc::disposed == 1; + } catch (...) { + Support::FailPoint::reset(); + return false; + } + } + + class MiniModelDefaultNodes : public Base { + public: + MiniModelDefaultNodes(void) + : Base("Fault::MiniModel::DefaultNodes") {} + virtual bool run(void) { + return minimodel_failure_releases_default_nodes(); + } + }; + + class MiniModelBoolMisc : public Base { + public: + MiniModelBoolMisc(void) + : Base("Fault::MiniModel::BoolMisc") {} + virtual bool run(void) { + return minimodel_failure_releases_bool_misc(); + } + }; + + class IntSetAllocation : public Base { + public: + IntSetAllocation(void) + : Base("Fault::IntSet::Allocation") {} + virtual bool run(void) { + return int_set_failure_releases_object(); + } + }; + + class DisposeNoticeArray : public Base { + public: + DisposeNoticeArray(void) + : Base("Fault::Dispose::NoticeArray") {} + virtual bool run(void) { + return dispose_notice_initial_failure_does_not_dispose_actor(); + } + }; + + class DisposeNoticeArrayResize : public Base { + public: + DisposeNoticeArrayResize(void) + : Base("Fault::Dispose::NoticeArrayResize") {} + virtual bool run(void) { + return dispose_notice_resize_failure_does_not_dispose_unregistered_actor(); + } + }; + + class CloneDisposalArray : public Base { + public: + CloneDisposalArray(void) + : Base("Fault::Clone::DisposalArray") {} + virtual bool run(void) { + return clone_after_failed_disposal_array(); + } + }; + + class CloneDerivedSpaceUpdate : public Base { + public: + CloneDerivedSpaceUpdate(void) + : Base("Fault::Clone::DerivedSpaceUpdate") {} + virtual bool run(void) { + return clone_after_failed_derived_update(); + } + }; + + class HeapAllocation : public Base { + public: + HeapAllocation(void) + : Base("Fault::Heap::Allocation") {} + virtual bool run(void) { + return heap_allocation_failpoint_throws(); + } + }; + + class HeapReallocation : public Base { + public: + HeapReallocation(void) + : Base("Fault::Heap::Reallocation") {} + virtual bool run(void) { + return heap_reallocation_failpoint_throws(); + } + }; + + class CloneHeapFailures : public Base { + public: + CloneHeapFailures(void) + : Base("Fault::Clone::HeapFailures") {} + virtual bool run(void) { + return clone_heap_failures_recover_source(); + } + }; + + class BranchActionHeapFailures : public Base { + public: + BranchActionHeapFailures(void) + : Base("Fault::Branch::ActionHeapFailures") {} + virtual bool run(void) { + return branch_post_heap_failures_are_recoverable(false); + } + }; + + class BranchChbHeapFailures : public Base { + public: + BranchChbHeapFailures(void) + : Base("Fault::Branch::ChbHeapFailures") {} + virtual bool run(void) { + return branch_post_heap_failures_are_recoverable(true); + } + }; + + class IntSetHeapFailures : public Base { + public: + IntSetHeapFailures(void) + : Base("Fault::IntSet::HeapFailures") {} + virtual bool run(void) { + return int_set_heap_failures_release_object(); + } + }; + + class MiniModelHeapFailures : public Base { + public: + MiniModelHeapFailures(void) + : Base("Fault::MiniModel::HeapFailures") {} + virtual bool run(void) { + return minimodel_heap_failures_release_nodes(); + } + }; + + class MiniModelLinearSumHeapFailures : public Base { + public: + MiniModelLinearSumHeapFailures(void) + : Base("Fault::MiniModel::LinearSumHeapFailures") {} + virtual bool run(void) { + return minimodel_linear_sum_heap_failures_release_nodes(); + } + }; + + class MiniModelBoolMiscHeapFailure : public Base { + public: + MiniModelBoolMiscHeapFailure(void) + : Base("Fault::MiniModel::BoolMiscHeapFailure") {} + virtual bool run(void) { + return minimodel_heap_failure_releases_bool_misc(); + } + }; + +#ifdef GECODE_HAS_FLOAT_VARS + class MiniModelFloatHeapFailures : public Base { + public: + MiniModelFloatHeapFailures(void) + : Base("Fault::MiniModel::FloatHeapFailures") {} + virtual bool run(void) { + return minimodel_float_heap_failures_are_recoverable(); + } + }; +#endif + +#ifdef GECODE_HAS_SET_VARS + class MiniModelSetHeapFailures : public Base { + public: + MiniModelSetHeapFailures(void) + : Base("Fault::MiniModel::SetHeapFailures") {} + virtual bool run(void) { + return minimodel_set_heap_failures_are_recoverable(); + } + }; +#endif + + class ClonePropagatorCopy : public Base { + public: + ClonePropagatorCopy(void) + : Base("Fault::Clone::PropagatorCopy") {} + virtual bool run(void) { + return clone_after_failed_copy(Phase::PropagatorCopy); + } + }; + + class CloneBrancherCopy : public Base { + public: + CloneBrancherCopy(void) + : Base("Fault::Clone::BrancherCopy") {} + virtual bool run(void) { + return clone_after_failed_copy(Phase::BrancherCopy); + } + }; + + class CloneAdvisorCopy : public Base { + public: + CloneAdvisorCopy(void) + : Base("Fault::Clone::AdvisorCopy") {} + virtual bool run(void) { + return clone_after_failed_advisor_copy(); + } + }; + + class CloneDerivedSpaceCopy : public Base { + public: + CloneDerivedSpaceCopy(void) + : Base("Fault::Clone::DerivedSpaceCopy") {} + virtual bool run(void) { + return expect_memory_exhausted(Phase::DerivedSpaceCopy,0); + } + }; + + class CloneLocalObjectCopy : public Base { + public: + CloneLocalObjectCopy(void) + : Base("Fault::Clone::LocalObjectCopy") {} + virtual bool run(void) { + return clone_after_failed_local_object_copy(); + } + }; + + BranchActionHeapFailures branch_action_heap_failures; + BranchChbHeapFailures branch_chb_heap_failures; + CloneDisposalArray clone_disposal_array; + CloneDerivedSpaceUpdate clone_derived_space_update; + CloneHeapFailures clone_heap_failures; + DisposeNoticeArray dispose_notice_array; + DisposeNoticeArrayResize dispose_notice_array_resize; + HeapAllocation heap_allocation; + HeapReallocation heap_reallocation; + IntSetAllocation int_set_allocation; + IntSetHeapFailures int_set_heap_failures; + MiniModelDefaultNodes minimodel_default_nodes; + MiniModelBoolMisc minimodel_bool_misc; + MiniModelBoolMiscHeapFailure minimodel_bool_misc_heap_failure; + MiniModelHeapFailures minimodel_heap_failures; +#ifdef GECODE_HAS_FLOAT_VARS + MiniModelFloatHeapFailures minimodel_float_heap_failures; +#endif +#ifdef GECODE_HAS_SET_VARS + MiniModelSetHeapFailures minimodel_set_heap_failures; +#endif + ClonePropagatorCopy clone_propagator_copy; + CloneBrancherCopy clone_brancher_copy; + CloneAdvisorCopy clone_advisor_copy; + CloneDerivedSpaceCopy clone_derived_space_copy; + CloneLocalObjectCopy clone_local_object_copy; + MiniModelLinearSumHeapFailures minimodel_linear_sum_heap_failures; + +}} + +// STATISTICS: test-fault diff --git a/test/int.cpp b/test/int.cpp index af583e530d..949652866d 100755 --- a/test/int.cpp +++ b/test/int.cpp @@ -6,7 +6,7 @@ * * Copyright: * Christian Schulte, 2005 - * Mikael Zayenz Lagerkvist, 2005 + * Mikael Zayenz Lagerkvist, 2005, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -185,16 +185,20 @@ namespace Test { namespace Int { switch (rand(3)) { case 0: if (a[i] < x[i].max()) { - v=a[i]+1+ - static_cast(rand(static_cast(x[i].max()-a[i]))); + unsigned int n = + static_cast(x[i].max()) - static_cast(a[i]); + unsigned int offset = rand(n); + v = static_cast(static_cast(a[i]) + 1LL + offset); assert((v > a[i]) && (v <= x[i].max())); irt = IRT_LE; } break; case 1: if (a[i] > x[i].min()) { - v=x[i].min()+ - static_cast(rand(static_cast(a[i]-x[i].min()))); + unsigned int n = + static_cast(a[i]) - static_cast(x[i].min()); + unsigned int offset = rand(n); + v = static_cast(static_cast(x[i].min()) + offset); assert((v < a[i]) && (v >= x[i].min())); irt = IRT_GR; } @@ -205,7 +209,7 @@ namespace Test { namespace Int { unsigned int skip = rand(static_cast(x[i].size()-1)); while (true) { if (it.width() > skip) { - v = it.min() + static_cast(skip); + v = static_cast(static_cast(it.min()) + skip); if (v == a[i]) { if (it.width() == 1) { ++it; v = it.min(); @@ -278,14 +282,21 @@ namespace Test { namespace Int { // Prune values if (bounds_only) { if (rand(2) && !x[i].assigned()) { - int v=x[i].min()+1+ - static_cast(rand(static_cast(x[i].max()-x[i].min()))); + unsigned int n = + static_cast(x[i].max()) - + static_cast(x[i].min()); + unsigned int offset = rand(n); + int v = static_cast(static_cast(x[i].min()) + + 1LL + offset); assert((v > x[i].min()) && (v <= x[i].max())); rel(i, Gecode::IRT_LE, v); } if (rand(2) && !x[i].assigned()) { - int v=x[i].min()+ - static_cast(rand(static_cast(x[i].max()-x[i].min()))); + unsigned int n = + static_cast(x[i].max()) - + static_cast(x[i].min()); + unsigned int offset = rand(n); + int v = static_cast(static_cast(x[i].min()) + offset); assert((v < x[i].max()) && (v >= x[i].min())); rel(i, Gecode::IRT_GR, v); } @@ -297,7 +308,8 @@ namespace Test { namespace Int { unsigned int skip = rand(x[i].size()-1); while (true) { if (it.width() > skip) { - v = it.min() + static_cast(skip); break; + v = static_cast(static_cast(it.min()) + skip); + break; } skip -= it.width(); ++it; } diff --git a/test/int/mm-lin.cpp b/test/int/mm-lin.cpp index 22dd0f050d..0ce04056b8 100755 --- a/test/int/mm-lin.cpp +++ b/test/int/mm-lin.cpp @@ -3,8 +3,12 @@ * Main authors: * Christian Schulte * + * Contributing authors: + * Mikael Zayenz Lagerkvist + * * Copyright: * Christian Schulte, 2008 + * Mikael Zayenz Lagerkvist, 2026 * * This file is part of Gecode, the generic constraint * development environment: @@ -90,6 +94,44 @@ namespace Test { namespace Int { * \ingroup TaskTestInt */ //@{ + /// %Test public default linear expression behavior + class LinExprDefaultSpace : public Gecode::Space { + public: + Gecode::IntVar x; + LinExprDefaultSpace(void) : x(*this,-5,5) { + using namespace Gecode; + LinIntExpr e; + LinIntExpr c(e); + LinIntExpr a = e + x; + LinIntExpr b = 2 + c; + LinIntExpr m = 3 * e; + rel(*this, a + b + m == 3); + } + LinExprDefaultSpace(LinExprDefaultSpace& s) : Gecode::Space(s) { + x.update(*this,s.x); + } + virtual Gecode::Space* copy(void) { + return new LinExprDefaultSpace(*this); + } + }; + + /// %Test public default linear expression behavior + class LinExprDefault : public Test::Base { + public: + LinExprDefault(void) : Test::Base("MiniModel::LinExpr::Default") {} + virtual bool run(void) { + LinExprDefaultSpace* s = new LinExprDefaultSpace; + Gecode::DFS e(s); + delete s; + LinExprDefaultSpace* sol = e.next(); + if (sol == nullptr) + return false; + bool ok = sol->x.assigned() && (sol->x.val() == 1); + delete sol; + return ok && (e.next() == nullptr); + } + }; + /// %Test linear expressions over integer variables class LinExprInt : public Test { protected: @@ -2172,6 +2214,7 @@ namespace Test { namespace Int { } }; + LinExprDefault lin_expr_default; Create c; //@} }