| 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 | |