/src/pjsip/pjlib/src/pj/pool_caching.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) |
3 | | * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> |
4 | | * |
5 | | * This program is free software; you can redistribute it and/or modify |
6 | | * it under the terms of the GNU General Public License as published by |
7 | | * the Free Software Foundation; either version 2 of the License, or |
8 | | * (at your option) any later version. |
9 | | * |
10 | | * This program is distributed in the hope that it will be useful, |
11 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | * GNU General Public License for more details. |
14 | | * |
15 | | * You should have received a copy of the GNU General Public License |
16 | | * along with this program; if not, write to the Free Software |
17 | | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
18 | | */ |
19 | | #include <pj/pool.h> |
20 | | #include <pj/assert.h> |
21 | | #include <pj/log.h> |
22 | | #include <pj/string.h> |
23 | | #include <pj/assert.h> |
24 | | #include <pj/lock.h> |
25 | | #include <pj/os.h> |
26 | | #include <pj/pool_buf.h> |
27 | | |
28 | | #if !PJ_HAS_POOL_ALT_API |
29 | | |
30 | | static pj_pool_t* cpool_create_pool(pj_pool_factory *pf, |
31 | | const char *name, |
32 | | pj_size_t initial_size, |
33 | | pj_size_t increment_sz, |
34 | | pj_size_t alignment, |
35 | | pj_pool_callback *callback); |
36 | | static void cpool_release_pool(pj_pool_factory *pf, pj_pool_t *pool); |
37 | | static void cpool_dump_status(pj_pool_factory *factory, pj_bool_t detail ); |
38 | | static pj_bool_t cpool_on_block_alloc(pj_pool_factory *f, pj_size_t sz); |
39 | | static void cpool_on_block_free(pj_pool_factory *f, pj_size_t sz); |
40 | | |
41 | | |
42 | | static pj_size_t pool_sizes[PJ_CACHING_POOL_ARRAY_SIZE] = |
43 | | { |
44 | | 256, 512, 1024, 2048, 4096, 8192, 12288, 16384, |
45 | | 20480, 24576, 28672, 32768, 40960, 49152, 57344, 65536 |
46 | | }; |
47 | | |
48 | | /* Index where the search for size should begin. |
49 | | * Start with pool_sizes[5], which is 8192. |
50 | | */ |
51 | 76 | #define START_SIZE 5 |
52 | | |
53 | | |
54 | | PJ_DEF(void) pj_caching_pool_init( pj_caching_pool *cp, |
55 | | const pj_pool_factory_policy *policy, |
56 | | pj_size_t max_capacity) |
57 | 38 | { |
58 | 38 | int i; |
59 | 38 | pj_pool_t *pool; |
60 | 38 | pj_status_t status; |
61 | | |
62 | 38 | PJ_CHECK_STACK(); |
63 | | |
64 | 38 | pj_bzero(cp, sizeof(*cp)); |
65 | | |
66 | 38 | cp->max_capacity = max_capacity; |
67 | 38 | pj_list_init(&cp->used_list); |
68 | 646 | for (i=0; i<PJ_CACHING_POOL_ARRAY_SIZE; ++i) |
69 | 608 | pj_list_init(&cp->free_list[i]); |
70 | | |
71 | 38 | if (policy == NULL) { |
72 | 0 | policy = &pj_pool_factory_default_policy; |
73 | 0 | } |
74 | | |
75 | 38 | pj_memcpy(&cp->factory.policy, policy, sizeof(pj_pool_factory_policy)); |
76 | 38 | cp->factory.create_pool = &cpool_create_pool; |
77 | 38 | cp->factory.release_pool = &cpool_release_pool; |
78 | 38 | cp->factory.dump_status = &cpool_dump_status; |
79 | | |
80 | | /* Deprecated, these callbacks are only used for updating cp.used_size & |
81 | | * cp.peak_used_size which are no longer used. |
82 | | */ |
83 | | //cp->factory.on_block_alloc = &cpool_on_block_alloc; |
84 | | //cp->factory.on_block_free = &cpool_on_block_free; |
85 | 38 | PJ_UNUSED_ARG(cpool_on_block_alloc); |
86 | 38 | PJ_UNUSED_ARG(cpool_on_block_free); |
87 | | |
88 | 38 | pool = pj_pool_create_on_buf("cachingpool", cp->pool_buf, sizeof(cp->pool_buf)); |
89 | 38 | status = pj_lock_create_simple_mutex(pool, "cachingpool", &cp->lock); |
90 | | /* This mostly serves to silent coverity warning about unchecked |
91 | | * return value. There's not much we can do if it fails. */ |
92 | 38 | PJ_ASSERT_ON_FAIL(status==PJ_SUCCESS, return); |
93 | 38 | } |
94 | | |
95 | | PJ_DEF(void) pj_caching_pool_destroy( pj_caching_pool *cp ) |
96 | 38 | { |
97 | 38 | int i; |
98 | 38 | pj_pool_t *pool; |
99 | | |
100 | 38 | PJ_CHECK_STACK(); |
101 | | |
102 | | /* Delete all pool in free list */ |
103 | 646 | for (i=0; i < PJ_CACHING_POOL_ARRAY_SIZE; ++i) { |
104 | 608 | pj_pool_t *next; |
105 | 608 | pool = (pj_pool_t*) cp->free_list[i].next; |
106 | 608 | for (; pool != (void*)&cp->free_list[i]; pool = next) { |
107 | 0 | next = pool->next; |
108 | 0 | pj_list_erase(pool); |
109 | 0 | pj_pool_destroy_int(pool); |
110 | 0 | } |
111 | 608 | } |
112 | | |
113 | | /* Delete all pools in used list */ |
114 | 38 | pool = (pj_pool_t*) cp->used_list.next; |
115 | 38 | while (pool != (pj_pool_t*) &cp->used_list) { |
116 | 0 | pj_pool_t *next = pool->next; |
117 | 0 | pj_list_erase(pool); |
118 | 0 | PJ_LOG(4,(pool->obj_name, |
119 | 0 | "Pool is not released by application, releasing now")); |
120 | 0 | pj_pool_destroy_int(pool); |
121 | 0 | pool = next; |
122 | 0 | } |
123 | | |
124 | 38 | if (cp->lock) { |
125 | 38 | pj_status_t status; |
126 | 38 | pj_lock_destroy(cp->lock); |
127 | 38 | status = pj_lock_create_null_mutex(NULL, "cachingpool", &cp->lock); |
128 | 38 | PJ_ASSERT_ON_FAIL(status==PJ_SUCCESS, return); |
129 | 38 | } |
130 | 38 | } |
131 | | |
132 | | static pj_pool_t* cpool_create_pool(pj_pool_factory *pf, |
133 | | const char *name, |
134 | | pj_size_t initial_size, |
135 | | pj_size_t increment_sz, |
136 | | pj_size_t alignment, |
137 | | pj_pool_callback *callback) |
138 | 38 | { |
139 | 38 | pj_caching_pool *cp = (pj_caching_pool*)pf; |
140 | 38 | pj_pool_t *pool; |
141 | 38 | int idx; |
142 | | |
143 | 38 | PJ_CHECK_STACK(); |
144 | | |
145 | 38 | pj_lock_acquire(cp->lock); |
146 | | |
147 | | /* Use pool factory's policy when callback is NULL */ |
148 | 38 | if (callback == NULL) { |
149 | 38 | callback = pf->policy.callback; |
150 | 38 | } |
151 | | |
152 | | /* Search the suitable size for the pool. |
153 | | * We'll just do linear search to the size array, as the array size itself |
154 | | * is only a few elements. Binary search I suspect will be less efficient |
155 | | * for this purpose. |
156 | | */ |
157 | 38 | if (initial_size <= pool_sizes[START_SIZE]) { |
158 | 38 | for (idx=START_SIZE-1; |
159 | 152 | idx >= 0 && pool_sizes[idx] >= initial_size; |
160 | 114 | --idx) |
161 | 114 | ; |
162 | 38 | ++idx; |
163 | 38 | } else { |
164 | 0 | for (idx=START_SIZE+1; |
165 | 0 | idx < PJ_CACHING_POOL_ARRAY_SIZE && |
166 | 0 | pool_sizes[idx] < initial_size; |
167 | 0 | ++idx) |
168 | 0 | ; |
169 | 0 | } |
170 | | |
171 | | /* Check whether there's a pool in the list. */ |
172 | 38 | if (idx==PJ_CACHING_POOL_ARRAY_SIZE || pj_list_empty(&cp->free_list[idx])) { |
173 | | /* No pool is available. */ |
174 | | /* Set minimum size. */ |
175 | 38 | if (idx < PJ_CACHING_POOL_ARRAY_SIZE) |
176 | 38 | initial_size = pool_sizes[idx]; |
177 | | |
178 | | /* Create new pool */ |
179 | 38 | pool = pj_pool_create_int(&cp->factory, name, initial_size, |
180 | 38 | increment_sz, alignment, callback); |
181 | 38 | if (!pool) { |
182 | 0 | pj_lock_release(cp->lock); |
183 | 0 | return NULL; |
184 | 0 | } |
185 | | |
186 | 38 | } else { |
187 | | /* Get one pool from the list. */ |
188 | 0 | pool = (pj_pool_t*) cp->free_list[idx].next; |
189 | 0 | pj_list_erase(pool); |
190 | | |
191 | | /* Initialize the pool. */ |
192 | 0 | pj_pool_init_int(pool, name, increment_sz, alignment, callback); |
193 | | |
194 | | /* Update pool manager's free capacity. */ |
195 | 0 | if (cp->capacity > pj_pool_get_capacity(pool)) { |
196 | 0 | cp->capacity -= pj_pool_get_capacity(pool); |
197 | 0 | } else { |
198 | 0 | cp->capacity = 0; |
199 | 0 | } |
200 | |
|
201 | 0 | PJ_LOG(6, (pool->obj_name, "pool reused, size=%lu", |
202 | 0 | (unsigned long)pool->capacity)); |
203 | 0 | } |
204 | | |
205 | | /* Put in used list. */ |
206 | 38 | pj_list_insert_before( &cp->used_list, pool ); |
207 | | |
208 | | /* Mark factory data */ |
209 | 38 | pool->factory_data = (void*) (pj_ssize_t) idx; |
210 | | |
211 | | /* Increment used count. */ |
212 | 38 | ++cp->used_count; |
213 | | |
214 | 38 | pj_lock_release(cp->lock); |
215 | 38 | return pool; |
216 | 38 | } |
217 | | |
218 | | static void cpool_release_pool( pj_pool_factory *pf, pj_pool_t *pool) |
219 | 38 | { |
220 | 38 | pj_caching_pool *cp = (pj_caching_pool*)pf; |
221 | 38 | pj_size_t pool_capacity; |
222 | 38 | unsigned i; |
223 | | |
224 | 38 | PJ_CHECK_STACK(); |
225 | | |
226 | 38 | PJ_ASSERT_ON_FAIL(pf && pool, return); |
227 | | |
228 | 38 | pj_lock_acquire(cp->lock); |
229 | | |
230 | | #if PJ_SAFE_POOL |
231 | | /* Make sure pool is still in our used list */ |
232 | | if (pj_list_find_node(&cp->used_list, pool) != pool) { |
233 | | pj_lock_release(cp->lock); |
234 | | pj_assert(!"Attempt to destroy pool that has been destroyed before"); |
235 | | return; |
236 | | } |
237 | | #endif |
238 | | |
239 | | /* Erase from the used list. */ |
240 | 38 | pj_list_erase(pool); |
241 | | |
242 | | /* Decrement used count. */ |
243 | 38 | --cp->used_count; |
244 | | |
245 | 38 | pool_capacity = pj_pool_get_capacity(pool); |
246 | | |
247 | | /* Destroy the pool if the size is greater than our size or if the total |
248 | | * capacity in our recycle list (plus the size of the pool) exceeds |
249 | | * maximum capacity. |
250 | | . */ |
251 | 38 | if (pool_capacity > pool_sizes[PJ_CACHING_POOL_ARRAY_SIZE-1] || |
252 | 38 | cp->capacity + pool_capacity > cp->max_capacity) |
253 | 38 | { |
254 | 38 | pj_pool_destroy_int(pool); |
255 | 38 | pj_lock_release(cp->lock); |
256 | 38 | return; |
257 | 38 | } |
258 | | |
259 | | /* Reset pool. */ |
260 | 0 | PJ_LOG(6, (pool->obj_name, "recycle(): cap=%lu, used=%lu(%lu%%)", |
261 | 0 | (unsigned long)pool_capacity, |
262 | 0 | (unsigned long)pj_pool_get_used_size(pool), |
263 | 0 | (unsigned long)(pj_pool_get_used_size(pool)*100/ |
264 | 0 | pool_capacity))); |
265 | 0 | pj_pool_reset(pool); |
266 | |
|
267 | 0 | pool_capacity = pj_pool_get_capacity(pool); |
268 | | |
269 | | /* |
270 | | * Otherwise put the pool in our recycle list. |
271 | | */ |
272 | 0 | i = (unsigned) (unsigned long) (pj_ssize_t) pool->factory_data; |
273 | |
|
274 | 0 | pj_assert(i<PJ_CACHING_POOL_ARRAY_SIZE); |
275 | 0 | if (i >= PJ_CACHING_POOL_ARRAY_SIZE ) { |
276 | | /* Something has gone wrong with the pool. */ |
277 | 0 | pj_pool_destroy_int(pool); |
278 | 0 | pj_lock_release(cp->lock); |
279 | 0 | return; |
280 | 0 | } |
281 | | |
282 | 0 | pj_list_insert_after(&cp->free_list[i], pool); |
283 | 0 | cp->capacity += pool_capacity; |
284 | |
|
285 | 0 | pj_lock_release(cp->lock); |
286 | 0 | } |
287 | | |
288 | | static void cpool_dump_status(pj_pool_factory *factory, pj_bool_t detail ) |
289 | 0 | { |
290 | 0 | #if PJ_LOG_MAX_LEVEL >= 3 |
291 | 0 | pj_caching_pool *cp = (pj_caching_pool*)factory; |
292 | |
|
293 | 0 | pj_lock_acquire(cp->lock); |
294 | |
|
295 | 0 | PJ_LOG(3,("cachpool", " Dumping caching pool:")); |
296 | 0 | PJ_LOG(3,("cachpool", " Capacity=%lu, max_capacity=%lu, used_cnt=%lu", |
297 | 0 | (unsigned long)cp->capacity, (unsigned long)cp->max_capacity, |
298 | 0 | (unsigned long)cp->used_count)); |
299 | 0 | if (detail) { |
300 | 0 | pj_pool_t *pool = (pj_pool_t*) cp->used_list.next; |
301 | 0 | pj_size_t total_used = 0, total_capacity = 0; |
302 | 0 | PJ_LOG(3,("cachpool", " Dumping all active pools:")); |
303 | 0 | while (pool != (void*)&cp->used_list) { |
304 | 0 | pj_size_t pool_capacity = pj_pool_get_capacity(pool); |
305 | 0 | pj_pool_block *block = pool->block_list.next; |
306 | 0 | unsigned nblocks = 0; |
307 | |
|
308 | 0 | while (block != &pool->block_list) { |
309 | | #if 0 |
310 | | PJ_LOG(6, ("cachpool", " %16s block %u, size %ld", |
311 | | pj_pool_getobjname(pool), nblocks, |
312 | | (long)(block->end - block->buf + 1))); |
313 | | #endif |
314 | 0 | nblocks++; |
315 | 0 | block = block->next; |
316 | 0 | } |
317 | |
|
318 | 0 | PJ_LOG(3,("cachpool", " %16s: %8lu of %8lu (%lu%%) used, " |
319 | 0 | "nblocks: %d", |
320 | 0 | pj_pool_getobjname(pool), |
321 | 0 | (unsigned long)pj_pool_get_used_size(pool), |
322 | 0 | (unsigned long)pool_capacity, |
323 | 0 | (unsigned long)(pj_pool_get_used_size(pool)* |
324 | 0 | 100/pool_capacity), |
325 | 0 | nblocks)); |
326 | |
|
327 | | #if PJ_POOL_MAX_SEARCH_BLOCK_COUNT == 0 |
328 | | if (nblocks >= 10) { |
329 | | PJ_LOG(3,("cachpool", " %16s has too many blocks (%d), " |
330 | | "consider increasing its initial and/or " |
331 | | "increment size for better performance", |
332 | | pj_pool_getobjname(pool), nblocks)); |
333 | | } |
334 | | #endif |
335 | |
|
336 | 0 | total_used += pj_pool_get_used_size(pool); |
337 | 0 | total_capacity += pool_capacity; |
338 | 0 | pool = pool->next; |
339 | 0 | } |
340 | 0 | if (total_capacity) { |
341 | 0 | PJ_LOG(3,("cachpool", " Total %9lu of %9lu (%lu %%) used!", |
342 | 0 | (unsigned long)total_used, |
343 | 0 | (unsigned long)total_capacity, |
344 | 0 | (unsigned long)(total_used * 100 / |
345 | 0 | total_capacity))); |
346 | 0 | } |
347 | 0 | } |
348 | |
|
349 | 0 | pj_lock_release(cp->lock); |
350 | | #else |
351 | | PJ_UNUSED_ARG(factory); |
352 | | PJ_UNUSED_ARG(detail); |
353 | | #endif |
354 | 0 | } |
355 | | |
356 | | |
357 | | static pj_bool_t cpool_on_block_alloc(pj_pool_factory *f, pj_size_t sz) |
358 | 0 | { |
359 | 0 | pj_caching_pool *cp = (pj_caching_pool*)f; |
360 | | |
361 | | //Can't lock because mutex is not recursive |
362 | | //if (cp->mutex) pj_mutex_lock(cp->mutex); |
363 | |
|
364 | 0 | cp->used_size += sz; |
365 | 0 | if (cp->used_size > cp->peak_used_size) |
366 | 0 | cp->peak_used_size = cp->used_size; |
367 | | |
368 | | //if (cp->mutex) pj_mutex_unlock(cp->mutex); |
369 | |
|
370 | 0 | return PJ_TRUE; |
371 | 0 | } |
372 | | |
373 | | |
374 | | static void cpool_on_block_free(pj_pool_factory *f, pj_size_t sz) |
375 | 0 | { |
376 | 0 | pj_caching_pool *cp = (pj_caching_pool*)f; |
377 | | |
378 | | //pj_mutex_lock(cp->mutex); |
379 | 0 | cp->used_size -= sz; |
380 | | //pj_mutex_unlock(cp->mutex); |
381 | 0 | } |
382 | | |
383 | | |
384 | | #endif /* PJ_HAS_POOL_ALT_API */ |
385 | | |