diff --git a/Builds/VisualStudio/stellar-core.vcxproj b/Builds/VisualStudio/stellar-core.vcxproj
index 318f13f7f0..f6f6d869e0 100644
--- a/Builds/VisualStudio/stellar-core.vcxproj
+++ b/Builds/VisualStudio/stellar-core.vcxproj
@@ -771,6 +771,7 @@ exit /b 0
+
@@ -1197,6 +1198,7 @@ exit /b 0
+
diff --git a/Builds/VisualStudio/stellar-core.vcxproj.filters b/Builds/VisualStudio/stellar-core.vcxproj.filters
index d1cf2654c5..8121ba52e4 100644
--- a/Builds/VisualStudio/stellar-core.vcxproj.filters
+++ b/Builds/VisualStudio/stellar-core.vcxproj.filters
@@ -492,6 +492,9 @@
util
+
+ util
+
util
@@ -2599,6 +2602,9 @@
main
+
+ util
+
diff --git a/src/transactions/InvokeHostFunctionOpFrame.cpp b/src/transactions/InvokeHostFunctionOpFrame.cpp
index 5fc3cf8d34..ae930f736d 100644
--- a/src/transactions/InvokeHostFunctionOpFrame.cpp
+++ b/src/transactions/InvokeHostFunctionOpFrame.cpp
@@ -34,6 +34,12 @@
#include
#include
+#ifdef _WIN32
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
namespace stellar
{
namespace
diff --git a/src/transactions/test/InvokeHostFunctionTests.cpp b/src/transactions/test/InvokeHostFunctionTests.cpp
index 710ca74aae..c481522b6e 100644
--- a/src/transactions/test/InvokeHostFunctionTests.cpp
+++ b/src/transactions/test/InvokeHostFunctionTests.cpp
@@ -6862,8 +6862,6 @@ TEST_CASE("Soroban delegated signer authentication", "[tx][soroban]")
InvokeHostFunctionResultCode::INVOKE_HOST_FUNCTION_TRAPPED);
}
}
- // This test causes stack overflow on Windows, but works fine on Linux.
-#ifndef WIN32
SECTION("deep delegate tree")
{
auto buildDelegateChain = [&](int depth) {
@@ -6910,7 +6908,6 @@ TEST_CASE("Soroban delegated signer authentication", "[tx][soroban]")
InvokeHostFunctionResultCode::INVOKE_HOST_FUNCTION_TRAPPED);
}
}
-#endif
}
TEST_CASE("Soroban authorization", "[tx][soroban]")
diff --git a/src/util/BatchExecutor.cpp b/src/util/BatchExecutor.cpp
index 324efc429b..91a18e1eb1 100644
--- a/src/util/BatchExecutor.cpp
+++ b/src/util/BatchExecutor.cpp
@@ -160,8 +160,9 @@ BatchExecutor::ensureWorkers(size_t count)
{
size_t index = mWorkers.size();
uint64_t batchId = mBatchId;
- mWorkers.emplace_back(
- [this, index, batchId]() { workerLoop(index, batchId); });
+ mWorkers.emplace_back(WORKER_STACK_BYTES, [this, index, batchId]() {
+ workerLoop(index, batchId);
+ });
pinWorker(index);
}
}
diff --git a/src/util/BatchExecutor.h b/src/util/BatchExecutor.h
index 652dfc357c..ce13427765 100644
--- a/src/util/BatchExecutor.h
+++ b/src/util/BatchExecutor.h
@@ -7,6 +7,7 @@
#include "lib/util/finally.h"
#include "util/GlobalChecks.h"
#include "util/NonCopyable.h"
+#include "util/StackThread.h"
#include
#include
@@ -15,12 +16,12 @@
#include
#include
#include
-#include
#include
#include
namespace stellar
{
+inline constexpr size_t WORKER_STACK_BYTES = 1 << 23; // 8 MiB
// Executes batches of CPU-bound tasks in parallel on a pool of worker threads.
//
@@ -77,7 +78,7 @@ class BatchExecutor : private NonMovableOrCopyable
// with std::condition_variable.
std::mutex mMutex;
std::condition_variable mCondition;
- std::vector mWorkers;
+ std::vector mWorkers;
// All allowed logical CPUs in pinning-preference order.
std::vector mPinCpuOrder;
// Number of distinct physical cores found in the allowed logical CPUs.
diff --git a/src/util/StackThread.cpp b/src/util/StackThread.cpp
new file mode 100644
index 0000000000..e2a290e941
--- /dev/null
+++ b/src/util/StackThread.cpp
@@ -0,0 +1,447 @@
+// Copyright 2020 Stellar Development Foundation and contributors. Licensed
+// under the Apache License, Version 2.0. See the COPYING file at the root
+// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
+
+#include "util/StackThread.h"
+
+#include
+#include
+#include
+
+#if defined(_WIN32)
+// windows.h first, then:
+#include
+#include
+#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
+#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000
+#endif
+#else
+#include
+#include
+#endif
+
+namespace stellar
+{
+
+namespace detail
+{
+
+void
+setCurrentThreadName(std::string const& name)
+{
+ if (name.empty())
+ {
+ return;
+ }
+#if defined(_WIN32)
+ // SetThreadDescription is Windows 10 1607+; resolve dynamically so the
+ // binary still loads on older systems.
+ using SetDescFn = HRESULT(WINAPI*)(HANDLE, PCWSTR);
+ static SetDescFn const setDesc =
+ reinterpret_cast(reinterpret_cast(::GetProcAddress(
+ ::GetModuleHandleW(L"kernel32.dll"), "SetThreadDescription")));
+ if (setDesc != nullptr)
+ {
+ // Thread names are ASCII in practice; widen naively.
+ std::wstring wide(name.begin(), name.end());
+ setDesc(::GetCurrentThread(), wide.c_str());
+ }
+#elif defined(__APPLE__)
+ // Hard limit of 64 bytes including the NUL; longer names fail with ERANGE.
+ std::string const truncated = name.substr(0, 63);
+ ::pthread_setname_np(truncated.c_str());
+#elif defined(__linux__)
+ // Hard limit of 16 bytes including the NUL; longer names fail with ERANGE.
+ std::string const truncated = name.substr(0, 15);
+ ::pthread_setname_np(::pthread_self(), truncated.c_str());
+#elif defined(__FreeBSD__) || defined(__OpenBSD__)
+ ::pthread_set_name_np(::pthread_self(), name.c_str());
+#else
+ (void)name;
+#endif
+}
+
+#if !defined(_WIN32)
+// pthread_attr_setstacksize requires a multiple of the page size and at least
+// PTHREAD_STACK_MIN. On glibc >= 2.34 PTHREAD_STACK_MIN is no longer a
+// compile-time constant on every architecture, so prefer sysconf.
+std::size_t
+roundStackSize(std::size_t bytes)
+{
+ long const pageRaw = ::sysconf(_SC_PAGESIZE);
+ std::size_t const page =
+ (pageRaw > 0) ? static_cast(pageRaw) : 4096u;
+
+ std::size_t minStack = page;
+#if defined(_SC_THREAD_STACK_MIN)
+ long const minRaw = ::sysconf(_SC_THREAD_STACK_MIN);
+ if (minRaw > 0)
+ {
+ minStack = std::max(minStack, static_cast(minRaw));
+ }
+#endif
+#if defined(PTHREAD_STACK_MIN)
+ minStack = std::max(minStack, static_cast(PTHREAD_STACK_MIN));
+#endif
+
+ bytes = std::max(bytes, minStack);
+ // Round up to a page multiple, guarding against overflow.
+ if (bytes > (~static_cast(0)) - (page - 1))
+ {
+ return bytes - (bytes % page);
+ }
+ return ((bytes + page - 1) / page) * page;
+}
+
+bool
+isNullThread(::pthread_t const& t) noexcept
+{
+ ::pthread_t nullThread{};
+ return std::memcmp(&t, &nullThread, sizeof(t)) == 0;
+}
+#endif
+
+// Note: passing a C++-linkage function to pthread_create is formally
+// unspecified, but is what every real implementation (and libstdc++/libc++
+// themselves) does.
+#if defined(_WIN32)
+unsigned __stdcall trampoline(void* raw)
+#else
+void*
+trampoline(void* raw)
+#endif
+{
+ std::unique_ptr payload(static_cast(raw));
+ try
+ {
+ setCurrentThreadName(payload->mName);
+ payload->run();
+ }
+ catch (...)
+ {
+ // std::thread's contract: an exception escaping the thread function
+ // calls std::terminate. Letting it unwind into the C runtime here
+ // would be undefined behaviour, so make it explicit.
+ std::terminate();
+ }
+#if defined(_WIN32)
+ return 0u;
+#else
+ return nullptr;
+#endif
+}
+
+} // namespace detail
+
+StackThread::id::id() noexcept : mNative{}, mValid(false)
+{
+}
+
+StackThread::id::id(native_id_type native) noexcept
+ : mNative(native), mValid(true)
+{
+}
+
+bool
+operator==(StackThread::id const& a, StackThread::id const& b) noexcept
+{
+ if (a.mValid != b.mValid)
+ {
+ return false;
+ }
+ if (!a.mValid)
+ {
+ return true;
+ }
+#if defined(_WIN32)
+ return a.mNative == b.mNative;
+#else
+ return ::pthread_equal(a.mNative, b.mNative) != 0;
+#endif
+}
+
+bool
+operator!=(StackThread::id const& a, StackThread::id const& b) noexcept
+{
+ return !(a == b);
+}
+
+bool
+operator<(StackThread::id const& a, StackThread::id const& b) noexcept
+{
+ if (a.mValid != b.mValid)
+ {
+ return !a.mValid;
+ }
+ if (!a.mValid)
+ {
+ return false;
+ }
+ return std::memcmp(&a.mNative, &b.mNative,
+ sizeof(StackThread::native_id_type)) < 0;
+}
+
+bool
+operator>(StackThread::id const& a, StackThread::id const& b) noexcept
+{
+ return b < a;
+}
+
+bool
+operator<=(StackThread::id const& a, StackThread::id const& b) noexcept
+{
+ return !(b < a);
+}
+
+bool
+operator>=(StackThread::id const& a, StackThread::id const& b) noexcept
+{
+ return !(a < b);
+}
+
+std::size_t
+StackThread::id::hash() const noexcept
+{
+ if (!mValid)
+ {
+ return 0;
+ }
+ std::uintptr_t scalar = 0;
+ std::memcpy(&scalar, &mNative,
+ std::min(sizeof(scalar), sizeof(native_id_type)));
+ return std::hash{}(scalar);
+}
+
+StackThread::StackThread(StackThread&& other) noexcept
+{
+ swap(other);
+}
+
+StackThread&
+StackThread::operator=(StackThread&& other) noexcept
+{
+ if (this != &other)
+ {
+ if (joinable())
+ {
+ // Matching std::thread: assigning over a joinable thread is
+ // a programming error, not a silent detach.
+ std::terminate();
+ }
+ swap(other);
+ }
+ return *this;
+}
+
+StackThread::~StackThread()
+{
+ if (joinable())
+ {
+ std::terminate();
+ }
+}
+
+bool
+StackThread::joinable() const noexcept
+{
+#if defined(_WIN32)
+ return mHandle != nullptr;
+#else
+ return !detail::isNullThread(mThread);
+#endif
+}
+
+void
+StackThread::join()
+{
+ if (!joinable())
+ {
+ throw std::system_error(
+ std::make_error_code(std::errc::invalid_argument),
+ "StackThread::join on a non-joinable thread");
+ }
+#if defined(_WIN32)
+ if (::GetThreadId(mHandle) == ::GetCurrentThreadId())
+ {
+ throw std::system_error(
+ std::make_error_code(std::errc::resource_deadlock_would_occur),
+ "StackThread::join on itself");
+ }
+ DWORD const rc = ::WaitForSingleObject(mHandle, INFINITE);
+ if (rc != WAIT_OBJECT_0)
+ {
+ throw std::system_error(static_cast(::GetLastError()),
+ std::system_category(), "WaitForSingleObject");
+ }
+ ::CloseHandle(mHandle);
+ mHandle = nullptr;
+#else
+ int const rc = ::pthread_join(mThread, nullptr);
+ if (rc != 0)
+ {
+ throw std::system_error(rc, std::system_category(), "pthread_join");
+ }
+ mThread = native_handle_type{};
+#endif
+}
+
+void
+StackThread::detach()
+{
+ if (!joinable())
+ {
+ throw std::system_error(
+ std::make_error_code(std::errc::invalid_argument),
+ "StackThread::detach on a non-joinable thread");
+ }
+#if defined(_WIN32)
+ ::CloseHandle(mHandle);
+ mHandle = nullptr;
+#else
+ int const rc = ::pthread_detach(mThread);
+ if (rc != 0)
+ {
+ throw std::system_error(rc, std::system_category(), "pthread_detach");
+ }
+ mThread = native_handle_type{};
+#endif
+}
+
+StackThread::id
+StackThread::get_id() const noexcept
+{
+ if (!joinable())
+ {
+ return id{};
+ }
+#if defined(_WIN32)
+ return id{::GetThreadId(mHandle)};
+#else
+ return id{mThread};
+#endif
+}
+
+StackThread::native_handle_type
+StackThread::native_handle() const noexcept
+{
+#if defined(_WIN32)
+ return mHandle;
+#else
+ return mThread;
+#endif
+}
+
+void
+StackThread::swap(StackThread& other) noexcept
+{
+#if defined(_WIN32)
+ std::swap(mHandle, other.mHandle);
+#else
+ std::swap(mThread, other.mThread);
+#endif
+}
+
+unsigned int
+StackThread::hardware_concurrency() noexcept
+{
+ return std::thread::hardware_concurrency();
+}
+
+void
+StackThread::setCurrentName(std::string const& name)
+{
+ detail::setCurrentThreadName(name);
+}
+
+void
+StackThread::start(std::size_t stackBytes,
+ std::unique_ptr payload)
+{
+#if defined(_WIN32)
+ if (stackBytes > static_cast(UINT_MAX))
+ {
+ throw std::system_error(
+ std::make_error_code(std::errc::invalid_argument),
+ "requested stack size exceeds the Win32 unsigned limit");
+ }
+ // Without STACK_SIZE_PARAM_IS_A_RESERVATION the size argument is the
+ // initial *commit*, and the reserve still comes from the PE header.
+ // _beginthreadex may start executing the trampoline before it returns, so
+ // create the thread suspended until mHandle is published. This preserves
+ // std::thread-like constructor synchronization for callables that capture
+ // the StackThread object under construction.
+ uintptr_t const h = ::_beginthreadex(
+ nullptr, static_cast(stackBytes), &detail::trampoline,
+ payload.get(), STACK_SIZE_PARAM_IS_A_RESERVATION | CREATE_SUSPENDED,
+ nullptr);
+ if (h == 0)
+ {
+ throw std::system_error(errno, std::generic_category(),
+ "_beginthreadex");
+ }
+ mHandle = reinterpret_cast(h);
+ [[maybe_unused]] auto* transferredPayload = payload.release();
+ if (::ResumeThread(mHandle) == static_cast(-1))
+ {
+ DWORD const ec = ::GetLastError();
+ ::CloseHandle(mHandle);
+ mHandle = nullptr;
+ throw std::system_error(static_cast(ec), std::system_category(),
+ "ResumeThread");
+ }
+#else
+ ::pthread_attr_t attr;
+ int rc = ::pthread_attr_init(&attr);
+ if (rc != 0)
+ {
+ throw std::system_error(rc, std::system_category(),
+ "pthread_attr_init");
+ }
+ struct AttrGuard
+ {
+ ::pthread_attr_t* a;
+ ~AttrGuard()
+ {
+ ::pthread_attr_destroy(a);
+ }
+ } guard{&attr};
+
+ if (stackBytes != 0)
+ {
+ rc = ::pthread_attr_setstacksize(&attr,
+ detail::roundStackSize(stackBytes));
+ if (rc != 0)
+ {
+ throw std::system_error(rc, std::system_category(),
+ "pthread_attr_setstacksize");
+ }
+ }
+
+ rc = ::pthread_create(&mThread, &attr, &detail::trampoline, payload.get());
+ if (rc != 0)
+ {
+ throw std::system_error(rc, std::system_category(), "pthread_create");
+ }
+ [[maybe_unused]] auto* transferredPayload = payload.release();
+#endif
+}
+
+void
+swap(StackThread& a, StackThread& b) noexcept
+{
+ a.swap(b);
+}
+
+} // namespace stellar
+
+namespace std
+{
+
+std::size_t
+hash<::stellar::StackThread::id>::operator()(
+ ::stellar::StackThread::id const& v) const noexcept
+{
+ return v.hash();
+}
+
+} // namespace std
\ No newline at end of file
diff --git a/src/util/StackThread.h b/src/util/StackThread.h
new file mode 100644
index 0000000000..2764dad4de
--- /dev/null
+++ b/src/util/StackThread.h
@@ -0,0 +1,246 @@
+// Copyright 2020 Stellar Development Foundation and contributors. Licensed
+// under the Apache License, Version 2.0. See the COPYING file at the root
+// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
+//
+// StackThread.h -- a std::thread work-alike with a settable stack size.
+//
+// Platforms: Linux (glibc/musl), macOS, Windows (MSVC / clang-cl / MinGW-w64).
+// Language: C++17.
+//
+// Differences from std::thread, all deliberate:
+// * ctor takes a stack size in bytes as its first argument (0 = platform
+// default)
+// * optional thread name as a second argument, applied by the new thread
+// itself
+// (macOS only permits naming the calling thread, so this is the only
+// portable point)
+// * native_handle() is always available, not conditionally-supported
+// * id is a distinct type from native_handle_type (they differ on Windows)
+//
+// Everything else -- move-only, terminate-on-joinable-destruction, INVOKE-style
+// argument decay-copying, terminate on escaping exception -- matches
+// std::thread.
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if defined(_WIN32)
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+#include
+#else
+#include
+#endif
+
+namespace stellar
+{
+
+namespace detail
+{
+
+// Type-erased callable. Virtual dispatch rather than a function template keeps
+// the trampoline a single non-template function, which avoids instantiating a
+// C-callback-shaped function per callable type.
+struct PayloadBase
+{
+ virtual ~PayloadBase() = default;
+ virtual void run() = 0;
+ std::string mName;
+};
+
+template struct Payload final : PayloadBase
+{
+ Fn mFn;
+ explicit Payload(Fn&& fn) : mFn(std::move(fn))
+ {
+ }
+ void
+ run() override
+ {
+ mFn();
+ }
+};
+
+void setCurrentThreadName(std::string const& name);
+#if !defined(_WIN32)
+std::size_t roundStackSize(std::size_t bytes);
+bool isNullThread(::pthread_t const& t) noexcept;
+#endif
+
+#if defined(_WIN32)
+unsigned __stdcall trampoline(void* raw);
+#else
+void* trampoline(void* raw);
+#endif
+
+} // namespace detail
+
+class StackThread
+{
+ public:
+#if defined(_WIN32)
+ using native_handle_type = HANDLE;
+ using native_id_type = DWORD;
+#else
+ using native_handle_type = ::pthread_t;
+ using native_id_type = ::pthread_t;
+#endif
+
+ // Opaque, comparable, hashable, streamable thread identity -- the analogue
+ // of std::thread::id.
+ class id
+ {
+ public:
+ id() noexcept;
+
+ explicit id(native_id_type native) noexcept;
+
+ friend bool operator==(id const& a, id const& b) noexcept;
+
+ friend bool operator!=(id const& a, id const& b) noexcept;
+
+ // Ordering exists so `id` can key a std::map. POSIX gives no ordering
+ // over pthread_t, so this compares the object representation. That is
+ // a strict weak ordering consistent with == on every implementation
+ // where pthread_t is a scalar (all of glibc, musl, macOS).
+ friend bool operator<(id const& a, id const& b) noexcept;
+
+ friend bool operator>(id const& a, id const& b) noexcept;
+ friend bool operator<=(id const& a, id const& b) noexcept;
+ friend bool operator>=(id const& a, id const& b) noexcept;
+
+ template
+ friend std::basic_ostream&
+ operator<<(std::basic_ostream& os, id const& v)
+ {
+ if (!v.mValid)
+ {
+ return os << "thread::id(of a non-executing thread)";
+ }
+ std::uintptr_t scalar = 0;
+ std::memcpy(&scalar, &v.mNative,
+ std::min(sizeof(scalar), sizeof(native_id_type)));
+ return os << scalar;
+ }
+
+ std::size_t hash() const noexcept;
+
+ private:
+ native_id_type mNative;
+ bool mValid;
+ };
+
+ StackThread() noexcept = default;
+
+ // Primary constructor. stackBytes == 0 selects the platform default; any
+ // other value is rounded up to satisfy platform minimums.
+ template , std::decay_t...>>>
+ StackThread(std::size_t stackBytes, F&& f, Args&&... args)
+ : StackThread(stackBytes, std::string{}, std::forward(f),
+ std::forward(args)...)
+ {
+ }
+
+ // Same, but the new thread names itself before running the callable.
+ template , std::decay_t...>>>
+ StackThread(std::size_t stackBytes, std::string name, F&& f, Args&&... args)
+ {
+ // Decay-copy everything up front, exactly as std::thread does, so the
+ // new thread never touches the caller's storage.
+ auto bound =
+ [tup = std::make_tuple(
+ std::decay_t(std::forward(f)),
+ std::decay_t(std::forward(args))...)]() mutable {
+ std::apply(
+ [](auto&& fn, auto&&... rest) {
+ std::invoke(std::forward(fn),
+ std::forward(rest)...);
+ },
+ std::move(tup));
+ };
+
+ auto payload = std::make_unique>(
+ std::move(bound));
+ payload->mName = std::move(name);
+
+ start(stackBytes, std::move(payload));
+ }
+
+ StackThread(StackThread const&) = delete;
+ StackThread& operator=(StackThread const&) = delete;
+
+ StackThread(StackThread&& other) noexcept;
+
+ StackThread& operator=(StackThread&& other) noexcept;
+
+ ~StackThread();
+
+ bool joinable() const noexcept;
+
+ void join();
+
+ void detach();
+
+ id get_id() const noexcept;
+
+ // Valid only while joinable(). On POSIX this is the pthread_t, suitable for
+ // pthread_setaffinity_np, pthread_setschedparam, pthread_getattr_np, etc.
+ //
+ // Caveat: joinable() only means "not yet joined or detached", not "still
+ // running". Once the thread function returns, the pthread_t stays valid as
+ // a join target but the underlying kernel task is gone, so scheduling and
+ // affinity calls will fail with ESRCH. If you intend to pin or reprioritise
+ // a thread, do it promptly after construction, or have the thread do it to
+ // itself. (Windows HANDLEs do not have this problem -- they stay queryable
+ // after exit.)
+ native_handle_type native_handle() const noexcept;
+
+ void swap(StackThread& other) noexcept;
+
+ static unsigned int hardware_concurrency() noexcept;
+
+ // Convenience: name the calling thread. Note macOS can only name itself,
+ // so there is intentionally no name-another-thread entry point.
+ static void setCurrentName(std::string const& name);
+
+ private:
+ void start(std::size_t stackBytes,
+ std::unique_ptr payload);
+
+#if defined(_WIN32)
+ HANDLE mHandle = nullptr;
+#else
+ ::pthread_t mThread{};
+#endif
+};
+
+void swap(StackThread& a, StackThread& b) noexcept;
+
+} // namespace stellar
+
+namespace std
+{
+template <> struct hash<::stellar::StackThread::id>
+{
+ std::size_t operator()(::stellar::StackThread::id const& v) const noexcept;
+};
+} // namespace std
diff --git a/src/util/test/StackThreadTests.cpp b/src/util/test/StackThreadTests.cpp
new file mode 100644
index 0000000000..c2d7d8da5d
--- /dev/null
+++ b/src/util/test/StackThreadTests.cpp
@@ -0,0 +1,233 @@
+// Copyright 2026 Stellar Development Foundation and contributors. Licensed
+// under the Apache License, Version 2.0. See the COPYING file at the root
+// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
+
+#include "test/Catch2.h"
+#include "util/BatchExecutor.h"
+#include "util/StackThread.h"
+
+#include
+#include
+#include
+#include
+#include