Skip to content

Commit 74657d9

Browse files
authored
Defensive code for alloc/dealloc during TLS teardown (#161)
* Defensive code for alloc/dealloc during TLS teardown If an allocation or deallocation occurs during TLS teardown, then it is possible for a new allocator to be created and then this is leaked. On the mimalloc-bench mstressN benchmark this was observed leading to a large memory leak. This fix, detects if we are in the TLS teardown phase, and if so, the calls to alloc or dealloc must return the allocator once they have perform the specific operation. Uses a separate variable to represent if a thread_local's destructor has run already. This is used to detect thread teardown to put the allocator into a special slow path to avoid leaks. * Added some printing first operation to track progress * Improve error messages on posix Flush errors, print assert details, and present stack traces. * Detect incorrect use of pool. * Clang format. * Replace broken LL/SC implementation LL/SC implementation was broken, this replaces it with a locking implementation. Changes the API to support LL/SC for future implementation on ARM. * Improve TLS teardown. * Make std::function fully inlined. * Factor out PALLinux stack trace. * Add checks for leaking allocators. * Add release build of Windows Clang
1 parent d878880 commit 74657d9

File tree

13 files changed

+357
-145
lines changed

13 files changed

+357
-145
lines changed

CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ if(NOT DEFINED SNMALLOC_ONLY_HEADER_LIBRARY)
147147
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG")
148148
else()
149149
add_compile_options(-fno-exceptions -fno-rtti -g -ftls-model=initial-exec -fomit-frame-pointer)
150+
if(SNMALLOC_CI_BUILD OR (${CMAKE_BUILD_TYPE} MATCHES "Debug"))
151+
# Get better stack traces in CI and Debug.
152+
target_link_libraries(snmalloc_lib INTERFACE "-rdynamic")
153+
endif()
150154

151155
if((${CMAKE_SYSTEM_PROCESSOR} STREQUAL "x86_64") OR
152156
(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "x86") OR

azure-pipelines.yml

+12-10
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,11 @@ jobs:
224224
matrix:
225225
64-bit Debug Clang:
226226
BuildType: Debug
227-
CMakeArgs: '-GNinja -DCMAKE_C_COMPILER="C:/Program Files/LLVM/bin/clang.exe" -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe"'
227+
CMakeArgs: '-GNinja -DCMAKE_C_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe" -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe" -DCMAKE_RC_COMPILER="C:/Program Files/LLVM/bin/llvm-rc"'
228+
JFlag: -j 4
229+
64-bit Release Clang:
230+
BuildType: Release
231+
CMakeArgs: '-GNinja -DCMAKE_C_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe" -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe" -DCMAKE_RC_COMPILER="C:/Program Files/LLVM/bin/llvm-rc"'
228232
JFlag: -j 4
229233

230234
steps:
@@ -237,15 +241,13 @@ jobs:
237241
choco install --accept-license -y Ninja llvm
238242
displayName: 'Dependencies'
239243
240-
- task: CMake@1
241-
displayName: 'CMake .. $(CMakeArgs) -DCMAKE_BUILD_TYPE=$(BuildType) -DSNMALLOC_CI_BUILD=On -DSNMALLOC_RUST_SUPPORT=On'
242-
inputs:
243-
cmakeArgs: '.. $(CMakeArgs) -DCMAKE_BUILD_TYPE=$(BuildType) -DSNMALLOC_CI_BUILD=On -DSNMALLOC_RUST_SUPPORT=On'
244-
245-
- task: CMake@1
246-
displayName: 'CMake --build . --config ${BuildType}'
247-
inputs:
248-
cmakeArgs: '--build . --config ${BuildType}'
244+
- script: |
245+
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
246+
mkdir build
247+
cd build
248+
cmake .. $(CMakeArgs) -DCMAKE_BUILD_TYPE=$(BuildType) -DSNMALLOC_CI_BUILD=On -DSNMALLOC_RUST_SUPPORT=On
249+
cmake --build . --config ${BuildType}
250+
displayName: 'build'
249251
250252
- script: |
251253
set PATH=%PATH%;"C:\Program Files\LLVM\bin\"

src/ds/aba.h

+104-51
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,25 @@
22

33
#include "bits.h"
44

5+
/**
6+
* This file contains an abstraction of ABA protection. This API should be
7+
* implementable with double-word compare and exchange or with load-link
8+
* store conditional.
9+
*
10+
* We provide a lock based implementation.
11+
*/
512
namespace snmalloc
613
{
14+
#ifndef NDEBUG
15+
// LL/SC typically can only perform one operation at a time
16+
// check this on other platforms using a thread_local.
17+
inline thread_local bool operation_in_flight = false;
18+
#endif
19+
#ifdef PLATFORM_IS_X86
720
template<typename T, Construction c = RequiresInit>
821
class ABA
922
{
1023
public:
11-
#ifdef PLATFORM_IS_X86
1224
struct alignas(2 * sizeof(std::size_t)) Linked
1325
{
1426
T* ptr;
@@ -28,21 +40,12 @@ namespace snmalloc
2840
sizeof(Linked) == (2 * sizeof(std::size_t)),
2941
"Expecting ABA to be the size of two pointers");
3042

31-
using Cmp = Linked;
32-
#else
33-
using Cmp = T*;
34-
#endif
35-
3643
private:
37-
#ifdef PLATFORM_IS_X86
3844
union
3945
{
4046
alignas(2 * sizeof(std::size_t)) std::atomic<Linked> linked;
4147
Independent independent;
4248
};
43-
#else
44-
std::atomic<T*> raw;
45-
#endif
4649

4750
public:
4851
ABA()
@@ -53,66 +56,116 @@ namespace snmalloc
5356

5457
void init(T* x)
5558
{
56-
#ifdef PLATFORM_IS_X86
5759
independent.ptr.store(x, std::memory_order_relaxed);
5860
independent.aba.store(0, std::memory_order_relaxed);
59-
#else
60-
raw.store(x, std::memory_order_relaxed);
61-
#endif
6261
}
6362

64-
T* peek()
65-
{
66-
return
67-
#ifdef PLATFORM_IS_X86
68-
independent.ptr.load(std::memory_order_relaxed);
69-
#else
70-
raw.load(std::memory_order_relaxed);
71-
#endif
72-
}
63+
struct Cmp;
7364

7465
Cmp read()
7566
{
76-
return
77-
#ifdef PLATFORM_IS_X86
78-
Cmp{independent.ptr.load(std::memory_order_relaxed),
79-
independent.aba.load(std::memory_order_relaxed)};
80-
#else
81-
raw.load(std::memory_order_relaxed);
82-
#endif
67+
# ifndef NDEBUG
68+
if (operation_in_flight)
69+
error("Only one inflight ABA operation at a time is allowed.");
70+
operation_in_flight = true;
71+
# endif
72+
return Cmp{{independent.ptr.load(std::memory_order_relaxed),
73+
independent.aba.load(std::memory_order_relaxed)},
74+
this};
8375
}
8476

85-
static T* ptr(Cmp& from)
77+
struct Cmp
8678
{
87-
#ifdef PLATFORM_IS_X86
88-
return from.ptr;
89-
#else
90-
return from;
91-
#endif
92-
}
79+
Linked old;
80+
ABA* parent;
9381

94-
bool compare_exchange(Cmp& expect, T* value)
95-
{
96-
#ifdef PLATFORM_IS_X86
82+
T* ptr()
83+
{
84+
return old.ptr;
85+
}
86+
87+
bool store_conditional(T* value)
88+
{
9789
# if defined(_MSC_VER) && defined(SNMALLOC_VA_BITS_64)
98-
return _InterlockedCompareExchange128(
99-
(volatile __int64*)&linked,
100-
(__int64)(expect.aba + (uintptr_t)1),
101-
(__int64)value,
102-
(__int64*)&expect);
90+
auto result = _InterlockedCompareExchange128(
91+
(volatile __int64*)parent,
92+
(__int64)(old.aba + (uintptr_t)1),
93+
(__int64)value,
94+
(__int64*)&old);
10395
# else
10496
# if defined(__GNUC__) && !defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16)
10597
#error You must compile with -mcx16 to enable 16-byte atomic compare and swap.
10698
# endif
107-
Cmp xchg{value, expect.aba + 1};
99+
Linked xchg{value, old.aba + 1};
100+
std::atomic<Linked>& addr = parent->linked;
101+
102+
auto result = addr.compare_exchange_weak(
103+
old, xchg, std::memory_order_relaxed, std::memory_order_relaxed);
104+
# endif
105+
return result;
106+
}
108107

109-
return linked.compare_exchange_weak(
110-
expect, xchg, std::memory_order_relaxed, std::memory_order_relaxed);
108+
~Cmp()
109+
{
110+
# ifndef NDEBUG
111+
operation_in_flight = false;
111112
# endif
113+
}
114+
115+
Cmp(const Cmp&) = delete;
116+
};
117+
};
112118
#else
113-
return raw.compare_exchange_weak(
114-
expect, value, std::memory_order_relaxed, std::memory_order_relaxed);
115-
#endif
119+
/**
120+
* Naive implementation of ABA protection using a spin lock.
121+
*/
122+
template<typename T, Construction c = RequiresInit>
123+
class ABA
124+
{
125+
std::atomic<T*> ptr = nullptr;
126+
std::atomic_flag lock = ATOMIC_FLAG_INIT;
127+
128+
public:
129+
struct Cmp;
130+
131+
Cmp read()
132+
{
133+
while (lock.test_and_set(std::memory_order_acquire))
134+
Aal::pause();
135+
136+
# ifndef NDEBUG
137+
if (operation_in_flight)
138+
error("Only one inflight ABA operation at a time is allowed.");
139+
operation_in_flight = true;
140+
# endif
141+
142+
return Cmp{this};
116143
}
144+
145+
struct Cmp
146+
{
147+
ABA* parent;
148+
149+
public:
150+
T* ptr()
151+
{
152+
return parent->ptr;
153+
}
154+
155+
bool store_conditional(T* t)
156+
{
157+
parent->ptr = t;
158+
return true;
159+
}
160+
161+
~Cmp()
162+
{
163+
parent->lock.clear(std::memory_order_release);
164+
# ifndef NDEBUG
165+
operation_in_flight = false;
166+
# endif
167+
}
168+
};
117169
};
170+
#endif
118171
} // namespace snmalloc

src/ds/defines.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ namespace snmalloc
3636
void error(const char* const str);
3737
} // namespace snmalloc
3838

39+
#define TOSTRING(expr) TOSTRING2(expr)
40+
#define TOSTRING2(expr) #expr
41+
3942
#ifdef NDEBUG
4043
# define SNMALLOC_ASSERT(expr) \
4144
{}
@@ -44,7 +47,8 @@ namespace snmalloc
4447
{ \
4548
if (!(expr)) \
4649
{ \
47-
snmalloc::error("assert fail"); \
50+
snmalloc::error("assert fail: " #expr " in " __FILE__ \
51+
" on " TOSTRING(__LINE__)); \
4852
} \
4953
}
5054
#endif

src/ds/mpmcstack.h

+6-6
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ namespace snmalloc
2929

3030
do
3131
{
32-
T* top = ABAT::ptr(cmp);
32+
T* top = cmp.ptr();
3333
last->next.store(top, std::memory_order_release);
34-
} while (!stack.compare_exchange(cmp, first));
34+
} while (!cmp.store_conditional(first));
3535
}
3636

3737
T* pop()
@@ -44,13 +44,13 @@ namespace snmalloc
4444

4545
do
4646
{
47-
top = ABAT::ptr(cmp);
47+
top = cmp.ptr();
4848

4949
if (top == nullptr)
5050
break;
5151

5252
next = top->next.load(std::memory_order_acquire);
53-
} while (!stack.compare_exchange(cmp, next));
53+
} while (!cmp.store_conditional(next));
5454

5555
return top;
5656
}
@@ -63,11 +63,11 @@ namespace snmalloc
6363

6464
do
6565
{
66-
top = ABAT::ptr(cmp);
66+
top = cmp.ptr();
6767

6868
if (top == nullptr)
6969
break;
70-
} while (!stack.compare_exchange(cmp, nullptr));
70+
} while (!cmp.store_conditional(nullptr));
7171

7272
return top;
7373
}

0 commit comments

Comments
 (0)