1/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2// vim: ft=cpp:expandtab:ts=8:sw=4:softtabstop=4:
3#ident "$Id$"
4/*======
5This file is part of PerconaFT.
6
7
8Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved.
9
10 PerconaFT is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License, version 2,
12 as published by the Free Software Foundation.
13
14 PerconaFT is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with PerconaFT. If not, see <http://www.gnu.org/licenses/>.
21
22----------------------------------------
23
24 PerconaFT is free software: you can redistribute it and/or modify
25 it under the terms of the GNU Affero General Public License, version 3,
26 as published by the Free Software Foundation.
27
28 PerconaFT is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 GNU Affero General Public License for more details.
32
33 You should have received a copy of the GNU Affero General Public License
34 along with PerconaFT. If not, see <http://www.gnu.org/licenses/>.
35======= */
36
37#ident "Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved."
38
39#include <portability/memory.h>
40
41#include <util/scoped_malloc.h>
42
43// The __thread storage class modifier isn't well supported on osx, but we
44// aren't worried about the performance on osx, so we provide a
45// pass-through implementation of scoped mallocs.
46#ifdef __APPLE__
47
48namespace toku {
49
50 scoped_malloc::scoped_malloc(const size_t size)
51 : m_size(size),
52 m_local(false),
53 m_buf(toku_xmalloc(size)) {}
54
55 scoped_malloc::~scoped_malloc() {
56 toku_free(m_buf);
57 }
58
59} // namespace toku
60
61void toku_scoped_malloc_init(void) {}
62void toku_scoped_malloc_destroy(void) {}
63void toku_scoped_malloc_destroy_set(void) {}
64void toku_scoped_malloc_destroy_key(void) {}
65
66#else // __APPLE__
67
68#include <set>
69#include <pthread.h>
70
71#include <portability/toku_pthread.h>
72
73namespace toku {
74
75 // see pthread_key handling at the bottom
76 //
77 // when we use gcc 4.8, we can use the 'thread_local' keyword and proper c++
78 // constructors/destructors instead of this pthread / global set wizardy.
79 static pthread_key_t tl_stack_destroy_pthread_key;
80 class tl_stack;
81 std::set<tl_stack *> *global_stack_set;
82 toku_mutex_t global_stack_set_mutex = TOKU_MUTEX_INITIALIZER;
83
84 class tl_stack {
85 // 1MB
86 static const size_t STACK_SIZE = 1 * 1024 * 1024;
87
88 public:
89 void init() {
90 m_stack = reinterpret_cast<char *>(toku_xmalloc(STACK_SIZE));
91 m_current_offset = 0;
92 int r = pthread_setspecific(tl_stack_destroy_pthread_key, this);
93 invariant_zero(r);
94 }
95
96 void destroy() {
97#ifdef TOKU_SCOPED_MALLOC_DEBUG
98 printf("%s %p %p\n", __FUNCTION__, this, m_stack);
99#endif
100 if (m_stack != NULL) {
101 toku_free(m_stack);
102 m_stack = NULL;
103 }
104 }
105
106 // initialize a tl_stack and insert it into the global map
107 static void init_and_register(tl_stack *st) {
108 st->init();
109 invariant_notnull(global_stack_set);
110
111 toku_mutex_lock(&global_stack_set_mutex);
112 std::pair<std::set<tl_stack *>::iterator, bool> p = global_stack_set->insert(st);
113 invariant(p.second);
114 toku_mutex_unlock(&global_stack_set_mutex);
115 }
116
117 // destruct a tl_stack and remove it from the global map
118 // passed in as void * to match the generic pthread destructor API
119 static void destroy_and_deregister(void *key) {
120 invariant_notnull(key);
121 tl_stack *st = reinterpret_cast<tl_stack *>(key);
122
123 size_t n = 0;
124 toku_mutex_lock(&global_stack_set_mutex);
125 if (global_stack_set) {
126 n = global_stack_set->erase(st);
127 }
128 toku_mutex_unlock(&global_stack_set_mutex);
129
130 if (n == 1) {
131 st->destroy(); // destroy the stack if this function erased it from the set. otherwise, somebody else destroyed it.
132 }
133 }
134
135 // Allocate 'size' bytes and return a pointer to the first byte
136 void *alloc(const size_t size) {
137 if (m_stack == NULL) {
138 init_and_register(this);
139 }
140 invariant(m_current_offset + size <= STACK_SIZE);
141 void *mem = &m_stack[m_current_offset];
142 m_current_offset += size;
143 return mem;
144 }
145
146 // Give back a previously allocated region of 'size' bytes.
147 void dealloc(const size_t size) {
148 invariant(m_current_offset >= size);
149 m_current_offset -= size;
150 }
151
152 // Get the current size of free-space in bytes.
153 size_t get_free_space() const {
154 invariant(m_current_offset <= STACK_SIZE);
155 return STACK_SIZE - m_current_offset;
156 }
157
158 private:
159 // Offset of the free region in the stack
160 size_t m_current_offset;
161 char *m_stack;
162 };
163
164 // Each thread has its own local stack.
165 static __thread tl_stack local_stack;
166
167 // Memory is allocated from thread-local storage if available, otherwise from malloc(1).
168 scoped_malloc::scoped_malloc(const size_t size) :
169 m_size(size),
170 m_local(local_stack.get_free_space() >= m_size),
171 m_buf(m_local ? local_stack.alloc(m_size) : toku_xmalloc(m_size)) {
172 }
173
174 scoped_malloc::~scoped_malloc() {
175 if (m_local) {
176 local_stack.dealloc(m_size);
177 } else {
178 toku_free(m_buf);
179 }
180 }
181
182} // namespace toku
183
184// pthread key handling:
185// - there is a process-wide pthread key that is associated with the destructor for a tl_stack
186// - on process construction, we initialize the key; on destruction, we clean it up.
187// - when a thread first uses its tl_stack, it calls pthread_setspecific(&destroy_key, "some key"),
188// associating the destroy key with the tl_stack_destroy_and_deregister destructor
189// - when a thread terminates, it calls the associated destructor; tl_stack_destroy_and_deregister.
190
191void toku_scoped_malloc_init(void) {
192 toku_mutex_lock(&toku::global_stack_set_mutex);
193 invariant_null(toku::global_stack_set);
194 toku::global_stack_set = new std::set<toku::tl_stack *>();
195 toku_mutex_unlock(&toku::global_stack_set_mutex);
196
197 int r = pthread_key_create(&toku::tl_stack_destroy_pthread_key,
198 toku::tl_stack::destroy_and_deregister);
199 invariant_zero(r);
200}
201
202void toku_scoped_malloc_destroy(void) {
203 toku_scoped_malloc_destroy_key();
204 toku_scoped_malloc_destroy_set();
205}
206
207void toku_scoped_malloc_destroy_set(void) {
208 toku_mutex_lock(&toku::global_stack_set_mutex);
209 invariant_notnull(toku::global_stack_set);
210 // Destroy any tl_stacks that were registered as thread locals but did not
211 // get a chance to clean up using the pthread key destructor (because this code
212 // is now running before those threads fully shutdown)
213 for (std::set<toku::tl_stack *>::iterator i = toku::global_stack_set->begin();
214 i != toku::global_stack_set->end(); i++) {
215 (*i)->destroy();
216 }
217 delete toku::global_stack_set;
218 toku::global_stack_set = nullptr;
219 toku_mutex_unlock(&toku::global_stack_set_mutex);
220}
221
222void toku_scoped_malloc_destroy_key(void) {
223 int r = pthread_key_delete(toku::tl_stack_destroy_pthread_key);
224 invariant_zero(r);
225}
226
227#endif // !__APPLE__
228