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 | /*====== |
5 | This file is part of PerconaFT. |
6 | |
7 | |
8 | Copyright (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 | |
48 | namespace 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 | |
61 | void toku_scoped_malloc_init(void) {} |
62 | void toku_scoped_malloc_destroy(void) {} |
63 | void toku_scoped_malloc_destroy_set(void) {} |
64 | void 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 | |
73 | namespace 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 | |
191 | void 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 | |
202 | void toku_scoped_malloc_destroy(void) { |
203 | toku_scoped_malloc_destroy_key(); |
204 | toku_scoped_malloc_destroy_set(); |
205 | } |
206 | |
207 | void 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 | |
222 | void 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 | |