2019-08-10 17:51:47 +00:00
|
|
|
#include <Common/checkStackSize.h>
|
|
|
|
#include <Common/Exception.h>
|
2023-07-28 19:03:32 +00:00
|
|
|
#include <base/getThreadId.h>
|
2021-10-02 07:13:14 +00:00
|
|
|
#include <base/scope_guard.h>
|
2021-10-23 08:14:10 +00:00
|
|
|
#include <base/defines.h> /// THREAD_SANITIZER
|
2023-07-28 19:03:32 +00:00
|
|
|
#include <sys/resource.h>
|
2019-08-10 17:51:47 +00:00
|
|
|
#include <pthread.h>
|
2023-07-28 19:03:32 +00:00
|
|
|
#include <unistd.h>
|
2019-08-10 17:51:47 +00:00
|
|
|
#include <cstdint>
|
|
|
|
|
2022-06-10 08:22:31 +00:00
|
|
|
#if defined(OS_FREEBSD)
|
2019-10-05 19:25:31 +00:00
|
|
|
# include <pthread_np.h>
|
|
|
|
#endif
|
|
|
|
|
2019-08-10 17:51:47 +00:00
|
|
|
|
|
|
|
namespace DB
|
|
|
|
{
|
|
|
|
namespace ErrorCodes
|
|
|
|
{
|
|
|
|
extern const int CANNOT_PTHREAD_ATTR;
|
|
|
|
extern const int LOGICAL_ERROR;
|
|
|
|
extern const int TOO_DEEP_RECURSION;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static thread_local void * stack_address = nullptr;
|
|
|
|
static thread_local size_t max_stack_size = 0;
|
|
|
|
|
2021-06-19 14:12:30 +00:00
|
|
|
/**
|
2021-06-21 09:09:14 +00:00
|
|
|
* @param out_address - if not nullptr, here the address of the stack will be written.
|
2021-06-19 14:12:30 +00:00
|
|
|
* @return stack size
|
|
|
|
*/
|
2023-06-28 09:56:11 +00:00
|
|
|
static size_t getStackSize(void ** out_address)
|
2019-08-10 17:51:47 +00:00
|
|
|
{
|
|
|
|
using namespace DB;
|
|
|
|
|
2021-06-19 14:12:30 +00:00
|
|
|
size_t size;
|
2021-06-21 09:08:30 +00:00
|
|
|
void * address;
|
2021-06-19 14:12:30 +00:00
|
|
|
|
2019-09-23 13:07:19 +00:00
|
|
|
#if defined(OS_DARWIN)
|
2021-06-19 14:12:30 +00:00
|
|
|
// pthread_get_stacksize_np() returns a value too low for the main thread on
|
|
|
|
// OSX 10.9, http://mail.openjdk.java.net/pipermail/hotspot-dev/2013-October/011369.html
|
|
|
|
//
|
|
|
|
// Multiple workarounds possible, adopt the one made by https://github.com/robovm/robovm/issues/274
|
|
|
|
// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html
|
|
|
|
// Stack size for the main thread is 8MB on OSX excluding the guard page size.
|
|
|
|
pthread_t thread = pthread_self();
|
|
|
|
size = pthread_main_np() ? (8 * 1024 * 1024) : pthread_get_stacksize_np(thread);
|
2019-09-23 19:47:12 +00:00
|
|
|
|
2021-06-19 14:12:30 +00:00
|
|
|
// stack address points to the start of the stack, not the end how it's returned by pthread_get_stackaddr_np
|
2021-06-24 06:47:23 +00:00
|
|
|
address = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(pthread_get_stackaddr_np(thread)) - size);
|
2019-09-21 18:30:01 +00:00
|
|
|
#else
|
2021-06-19 14:12:30 +00:00
|
|
|
pthread_attr_t attr;
|
2022-06-10 08:22:31 +00:00
|
|
|
# if defined(OS_FREEBSD) || defined(OS_SUNOS)
|
2021-06-19 14:12:30 +00:00
|
|
|
pthread_attr_init(&attr);
|
|
|
|
if (0 != pthread_attr_get_np(pthread_self(), &attr))
|
|
|
|
throwFromErrno("Cannot pthread_attr_get_np", ErrorCodes::CANNOT_PTHREAD_ATTR);
|
2019-09-23 13:07:19 +00:00
|
|
|
# else
|
2021-06-19 14:12:30 +00:00
|
|
|
if (0 != pthread_getattr_np(pthread_self(), &attr))
|
2023-06-28 09:56:11 +00:00
|
|
|
{
|
|
|
|
if (errno == ENOENT)
|
|
|
|
{
|
|
|
|
/// Most likely procfs is not mounted.
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
throwFromErrno("Cannot pthread_getattr_np", ErrorCodes::CANNOT_PTHREAD_ATTR);
|
|
|
|
}
|
2019-09-23 13:07:19 +00:00
|
|
|
# endif
|
2019-08-10 17:51:47 +00:00
|
|
|
|
2021-06-19 14:12:30 +00:00
|
|
|
SCOPE_EXIT({ pthread_attr_destroy(&attr); });
|
2019-08-10 17:51:47 +00:00
|
|
|
|
2021-06-19 14:12:30 +00:00
|
|
|
if (0 != pthread_attr_getstack(&attr, &address, &size))
|
|
|
|
throwFromErrno("Cannot pthread_getattr_np", ErrorCodes::CANNOT_PTHREAD_ATTR);
|
2023-07-28 19:03:32 +00:00
|
|
|
|
|
|
|
#ifdef USE_MUSL
|
|
|
|
/// Adjust stack size for the main thread under musl.
|
|
|
|
/// musl returns not the maximum available stack, but current stack.
|
|
|
|
///
|
|
|
|
/// TL;DR;
|
|
|
|
///
|
|
|
|
/// musl uses mremap() and calls it until it returns ENOMEM, but after the
|
|
|
|
/// available stack there will be a guard page (that is handled by the
|
|
|
|
/// kernel to expand the stack), and when you will try to mremap() on it
|
|
|
|
/// you will get EFAULT.
|
|
|
|
if (static_cast<pid_t>(getThreadId()) == getpid())
|
|
|
|
{
|
|
|
|
::rlimit rlimit{};
|
|
|
|
if (::getrlimit(RLIMIT_STACK, &rlimit))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
address = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(address) + size);
|
|
|
|
size = rlimit.rlim_cur;
|
|
|
|
address = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(address) - size);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#endif
|
2021-06-19 14:12:30 +00:00
|
|
|
|
2021-06-21 09:08:30 +00:00
|
|
|
if (out_address)
|
|
|
|
*out_address = address;
|
2021-06-19 14:12:30 +00:00
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** It works fine when interpreters are instantiated by ClickHouse code in properly prepared threads,
|
|
|
|
* but there are cases when ClickHouse runs as a library inside another application.
|
|
|
|
* If application is using user-space lightweight threads with manually allocated stacks,
|
|
|
|
* current implementation is not reasonable, as it has no way to properly check the remaining
|
|
|
|
* stack size without knowing the details of how stacks are allocated.
|
|
|
|
* We mark this function as weak symbol to be able to replace it in another ClickHouse-based products.
|
|
|
|
*/
|
|
|
|
__attribute__((__weak__)) void checkStackSize()
|
|
|
|
{
|
|
|
|
using namespace DB;
|
|
|
|
|
|
|
|
if (!stack_address)
|
|
|
|
max_stack_size = getStackSize(&stack_address);
|
2019-08-10 17:51:47 +00:00
|
|
|
|
2023-06-28 09:56:11 +00:00
|
|
|
/// The check is impossible.
|
|
|
|
if (!max_stack_size)
|
|
|
|
return;
|
|
|
|
|
2019-08-10 17:51:47 +00:00
|
|
|
const void * frame_address = __builtin_frame_address(0);
|
|
|
|
uintptr_t int_frame_address = reinterpret_cast<uintptr_t>(frame_address);
|
|
|
|
uintptr_t int_stack_address = reinterpret_cast<uintptr_t>(stack_address);
|
|
|
|
|
2021-10-23 08:14:10 +00:00
|
|
|
#if !defined(THREAD_SANITIZER)
|
|
|
|
/// It's overkill to use more then half of stack.
|
|
|
|
static constexpr double STACK_SIZE_FREE_RATIO = 0.5;
|
|
|
|
#else
|
|
|
|
/// Under TSan recursion eats too much RAM, so half of stack is too much.
|
|
|
|
/// So under TSan only 5% of a stack is allowed (this is ~400K)
|
|
|
|
static constexpr double STACK_SIZE_FREE_RATIO = 0.05;
|
|
|
|
#endif
|
|
|
|
|
2019-08-10 17:51:47 +00:00
|
|
|
/// We assume that stack grows towards lower addresses. And that it starts to grow from the end of a chunk of memory of max_stack_size.
|
|
|
|
if (int_frame_address > int_stack_address + max_stack_size)
|
2023-01-23 21:13:58 +00:00
|
|
|
throw Exception(ErrorCodes::LOGICAL_ERROR, "Logical error: frame address is greater than stack begin address");
|
2019-08-10 17:51:47 +00:00
|
|
|
|
|
|
|
size_t stack_size = int_stack_address + max_stack_size - int_frame_address;
|
2022-09-10 02:07:51 +00:00
|
|
|
size_t max_stack_size_allowed = static_cast<size_t>(max_stack_size * STACK_SIZE_FREE_RATIO);
|
2019-08-10 17:51:47 +00:00
|
|
|
|
2021-10-23 08:14:10 +00:00
|
|
|
/// Just check if we have eat more than a STACK_SIZE_FREE_RATIO of stack size already.
|
|
|
|
if (stack_size > max_stack_size_allowed)
|
2019-08-10 17:51:47 +00:00
|
|
|
{
|
2020-11-10 18:22:26 +00:00
|
|
|
throw Exception(ErrorCodes::TOO_DEEP_RECURSION,
|
|
|
|
"Stack size too large. Stack address: {}, frame address: {}, stack size: {}, maximum stack size: {}",
|
|
|
|
stack_address, frame_address, stack_size, max_stack_size);
|
2019-08-10 17:51:47 +00:00
|
|
|
}
|
|
|
|
}
|