fix alloca usage
[m6w6/libmemcached] / src / libmemcachedutil / pool.cc
1 /*
2 +--------------------------------------------------------------------+
3 | libmemcached - C/C++ Client Library for memcached |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted under the terms of the BSD license. |
7 | You should have received a copy of the license in a bundled file |
8 | named LICENSE; in case you did not receive a copy you can review |
9 | the terms online at: https://opensource.org/licenses/BSD-3-Clause |
10 +--------------------------------------------------------------------+
11 | Copyright (c) 2006-2014 Brian Aker https://datadifferential.com/ |
12 | Copyright (c) 2020 Michael Wallner <mike@php.net> |
13 +--------------------------------------------------------------------+
14 */
15
16 #include "libmemcachedutil/common.h"
17
18 #include <cassert>
19 #include <cerrno>
20 #include <cstring>
21 #include <pthread.h>
22 #include <memory>
23
24 struct memcached_pool_st {
25 pthread_mutex_t mutex;
26 pthread_cond_t cond;
27 memcached_st *master;
28 memcached_st **server_pool;
29 int firstfree;
30 const uint32_t size;
31 uint32_t current_size;
32 bool _owns_master;
33 struct timespec _timeout;
34
35 memcached_pool_st(memcached_st *master_arg, size_t max_arg)
36 : master(master_arg)
37 , server_pool(NULL)
38 , firstfree(-1)
39 , size(uint32_t(max_arg))
40 , current_size(0)
41 , _owns_master(false) {
42 pthread_mutex_init(&mutex, NULL);
43 pthread_cond_init(&cond, NULL);
44 _timeout.tv_sec = 5;
45 _timeout.tv_nsec = 0;
46 }
47
48 const struct timespec &timeout() const {
49 return _timeout;
50 }
51
52 bool release(memcached_st *, memcached_return_t &rc);
53
54 memcached_st *fetch(memcached_return_t &rc);
55 memcached_st *fetch(const struct timespec &, memcached_return_t &rc);
56
57 bool init(uint32_t initial);
58
59 ~memcached_pool_st() {
60 for (int x = 0; x <= firstfree; ++x) {
61 memcached_free(server_pool[x]);
62 server_pool[x] = NULL;
63 }
64
65 int error;
66 if ((error = pthread_mutex_destroy(&mutex))) {
67 assert_vmsg(error, "pthread_mutex_destroy() %s(%d)", strerror(error), error);
68 }
69
70 if ((error = pthread_cond_destroy(&cond))) {
71 assert_vmsg(error, "pthread_cond_destroy() %s", strerror(error));
72 }
73
74 delete[] server_pool;
75 if (_owns_master) {
76 memcached_free(master);
77 }
78 }
79
80 void increment_version() {
81 ++master->configure.version;
82 }
83
84 bool compare_version(const memcached_st *arg) const {
85 return (arg->configure.version == version());
86 }
87
88 int32_t version() const {
89 return master->configure.version;
90 }
91 };
92
93 /**
94 * Grow the connection pool by creating a connection structure and clone the
95 * original memcached handle.
96 */
97 static bool grow_pool(memcached_pool_st *pool) {
98 assert(pool);
99
100 memcached_st *obj;
101 if (not(obj = memcached_clone(NULL, pool->master))) {
102 return false;
103 }
104
105 pool->server_pool[++pool->firstfree] = obj;
106 pool->current_size++;
107 obj->configure.version = pool->version();
108
109 return true;
110 }
111
112 bool memcached_pool_st::init(uint32_t initial) {
113 server_pool = new (std::nothrow) memcached_st *[size];
114 if (server_pool == NULL) {
115 return false;
116 }
117
118 /*
119 Try to create the initial size of the pool. An allocation failure at
120 this time is not fatal..
121 */
122 for (unsigned int x = 0; x < initial; ++x) {
123 if (grow_pool(this) == false) {
124 break;
125 }
126 }
127
128 return true;
129 }
130
131 static inline memcached_pool_st *_pool_create(memcached_st *master, uint32_t initial,
132 uint32_t max) {
133 if (initial == 0 or max == 0 or (initial > max)) {
134 return NULL;
135 }
136
137 memcached_pool_st *object = new (std::nothrow) memcached_pool_st(master, max);
138 if (object == NULL) {
139 return NULL;
140 }
141
142 /*
143 Try to create the initial size of the pool. An allocation failure at
144 this time is not fatal..
145 */
146 if (not object->init(initial)) {
147 delete object;
148 return NULL;
149 }
150
151 return object;
152 }
153
154 memcached_pool_st *memcached_pool_create(memcached_st *master, uint32_t initial, uint32_t max) {
155 return _pool_create(master, initial, max);
156 }
157
158 memcached_pool_st *memcached_pool(const char *option_string, size_t option_string_length) {
159 memcached_st *memc = memcached(option_string, option_string_length);
160
161 if (memc == NULL) {
162 return NULL;
163 }
164
165 memcached_pool_st *self =
166 memcached_pool_create(memc, memc->configure.initial_pool_size, memc->configure.max_pool_size);
167 if (self == NULL) {
168 memcached_free(memc);
169 return NULL;
170 }
171
172 self->_owns_master = true;
173
174 return self;
175 }
176
177 memcached_st *memcached_pool_destroy(memcached_pool_st *pool) {
178 if (pool == NULL) {
179 return NULL;
180 }
181
182 // Legacy that we return the original structure
183 memcached_st *ret = NULL;
184 if (pool->_owns_master) {
185 } else {
186 ret = pool->master;
187 }
188
189 delete pool;
190
191 return ret;
192 }
193
194 memcached_st *memcached_pool_st::fetch(memcached_return_t &rc) {
195 static struct timespec relative_time = {0, 0};
196 return fetch(relative_time, rc);
197 }
198
199 memcached_st *memcached_pool_st::fetch(const struct timespec &relative_time,
200 memcached_return_t &rc) {
201 rc = MEMCACHED_SUCCESS;
202
203 int error;
204 if ((error = pthread_mutex_lock(&mutex))) {
205 rc = MEMCACHED_IN_PROGRESS;
206 return NULL;
207 }
208
209 memcached_st *ret = NULL;
210 do {
211 if (firstfree > -1) {
212 ret = server_pool[firstfree--];
213 } else if (current_size == size) {
214 if (relative_time.tv_sec == 0 and relative_time.tv_nsec == 0) {
215 error = pthread_mutex_unlock(&mutex);
216 rc = MEMCACHED_NOTFOUND;
217
218 return NULL;
219 }
220
221 struct timespec time_to_wait = {0, 0};
222 time_to_wait.tv_sec = time(NULL) + relative_time.tv_sec;
223 time_to_wait.tv_nsec = relative_time.tv_nsec;
224
225 int thread_ret;
226 if ((thread_ret = pthread_cond_timedwait(&cond, &mutex, &time_to_wait))) {
227 int unlock_error;
228 if ((unlock_error = pthread_mutex_unlock(&mutex))) {
229 assert_vmsg(error, "pthread_mutex_unlock() %s", strerror(error));
230 }
231
232 if (thread_ret == ETIMEDOUT) {
233 rc = MEMCACHED_TIMEOUT;
234 } else {
235 errno = thread_ret;
236 rc = MEMCACHED_ERRNO;
237 }
238
239 return NULL;
240 }
241 } else if (grow_pool(this) == false) {
242 int unlock_error;
243 if ((unlock_error = pthread_mutex_unlock(&mutex))) {
244 assert_vmsg(error, "pthread_mutex_unlock() %s", strerror(error));
245 }
246
247 return NULL;
248 }
249 } while (ret == NULL);
250
251 if ((error = pthread_mutex_unlock(&mutex))) {
252 assert_vmsg(error, "pthread_mutex_unlock() %s", strerror(error));
253 }
254
255 return ret;
256 }
257
258 bool memcached_pool_st::release(memcached_st *released, memcached_return_t &rc) {
259 rc = MEMCACHED_SUCCESS;
260 if (released == NULL) {
261 rc = MEMCACHED_INVALID_ARGUMENTS;
262 return false;
263 }
264
265 int error;
266 if ((error = pthread_mutex_lock(&mutex))) {
267 rc = MEMCACHED_IN_PROGRESS;
268 return false;
269 }
270
271 /*
272 Someone updated the behavior on the object, so we clone a new memcached_st with the new
273 settings. If we fail to clone, we keep the old one around.
274 */
275 if (compare_version(released) == false) {
276 memcached_st *memc;
277 if ((memc = memcached_clone(NULL, master))) {
278 memcached_free(released);
279 released = memc;
280 }
281 }
282
283 server_pool[++firstfree] = released;
284
285 if (firstfree == 0 and current_size == size) {
286 /* we might have people waiting for a connection.. wake them up :-) */
287 if ((error = pthread_cond_broadcast(&cond))) {
288 assert_vmsg(error, "pthread_cond_broadcast() %s", strerror(error));
289 }
290 }
291
292 if ((error = pthread_mutex_unlock(&mutex))) {
293 }
294
295 return true;
296 }
297
298 memcached_st *memcached_pool_fetch(memcached_pool_st *pool, struct timespec *relative_time,
299 memcached_return_t *rc) {
300 if (pool == NULL) {
301 return NULL;
302 }
303
304 memcached_return_t unused;
305 if (rc == NULL) {
306 rc = &unused;
307 }
308
309 if (relative_time == NULL) {
310 return pool->fetch(*rc);
311 }
312
313 return pool->fetch(*relative_time, *rc);
314 }
315
316 memcached_st *memcached_pool_pop(memcached_pool_st *pool, bool block, memcached_return_t *rc) {
317 if (pool == NULL) {
318 return NULL;
319 }
320
321 memcached_return_t unused;
322 if (rc == NULL) {
323 rc = &unused;
324 }
325
326 memcached_st *memc;
327 if (block) {
328 memc = pool->fetch(pool->timeout(), *rc);
329 } else {
330 memc = pool->fetch(*rc);
331 }
332
333 return memc;
334 }
335
336 memcached_return_t memcached_pool_release(memcached_pool_st *pool, memcached_st *released) {
337 if (pool == NULL) {
338 return MEMCACHED_INVALID_ARGUMENTS;
339 }
340
341 memcached_return_t rc;
342
343 (void) pool->release(released, rc);
344
345 return rc;
346 }
347
348 memcached_return_t memcached_pool_push(memcached_pool_st *pool, memcached_st *released) {
349 return memcached_pool_release(pool, released);
350 }
351
352 memcached_return_t memcached_pool_behavior_set(memcached_pool_st *pool, memcached_behavior_t flag,
353 uint64_t data) {
354 if (pool == NULL) {
355 return MEMCACHED_INVALID_ARGUMENTS;
356 }
357
358 int error;
359 if ((error = pthread_mutex_lock(&pool->mutex))) {
360 return MEMCACHED_IN_PROGRESS;
361 }
362
363 /* update the master */
364 memcached_return_t rc = memcached_behavior_set(pool->master, flag, data);
365 if (memcached_failed(rc)) {
366 if ((error = pthread_mutex_unlock(&pool->mutex))) {
367 assert_vmsg(error, "pthread_mutex_unlock() %s", strerror(error));
368 }
369 return rc;
370 }
371
372 pool->increment_version();
373 /* update the clones */
374 for (int xx = 0; xx <= pool->firstfree; ++xx) {
375 if (memcached_success(memcached_behavior_set(pool->server_pool[xx], flag, data))) {
376 pool->server_pool[xx]->configure.version = pool->version();
377 } else {
378 memcached_st *memc;
379 if ((memc = memcached_clone(NULL, pool->master))) {
380 memcached_free(pool->server_pool[xx]);
381 pool->server_pool[xx] = memc;
382 /* I'm not sure what to do in this case.. this would happen
383 if we fail to push the server list inside the client..
384 I should add a testcase for this, but I believe the following
385 would work, except that you would add a hole in the pool list..
386 in theory you could end up with an empty pool....
387 */
388 }
389 }
390 }
391
392 if ((error = pthread_mutex_unlock(&pool->mutex))) {
393 assert_vmsg(error, "pthread_mutex_unlock() %s", strerror(error));
394 }
395
396 return rc;
397 }
398
399 memcached_return_t memcached_pool_behavior_get(memcached_pool_st *pool, memcached_behavior_t flag,
400 uint64_t *value) {
401 if (pool == NULL) {
402 return MEMCACHED_INVALID_ARGUMENTS;
403 }
404
405 int error;
406 if ((error = pthread_mutex_lock(&pool->mutex))) {
407 return MEMCACHED_IN_PROGRESS;
408 }
409
410 *value = memcached_behavior_get(pool->master, flag);
411
412 if ((error = pthread_mutex_unlock(&pool->mutex))) {
413 assert_vmsg(error, "pthread_mutex_unlock() %s", strerror(error));
414 }
415
416 return MEMCACHED_SUCCESS;
417 }