pool.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. /** BEGIN COPYRIGHT BLOCK
  2. * This Program is free software; you can redistribute it and/or modify it under
  3. * the terms of the GNU General Public License as published by the Free Software
  4. * Foundation; version 2 of the License.
  5. *
  6. * This Program is distributed in the hope that it will be useful, but WITHOUT
  7. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  8. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  9. *
  10. * You should have received a copy of the GNU General Public License along with
  11. * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
  12. * Place, Suite 330, Boston, MA 02111-1307 USA.
  13. *
  14. * In addition, as a special exception, Red Hat, Inc. gives You the additional
  15. * right to link the code of this Program with code not covered under the GNU
  16. * General Public License ("Non-GPL Code") and to distribute linked combinations
  17. * including the two, subject to the limitations in this paragraph. Non-GPL Code
  18. * permitted under this exception must only link to the code of this Program
  19. * through those well defined interfaces identified in the file named EXCEPTION
  20. * found in the source code files (the "Approved Interfaces"). The files of
  21. * Non-GPL Code may instantiate templates or use macros or inline functions from
  22. * the Approved Interfaces without causing the resulting work to be covered by
  23. * the GNU General Public License. Only Red Hat, Inc. may make changes or
  24. * additions to the list of Approved Interfaces. You must obey the GNU General
  25. * Public License in all respects for all of the Program code and other code used
  26. * in conjunction with the Program except the Non-GPL Code covered by this
  27. * exception. If you modify this file, you may extend this exception to your
  28. * version of the file, but you are not obligated to do so. If you do not wish to
  29. * provide this exception without modification, you must delete this exception
  30. * statement from your version and license this file solely under the GPL without
  31. * exception.
  32. *
  33. *
  34. * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
  35. * Copyright (C) 2005 Red Hat, Inc.
  36. * All rights reserved.
  37. * END COPYRIGHT BLOCK **/
  38. #ifdef HAVE_CONFIG_H
  39. # include <config.h>
  40. #endif
  41. /*
  42. * Generic pool handling routines.
  43. *
  44. * Hopefully these reduce the number of malloc/free calls.
  45. *
  46. *
  47. * Thread warning:
  48. * This implementation is thread safe. However, simultaneous
  49. * mallocs/frees to the same "pool" are not safe. If you wish to
  50. * use this module across multiple threads, you should define
  51. * POOL_LOCKING which will make the malloc pools safe.
  52. *
  53. * Mike Belshe
  54. * 11-20-95
  55. *
  56. */
  57. #include "netsite.h"
  58. #include "base/systems.h"
  59. #include "base/systhr.h"
  60. #include <plhash.h>
  61. #ifdef MALLOC_POOLS
  62. #include "base/pool.h"
  63. #include "base/ereport.h"
  64. #include "base/util.h"
  65. #include "base/crit.h"
  66. #include "base/dbtbase.h"
  67. #ifdef DEBUG
  68. #define POOL_ZERO_DEBUG
  69. #endif
  70. #undef POOL_LOCKING
  71. #define BLOCK_SIZE (32 * 1024)
  72. #define MAX_FREELIST_SIZE (BLOCK_SIZE * 32)
  73. /* WORD SIZE 8 sets us up for 8 byte alignment. */
  74. #define WORD_SIZE 8
  75. #undef ALIGN
  76. #define ALIGN(x) ( (x + WORD_SIZE-1) & (~(WORD_SIZE-1)) )
  77. /* block_t
  78. * When the user allocates space, a BLOCK_SIZE (or larger) block is created in
  79. * the pool. This block is used until all the space is eaten within it.
  80. * When all the space is gone, a new block is created.
  81. *
  82. */
  83. typedef struct block_t {
  84. char *data; /* the real alloc'd space */
  85. char *start; /* first free byte in block */
  86. char *end; /* ptr to end of block */
  87. struct block_t *next; /* ptr to next block */
  88. } block_t;
  89. /* pool_t
  90. * A pool is a collection of blocks. The blocks consist of multiple
  91. * allocations of memory, but a single allocation cannot be freed by
  92. * itself. Once the memory is allocated it is allocated until the
  93. * entire pool is freed.
  94. */
  95. typedef struct pool_t {
  96. #ifdef DEBUG_CACHES
  97. time_t time_created;
  98. #endif
  99. #ifdef POOL_LOCKING
  100. CRITICAL lock; /* lock for modifying the pool */
  101. #endif
  102. block_t *curr_block; /* current block being used */
  103. block_t *used_blocks; /* blocks that are all used up */
  104. long size; /* size of memory in pool */
  105. struct pool_t *next; /* known_pools list */
  106. } pool_t;
  107. /* known_pools
  108. * Primarily for debugging, keep a list of all active malloc pools.
  109. */
  110. static pool_t *known_pools = NULL;
  111. static CRITICAL known_pools_lock = NULL;
  112. static unsigned long pool_blocks_created = 0;
  113. static unsigned long pool_blocks_freed = 0;
  114. /* freelist
  115. * Internally we maintain a list of free blocks which we try to pull from
  116. * whenever possible. This list will never have more than MAX_FREELIST_SIZE
  117. * bytes within it.
  118. */
  119. static CRITICAL freelist_lock = NULL;
  120. static block_t *freelist = NULL;
  121. static unsigned long freelist_size = 0;
  122. //static unsigned long freelist_max = MAX_FREELIST_SIZE;
  123. static int pool_disable = 0;
  124. int
  125. pool_internal_init()
  126. {
  127. if (pool_disable == 0) {
  128. if (known_pools_lock == NULL) {
  129. known_pools_lock = crit_init();
  130. freelist_lock = crit_init();
  131. }
  132. }
  133. return 0;
  134. }
  135. static block_t *
  136. _create_block(int size)
  137. {
  138. block_t *newblock = NULL;
  139. long bytes = ALIGN(size);
  140. block_t *free_ptr,
  141. *last_free_ptr = NULL;
  142. /* check freelist for large enough block first */
  143. crit_enter(freelist_lock);
  144. free_ptr = freelist;
  145. while(free_ptr && ((free_ptr->end - free_ptr->data) < bytes)) {
  146. last_free_ptr = free_ptr;
  147. free_ptr = free_ptr->next;
  148. }
  149. if (free_ptr) {
  150. newblock = free_ptr;
  151. if (last_free_ptr)
  152. last_free_ptr->next = free_ptr->next;
  153. else
  154. freelist = free_ptr->next;
  155. freelist_size -= (newblock->end - newblock->data);
  156. crit_exit(freelist_lock);
  157. bytes = free_ptr->end - free_ptr->data;
  158. }
  159. else {
  160. pool_blocks_created++;
  161. crit_exit(freelist_lock);
  162. if (((newblock = (block_t *)PERM_MALLOC(sizeof(block_t))) == NULL) ||
  163. ((newblock->data = (char *)PERM_MALLOC(bytes)) == NULL)) {
  164. ereport(LOG_CATASTROPHE, const_cast<char *>("%s"), XP_GetAdminStr(DBT_poolCreateBlockOutOfMemory_));
  165. if (newblock)
  166. PERM_FREE(newblock);
  167. return NULL;
  168. }
  169. }
  170. newblock->start = newblock->data;
  171. newblock->end = newblock->data + bytes;
  172. newblock->next = NULL;
  173. return newblock;
  174. }
  175. /* Caller must hold lock for the pool */
  176. static void
  177. _free_block(block_t *block)
  178. {
  179. #ifdef POOL_ZERO_DEBUG
  180. memset(block->data, 0xa, block->end-block->data);
  181. #endif /* POOL_ZERO_DEBUG */
  182. /* Just have to delete the whole block! */
  183. crit_enter(freelist_lock);
  184. pool_blocks_freed++;
  185. crit_exit(freelist_lock);
  186. PERM_FREE(block->data);
  187. #ifdef POOL_ZERO_DEBUG
  188. memset(block, 0xa, sizeof(block_t));
  189. #endif /* POOL_ZERO_DEBUG */
  190. PERM_FREE(block);
  191. return;
  192. }
  193. /* ptr_in_pool()
  194. * Checks to see if the given pointer is in the given pool.
  195. * If true, returns a ptr to the block_t containing the ptr;
  196. * otherwise returns NULL
  197. */
  198. block_t *
  199. _ptr_in_pool(pool_t *pool, void *ptr)
  200. {
  201. block_t *block_ptr = NULL;
  202. /* try to find a block which contains this ptr */
  203. if ( ((char *)ptr < (char *)pool->curr_block->end) &&
  204. ((char *)ptr >= (char *)pool->curr_block->data) )
  205. block_ptr = pool->curr_block;
  206. else
  207. for( block_ptr = pool->used_blocks;
  208. block_ptr &&
  209. (((char *)ptr >= (char *)block_ptr->end) &&
  210. ((char *)ptr < (char *)block_ptr->data));
  211. block_ptr = block_ptr->next);
  212. return block_ptr;
  213. }
  214. NSAPI_PUBLIC pool_handle_t *
  215. pool_create()
  216. {
  217. pool_t *newpool;
  218. if (pool_disable)
  219. return NULL;
  220. newpool = (pool_t *)PERM_MALLOC(sizeof(pool_t));
  221. if (newpool) {
  222. /* Have to initialize now, as pools get created sometimes
  223. * before pool_init can be called...
  224. */
  225. if (known_pools_lock == NULL) {
  226. known_pools_lock = crit_init();
  227. freelist_lock = crit_init();
  228. }
  229. if ( (newpool->curr_block =_create_block(BLOCK_SIZE)) == NULL) {
  230. ereport(LOG_CATASTROPHE, const_cast<char *>("%s"), XP_GetAdminStr(DBT_poolCreateOutOfMemory_));
  231. PERM_FREE(newpool);
  232. return NULL;
  233. }
  234. newpool->used_blocks = NULL;
  235. newpool->size = 0;
  236. newpool->next = NULL;
  237. #ifdef POOL_LOCKING
  238. newpool->lock = crit_init();
  239. #endif
  240. #ifdef DEBUG_CACHES
  241. newpool->time_created = time(NULL);
  242. #endif
  243. /* Add to known pools list */
  244. crit_enter(known_pools_lock);
  245. newpool->next = known_pools;
  246. known_pools = newpool;
  247. crit_exit(known_pools_lock);
  248. }
  249. else
  250. ereport(LOG_CATASTROPHE, const_cast<char *>("%s"), XP_GetAdminStr(DBT_poolCreateOutOfMemory_1));
  251. return (pool_handle_t *)newpool;
  252. }
  253. NSAPI_PUBLIC void
  254. pool_destroy(pool_handle_t *pool_handle)
  255. {
  256. pool_t *pool = (pool_t *)pool_handle;
  257. block_t *tmp_blk;
  258. pool_t *last, *search;
  259. if (pool_disable)
  260. return;
  261. crit_enter(known_pools_lock);
  262. #ifdef POOL_LOCKING
  263. crit_enter(pool->lock);
  264. #endif
  265. if (pool->curr_block){
  266. _free_block(pool->curr_block);
  267. }
  268. while(pool->used_blocks) {
  269. tmp_blk = pool->used_blocks;
  270. pool->used_blocks = pool->used_blocks->next;
  271. _free_block(tmp_blk);
  272. }
  273. /* Remove from the known pools list */
  274. for (last = NULL, search = known_pools; search;
  275. last = search, search = search->next)
  276. if (search == pool)
  277. break;
  278. if (search) {
  279. if(last)
  280. last->next = search->next;
  281. else
  282. known_pools = search->next;
  283. }
  284. #ifdef POOL_LOCKING
  285. crit_exit(pool->lock);
  286. crit_terminate(pool->lock);
  287. #endif
  288. crit_exit(known_pools_lock);
  289. #ifdef POOL_ZERO_DEBUG
  290. memset(pool, 0xa, sizeof(pool_t));
  291. #endif /* POOL_ZERO_DEBUG */
  292. PERM_FREE(pool);
  293. return;
  294. }
  295. NSAPI_PUBLIC void
  296. pool_terminate(void)
  297. {
  298. crit_terminate(known_pools_lock);
  299. crit_terminate(freelist_lock);
  300. known_pools_lock = NULL;
  301. freelist_lock = NULL;
  302. }
  303. NSAPI_PUBLIC void *
  304. pool_malloc(pool_handle_t *pool_handle, size_t size)
  305. {
  306. pool_t *pool = (pool_t *)pool_handle;
  307. long reqsize, blocksize;
  308. char *ptr;
  309. if (pool == NULL || pool_disable) {
  310. return PERM_MALLOC(size);
  311. }
  312. #ifdef DEBUG
  313. if (size == 0)
  314. return NULL;
  315. #endif
  316. #ifdef POOL_LOCKING
  317. crit_enter(pool->lock);
  318. #endif
  319. reqsize = ALIGN(size);
  320. ptr = pool->curr_block->start;
  321. pool->curr_block->start += reqsize;
  322. /* does this fit into the last allocated block? */
  323. if (pool->curr_block->start > pool->curr_block->end) {
  324. /* Did not fit; time to allocate a new block */
  325. pool->curr_block->start -= reqsize; /* keep structs in tact */
  326. pool->curr_block->next = pool->used_blocks;
  327. pool->used_blocks = pool->curr_block;
  328. /* Allocate a chunk of memory which is a multiple of BLOCK_SIZE
  329. * bytes
  330. */
  331. blocksize = ( (size + BLOCK_SIZE-1) / BLOCK_SIZE ) * BLOCK_SIZE;
  332. if ( (pool->curr_block = _create_block(blocksize)) == NULL) {
  333. ereport(LOG_CATASTROPHE, const_cast<char *>("%s"), XP_GetAdminStr(DBT_poolMallocOutOfMemory_));
  334. #ifdef POOL_LOCKING
  335. crit_exit(pool->lock);
  336. #endif
  337. return NULL;
  338. }
  339. ptr = pool->curr_block->start;
  340. reqsize = ALIGN(size);
  341. pool->curr_block->start += reqsize;
  342. }
  343. pool->size += reqsize;
  344. #ifdef POOL_LOCKING
  345. crit_exit(pool->lock);
  346. #endif
  347. return ptr;
  348. }
  349. void _pool_free_error()
  350. {
  351. ereport(LOG_WARN, const_cast<char *>("%s"), XP_GetAdminStr(DBT_freeUsedWherePermFreeShouldHaveB_));
  352. return;
  353. }
  354. NSAPI_PUBLIC void
  355. pool_free(pool_handle_t *pool_handle, void *ptr)
  356. {
  357. if (pool_handle == NULL || pool_disable) {
  358. PERM_FREE(ptr);
  359. return;
  360. }
  361. #ifdef DEBUG
  362. /* Just to be nice, check to see if the ptr was allocated in a pool.
  363. * If not, issue a warning and do a REAL free just to make sure that
  364. * we don't leak memory.
  365. */
  366. if ( !_ptr_in_pool((pool_t *)pool_handle, ptr) ) {
  367. _pool_free_error();
  368. PERM_FREE(ptr);
  369. }
  370. #endif
  371. return;
  372. }
  373. NSAPI_PUBLIC void *
  374. pool_calloc(pool_handle_t *pool_handle, size_t nelem, size_t elsize)
  375. {
  376. void *ptr;
  377. if (pool_handle == NULL || pool_disable)
  378. return PERM_CALLOC(elsize * nelem);
  379. ptr = pool_malloc(pool_handle, elsize * nelem);
  380. if (ptr)
  381. memset(ptr, 0, elsize * nelem);
  382. return ptr;
  383. }
  384. NSAPI_PUBLIC void *
  385. pool_realloc(pool_handle_t *pool_handle, void *ptr, size_t size)
  386. {
  387. pool_t *pool = (pool_t *)pool_handle;
  388. void *newptr;
  389. block_t *block_ptr;
  390. size_t oldsize;
  391. if (pool_handle == NULL || pool_disable)
  392. return PERM_REALLOC(ptr, size);
  393. if ( (newptr = pool_malloc(pool_handle, size)) == NULL)
  394. return NULL;
  395. /* With our structure we don't know exactly where the end
  396. * of the original block is. But we do know an upper bound
  397. * which is a valid ptr. Search the outstanding blocks
  398. * for the block which contains this ptr, and copy...
  399. */
  400. #ifdef POOL_LOCKING
  401. crit_enter(pool->lock);
  402. #endif
  403. if ( !(block_ptr = _ptr_in_pool(pool, ptr)) ) {
  404. /* User is trying to realloc nonmalloc'd space! */
  405. return newptr;
  406. }
  407. oldsize = block_ptr->end - (char *)ptr ;
  408. if (oldsize > size)
  409. oldsize = size;
  410. memmove((char *)newptr, (char *)ptr, oldsize);
  411. #ifdef POOL_LOCKING
  412. crit_exit(pool->lock);
  413. #endif
  414. return newptr;
  415. }
  416. NSAPI_PUBLIC char *
  417. pool_strdup(pool_handle_t *pool_handle, const char *orig_str)
  418. {
  419. char *new_str;
  420. int len = strlen(orig_str);
  421. if (pool_handle == NULL || pool_disable)
  422. return PERM_STRDUP(orig_str);
  423. new_str = (char *)pool_malloc(pool_handle, len+1);
  424. if (new_str)
  425. memcpy(new_str, orig_str, len+1);
  426. return new_str;
  427. }
  428. NSAPI_PUBLIC long
  429. pool_space(pool_handle_t *pool_handle)
  430. {
  431. pool_t *pool = (pool_t *)pool_handle;
  432. return pool->size;
  433. }
  434. NSAPI_PUBLIC int pool_enabled()
  435. {
  436. #ifndef THREAD_ANY
  437. /* we don't have USE_NSPR defined so systhread_getdata is undef'ed */
  438. return 0;
  439. #else
  440. if (pool_disable || (getThreadMallocKey() == -1) )
  441. return 0;
  442. if (!systhread_getdata(getThreadMallocKey()))
  443. return 0;
  444. return 1;
  445. #endif
  446. }
  447. /* pool_service_debug()
  448. * NSAPI service routine to print state information about the existing
  449. * pools. Hopefully useful in debugging.
  450. *
  451. */
  452. #define MAX_DEBUG_LINE 1024
  453. #ifdef DEBUG_CACHES /* XXXrobm causes entanglement in install and admserv cgis */
  454. NSAPI_PUBLIC int
  455. pool_service_debug(pblock *pb, Session *sn, Request *rq)
  456. {
  457. char tmp_buf[MAX_DEBUG_LINE];
  458. char cbuf[DEF_CTIMEBUF];
  459. int len;
  460. pool_t *pool_ptr;
  461. block_t *block_ptr;
  462. int pool_cnt, block_cnt;
  463. param_free(pblock_remove("content-type", rq->srvhdrs));
  464. pblock_nvinsert("content-type", "text/html", rq->srvhdrs);
  465. protocol_status(sn, rq, PROTOCOL_OK, NULL);
  466. protocol_start_response(sn, rq);
  467. len = util_sprintf(tmp_buf, "<H2>Memory pool status report</H2>\n");
  468. net_write(sn->csd, tmp_buf, len);
  469. len = util_sprintf(tmp_buf, "Note: The 0 block in each pool is \
  470. the currently used block <P>\n");
  471. net_write(sn->csd, tmp_buf, len);
  472. len = util_sprintf(tmp_buf, "Freelist size: %d/%d<P>", freelist_size,
  473. freelist_max);
  474. net_write(sn->csd, tmp_buf, len);
  475. len = util_sprintf(tmp_buf, "Pool disabled: %d<P>", pool_disable);
  476. net_write(sn->csd, tmp_buf, len);
  477. len = util_sprintf(tmp_buf, "Blocks created: %d<P> Blocks freed: %d",
  478. pool_blocks_created, pool_blocks_freed);
  479. net_write(sn->csd, tmp_buf, len);
  480. /* Create an HTML table */
  481. len = util_sprintf(tmp_buf, "<UL><TABLE BORDER=4>\n");
  482. net_write(sn->csd, tmp_buf, len);
  483. len = util_sprintf(tmp_buf, "<TH>Pool #</TH>\n");
  484. net_write(sn->csd, tmp_buf, len);
  485. len = util_sprintf(tmp_buf, "<TH>Pool size #</TH>\n");
  486. net_write(sn->csd, tmp_buf, len);
  487. #ifdef DEBUG_CACHES
  488. len = util_sprintf(tmp_buf, "<TH>Time Created</TH>\n");
  489. net_write(sn->csd, tmp_buf, len);
  490. #endif
  491. len = util_sprintf(tmp_buf, "<TH>Blocks</TH>\n");
  492. net_write(sn->csd, tmp_buf, len);
  493. crit_enter(known_pools_lock);
  494. for (pool_cnt = 0, pool_ptr = known_pools; pool_ptr;
  495. pool_ptr = pool_ptr->next, pool_cnt++) {
  496. #ifdef POOL_LOCKING
  497. crit_enter(pool_ptr->lock);
  498. #endif
  499. len = util_snprintf(tmp_buf, MAX_DEBUG_LINE,
  500. #ifndef DEBUG_CACHES
  501. "<tr align=right> <td>%d</td> <td>%d</td> <td> <TABLE BORDER=2> <TH>Block #</TH><TH>data</TH><TH>curr size</TH> <TH>max size</TH>\n",
  502. #else
  503. "<tr align=right> <td>%d</td> <td>%d</td> <td>%s</td> <td> <TABLE BORDER=2> <TH>Block #</TH><TH>data</TH><TH>curr size</TH> <TH>max size</TH>\n",
  504. #endif
  505. pool_cnt, pool_space((pool_handle_t *)pool_ptr)
  506. #ifdef DEBUG_CACHES
  507. , util_ctime(&(pool_ptr->time_created), cbuf, DEF_CTIMEBUF));
  508. #else
  509. );
  510. #endif
  511. net_write(sn->csd, tmp_buf, len);
  512. /* Print the first block */
  513. len = util_snprintf(tmp_buf, MAX_DEBUG_LINE, "\
  514. <tr align=right> \
  515. <td>%d</td> \
  516. <td>%d</td> \
  517. <td>%d</td> \
  518. <td>%d</td> \
  519. </tr>\n",
  520. 0, pool_ptr->curr_block->data,
  521. pool_ptr->curr_block->start -pool_ptr->curr_block->data,
  522. pool_ptr->curr_block->end - pool_ptr->curr_block->data);
  523. net_write(sn->csd, tmp_buf, len);
  524. for (block_cnt = 1, block_ptr = pool_ptr->used_blocks; block_ptr;
  525. block_ptr = block_ptr->next, block_cnt++) {
  526. len = util_snprintf(tmp_buf, MAX_DEBUG_LINE, "\
  527. <tr align=right> \
  528. <td>%d</td> \
  529. <td>%d</td> \
  530. <td>%d</td> \
  531. <td>%d</td> \
  532. </tr>\n",
  533. block_cnt, block_ptr->data,
  534. block_ptr->start - block_ptr->data,
  535. block_ptr->end - block_ptr->data);
  536. net_write(sn->csd, tmp_buf, len);
  537. }
  538. #ifdef POOL_LOCKING
  539. crit_exit(pool_ptr->lock);
  540. #endif
  541. len = util_snprintf(tmp_buf, MAX_DEBUG_LINE, "</TABLE></TD></TR>");
  542. net_write(sn->csd, tmp_buf, len);
  543. }
  544. crit_exit(known_pools_lock);
  545. len = util_sprintf(tmp_buf, "</TABLE></UL>\n");
  546. net_write(sn->csd, tmp_buf, len);
  547. return REQ_PROCEED;
  548. }
  549. #endif /* 0 */
  550. #endif /* MALLOC_POOLS */