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 +--------------------------------------------------------------------+
16 #include "libmemcachedutil/common.h"
24 struct memcached_pool_st
{
25 pthread_mutex_t mutex
;
28 memcached_st
**server_pool
;
31 uint32_t current_size
;
33 struct timespec _timeout
;
35 memcached_pool_st(memcached_st
*master_arg
, size_t max_arg
)
39 , size(uint32_t(max_arg
))
41 , _owns_master(false) {
42 pthread_mutex_init(&mutex
, NULL
);
43 pthread_cond_init(&cond
, NULL
);
48 const struct timespec
&timeout() const {
52 bool release(memcached_st
*, memcached_return_t
&rc
);
54 memcached_st
*fetch(memcached_return_t
&rc
);
55 memcached_st
*fetch(const struct timespec
&, memcached_return_t
&rc
);
57 bool init(uint32_t initial
);
59 ~memcached_pool_st() {
60 for (int x
= 0; x
<= firstfree
; ++x
) {
61 memcached_free(server_pool
[x
]);
62 server_pool
[x
] = NULL
;
66 if ((error
= pthread_mutex_destroy(&mutex
))) {
67 assert_vmsg(error
, "pthread_mutex_destroy() %s(%d)", strerror(error
), error
);
70 if ((error
= pthread_cond_destroy(&cond
))) {
71 assert_vmsg(error
, "pthread_cond_destroy() %s", strerror(error
));
76 memcached_free(master
);
80 void increment_version() {
81 ++master
->configure
.version
;
84 bool compare_version(const memcached_st
*arg
) const {
85 return (arg
->configure
.version
== version());
88 int32_t version() const {
89 return master
->configure
.version
;
94 * Grow the connection pool by creating a connection structure and clone the
95 * original memcached handle.
97 static bool grow_pool(memcached_pool_st
*pool
) {
101 if (not(obj
= memcached_clone(NULL
, pool
->master
))) {
105 pool
->server_pool
[++pool
->firstfree
] = obj
;
106 pool
->current_size
++;
107 obj
->configure
.version
= pool
->version();
112 bool memcached_pool_st::init(uint32_t initial
) {
113 server_pool
= new (std::nothrow
) memcached_st
*[size
];
114 if (server_pool
== NULL
) {
119 Try to create the initial size of the pool. An allocation failure at
120 this time is not fatal..
122 for (unsigned int x
= 0; x
< initial
; ++x
) {
123 if (grow_pool(this) == false) {
131 static inline memcached_pool_st
*_pool_create(memcached_st
*master
, uint32_t initial
,
133 if (initial
== 0 or max
== 0 or (initial
> max
)) {
137 memcached_pool_st
*object
= new (std::nothrow
) memcached_pool_st(master
, max
);
138 if (object
== NULL
) {
143 Try to create the initial size of the pool. An allocation failure at
144 this time is not fatal..
146 if (not object
->init(initial
)) {
154 memcached_pool_st
*memcached_pool_create(memcached_st
*master
, uint32_t initial
, uint32_t max
) {
155 return _pool_create(master
, initial
, max
);
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
);
165 memcached_pool_st
*self
=
166 memcached_pool_create(memc
, memc
->configure
.initial_pool_size
, memc
->configure
.max_pool_size
);
168 memcached_free(memc
);
172 self
->_owns_master
= true;
177 memcached_st
*memcached_pool_destroy(memcached_pool_st
*pool
) {
182 // Legacy that we return the original structure
183 memcached_st
*ret
= NULL
;
184 if (pool
->_owns_master
) {
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
);
199 memcached_st
*memcached_pool_st::fetch(const struct timespec
&relative_time
,
200 memcached_return_t
&rc
) {
201 rc
= MEMCACHED_SUCCESS
;
204 if ((error
= pthread_mutex_lock(&mutex
))) {
205 rc
= MEMCACHED_IN_PROGRESS
;
209 memcached_st
*ret
= NULL
;
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
;
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
;
226 if ((thread_ret
= pthread_cond_timedwait(&cond
, &mutex
, &time_to_wait
))) {
228 if ((unlock_error
= pthread_mutex_unlock(&mutex
))) {
229 assert_vmsg(error
, "pthread_mutex_unlock() %s", strerror(error
));
232 if (thread_ret
== ETIMEDOUT
) {
233 rc
= MEMCACHED_TIMEOUT
;
236 rc
= MEMCACHED_ERRNO
;
241 } else if (grow_pool(this) == false) {
243 if ((unlock_error
= pthread_mutex_unlock(&mutex
))) {
244 assert_vmsg(error
, "pthread_mutex_unlock() %s", strerror(error
));
249 } while (ret
== NULL
);
251 if ((error
= pthread_mutex_unlock(&mutex
))) {
252 assert_vmsg(error
, "pthread_mutex_unlock() %s", strerror(error
));
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
;
266 if ((error
= pthread_mutex_lock(&mutex
))) {
267 rc
= MEMCACHED_IN_PROGRESS
;
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.
275 if (compare_version(released
) == false) {
277 if ((memc
= memcached_clone(NULL
, master
))) {
278 memcached_free(released
);
283 server_pool
[++firstfree
] = released
;
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
));
292 if ((error
= pthread_mutex_unlock(&mutex
))) {
298 memcached_st
*memcached_pool_fetch(memcached_pool_st
*pool
, struct timespec
*relative_time
,
299 memcached_return_t
*rc
) {
304 memcached_return_t unused
;
309 if (relative_time
== NULL
) {
310 return pool
->fetch(*rc
);
313 return pool
->fetch(*relative_time
, *rc
);
316 memcached_st
*memcached_pool_pop(memcached_pool_st
*pool
, bool block
, memcached_return_t
*rc
) {
321 memcached_return_t unused
;
328 memc
= pool
->fetch(pool
->timeout(), *rc
);
330 memc
= pool
->fetch(*rc
);
336 memcached_return_t
memcached_pool_release(memcached_pool_st
*pool
, memcached_st
*released
) {
338 return MEMCACHED_INVALID_ARGUMENTS
;
341 memcached_return_t rc
;
343 (void) pool
->release(released
, rc
);
348 memcached_return_t
memcached_pool_push(memcached_pool_st
*pool
, memcached_st
*released
) {
349 return memcached_pool_release(pool
, released
);
352 memcached_return_t
memcached_pool_behavior_set(memcached_pool_st
*pool
, memcached_behavior_t flag
,
355 return MEMCACHED_INVALID_ARGUMENTS
;
359 if ((error
= pthread_mutex_lock(&pool
->mutex
))) {
360 return MEMCACHED_IN_PROGRESS
;
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
));
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();
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....
392 if ((error
= pthread_mutex_unlock(&pool
->mutex
))) {
393 assert_vmsg(error
, "pthread_mutex_unlock() %s", strerror(error
));
399 memcached_return_t
memcached_pool_behavior_get(memcached_pool_st
*pool
, memcached_behavior_t flag
,
402 return MEMCACHED_INVALID_ARGUMENTS
;
406 if ((error
= pthread_mutex_lock(&pool
->mutex
))) {
407 return MEMCACHED_IN_PROGRESS
;
410 *value
= memcached_behavior_get(pool
->master
, flag
);
412 if ((error
= pthread_mutex_unlock(&pool
->mutex
))) {
413 assert_vmsg(error
, "pthread_mutex_unlock() %s", strerror(error
));
416 return MEMCACHED_SUCCESS
;