summaryrefslogtreecommitdiffhomepage
path: root/src/nxt_mp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nxt_mp.c')
-rw-r--r--src/nxt_mp.c890
1 files changed, 890 insertions, 0 deletions
diff --git a/src/nxt_mp.c b/src/nxt_mp.c
new file mode 100644
index 00000000..aa6776b5
--- /dev/null
+++ b/src/nxt_mp.c
@@ -0,0 +1,890 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_main.h>
+
+
+/*
+ * A memory pool allocates memory in clusters of specified size and aligned
+ * to page_alignment. A cluster is divided on pages of specified size. Page
+ * size must be a power of 2. A page can be used entirely or can be divided
+ * on chunks of equal size. Chunk size must be a power of 2. Non-freeable
+ * memory is also allocated from pages. A cluster can contains a mix of pages
+ * with different chunk sizes and non-freeable pages. Cluster size must be
+ * a multiple of page size and may be not a power of 2. Allocations greater
+ * than page are allocated outside clusters. Start addresses and sizes of
+ * the clusters and large allocations are stored in rbtree blocks to find
+ * them on free operations. The rbtree nodes are sorted by start addresses.
+ * The rbtree is also used to destroy memory pool.
+ */
+
+
+typedef struct {
+ /*
+ * Used to link
+ * *) pages with free chunks in pool chunk pages lists,
+ * *) pages with free space for non-freeable allocations,
+ * *) free pages in clusters.
+ */
+ nxt_queue_link_t link;
+
+ union {
+ /* Chunk bitmap. There can be no more than 32 chunks in a page. */
+ uint32_t map;
+
+ /* Size of taken non-freeable space. */
+ uint32_t taken;
+ } u;
+
+ /*
+ * Size of chunks or page shifted by pool->chunk_size_shift. Zero means
+ * that page is free, 0xFF means page with non-freeable allocations.
+ */
+ uint8_t size;
+
+ /* Number of free chunks of a chunked page. */
+ uint8_t chunks;
+
+ /*
+ * Number of allocation fails due to free space insufficiency
+ * in non-freeable page.
+ */
+ uint8_t fails;
+
+ /*
+ * Page number in page cluster.
+ * There can be no more than 256 pages in a cluster.
+ */
+ uint8_t number;
+} nxt_mp_page_t;
+
+
+/*
+ * Some malloc implementations (e.g. jemalloc) allocates large enough
+ * blocks (e.g. greater than 4K) with 4K alignment. So if a block
+ * descriptor will be allocated together with the block it will take
+ * excessive 4K memory. So it is better to allocate the block descriptor
+ * apart.
+ */
+
+typedef enum {
+ /* Block of cluster. The block is allocated apart of the cluster. */
+ NXT_MP_CLUSTER_BLOCK = 0,
+ /*
+ * Block of large allocation.
+ * The block is allocated apart of the allocation.
+ */
+ NXT_MP_DISCRETE_BLOCK,
+ /*
+ * Block of large allocation.
+ * The block is allocated just after of the allocation.
+ */
+ NXT_MP_EMBEDDED_BLOCK,
+} nxt_mp_block_type_t;
+
+
+typedef struct {
+ NXT_RBTREE_NODE (node);
+ nxt_mp_block_type_t type:8;
+
+ /* Block size must be less than 4G. */
+ uint32_t size;
+
+ u_char *start;
+ nxt_mp_page_t pages[];
+} nxt_mp_block_t;
+
+
+struct nxt_mp_s {
+ /* rbtree of nxt_mp_block_t. */
+ nxt_rbtree_t blocks;
+
+ uint8_t chunk_size_shift;
+ uint8_t page_size_shift;
+ uint32_t page_size;
+ uint32_t page_alignment;
+ uint32_t cluster_size;
+ uint32_t retain;
+
+ /* Lists of nxt_mp_page_t. */
+ nxt_queue_t free_pages;
+ nxt_queue_t nget_pages;
+ nxt_queue_t get_pages;
+ nxt_queue_t chunk_pages[0];
+};
+
+
+#define nxt_mp_chunk_get_free(map) \
+ (__builtin_ffs(map) - 1)
+
+
+#define nxt_mp_chunk_is_free(map, chunk) \
+ ((map & (1 << chunk)) != 0)
+
+
+#define nxt_mp_chunk_set_busy(map, chunk) \
+ map &= ~(1 << chunk)
+
+
+#define nxt_mp_chunk_set_free(map, chunk) \
+ map |= (1 << chunk)
+
+
+#define nxt_mp_free_junk(p, size) \
+ memset((p), 0x5A, size)
+
+
+#if !(NXT_DEBUG_MEMORY)
+static void *nxt_mp_get_small(nxt_mp_t *mp, nxt_queue_t *pages, size_t size);
+static void *nxt_mp_alloc_small(nxt_mp_t *mp, size_t size);
+static nxt_mp_page_t *nxt_mp_alloc_page(nxt_mp_t *mp);
+static nxt_mp_block_t *nxt_mp_alloc_cluster(nxt_mp_t *mp);
+#endif
+static void *nxt_mp_alloc_large(nxt_mp_t *mp, size_t alignment, size_t size);
+static intptr_t nxt_mp_rbtree_compare(nxt_rbtree_node_t *node1,
+ nxt_rbtree_node_t *node2);
+static nxt_mp_block_t *nxt_mp_find_block(nxt_rbtree_t *tree, u_char *p);
+static const char *nxt_mp_chunk_free(nxt_mp_t *mp, nxt_mp_block_t *cluster,
+ u_char *p);
+
+
+nxt_mp_t *
+nxt_mp_create(size_t cluster_size, size_t page_alignment, size_t page_size,
+ size_t min_chunk_size)
+{
+ nxt_mp_t *mp;
+ nxt_uint_t pages, chunk_size;
+ nxt_queue_t *chunk_pages;
+
+ pages = 0;
+ chunk_size = page_size;
+
+ do {
+ pages++;
+ chunk_size /= 2;
+ } while (chunk_size > min_chunk_size);
+
+ mp = nxt_zalloc(sizeof(nxt_mp_t) + pages * sizeof(nxt_queue_t));
+
+ if (nxt_fast_path(mp != NULL)) {
+ mp->retain = 1;
+ mp->page_size = page_size;
+ mp->page_alignment = nxt_max(page_alignment, NXT_MAX_ALIGNMENT);
+ mp->cluster_size = cluster_size;
+
+ chunk_pages = mp->chunk_pages;
+
+ do {
+ nxt_queue_init(chunk_pages);
+ chunk_pages++;
+ chunk_size *= 2;
+ } while (chunk_size < page_size);
+
+ mp->chunk_size_shift = nxt_lg2(min_chunk_size);
+ mp->page_size_shift = nxt_lg2(page_size);
+
+ nxt_rbtree_init(&mp->blocks, nxt_mp_rbtree_compare);
+
+ nxt_queue_init(&mp->free_pages);
+ }
+
+ return mp;
+}
+
+
+void
+nxt_mp_destroy(nxt_mp_t *mp)
+{
+ void *p;
+ nxt_mp_block_t *block;
+ nxt_rbtree_node_t *node, *next;
+
+ nxt_debug_alloc("mp destroy");
+
+ next = nxt_rbtree_root(&mp->blocks);
+
+ while (next != nxt_rbtree_sentinel(&mp->blocks)) {
+
+ node = nxt_rbtree_destroy_next(&mp->blocks, &next);
+ block = (nxt_mp_block_t *) node;
+
+ p = block->start;
+
+ if (block->type != NXT_MP_EMBEDDED_BLOCK) {
+ nxt_free(block);
+ }
+
+ nxt_free(p);
+ }
+
+ nxt_free(mp);
+}
+
+
+nxt_bool_t
+nxt_mp_test_sizes(size_t cluster_size, size_t page_alignment, size_t page_size,
+ size_t min_chunk_size)
+{
+ nxt_bool_t valid;
+
+ /* Alignment and sizes must be a power of 2. */
+
+ valid = nxt_expect(1, (nxt_is_power_of_two(page_alignment)
+ && nxt_is_power_of_two(page_size)
+ && nxt_is_power_of_two(min_chunk_size)));
+ if (!valid) {
+ return 0;
+ }
+
+ page_alignment = nxt_max(page_alignment, NXT_MAX_ALIGNMENT);
+
+ valid = nxt_expect(1, (page_size >= 64
+ && page_size >= page_alignment
+ && page_size >= min_chunk_size
+ && min_chunk_size * 32 >= page_size
+ && cluster_size >= page_size
+ && cluster_size / page_size <= 256
+ && cluster_size % page_size == 0));
+ if (!valid) {
+ return 0;
+ }
+
+ return 1;
+}
+
+
+nxt_bool_t
+nxt_mp_is_empty(nxt_mp_t *mp)
+{
+ return (nxt_rbtree_is_empty(&mp->blocks)
+ && nxt_queue_is_empty(&mp->free_pages));
+}
+
+
+void *
+nxt_mp_alloc(nxt_mp_t *mp, size_t size)
+{
+ nxt_debug_alloc("mp alloc: %uz", size);
+
+#if !(NXT_DEBUG_MEMORY)
+
+ if (size <= mp->page_size) {
+ return nxt_mp_alloc_small(mp, size);
+ }
+
+#endif
+
+ return nxt_mp_alloc_large(mp, NXT_MAX_ALIGNMENT, size);
+}
+
+
+void *
+nxt_mp_zalloc(nxt_mp_t *mp, size_t size)
+{
+ void *p;
+
+ p = nxt_mp_alloc(mp, size);
+
+ if (nxt_fast_path(p != NULL)) {
+ memset(p, 0, size);
+ }
+
+ return p;
+}
+
+
+void *
+nxt_mp_align(nxt_mp_t *mp, size_t alignment, size_t size)
+{
+ nxt_debug_alloc("mp align: @%uz:%uz", alignment, size);
+
+ /* Alignment must be a power of 2. */
+
+ if (nxt_fast_path(nxt_is_power_of_two(alignment))) {
+
+#if !(NXT_DEBUG_MEMORY)
+
+ if (size <= mp->page_size && alignment <= mp->page_alignment) {
+ size = nxt_max(size, alignment);
+
+ if (size <= mp->page_size) {
+ return nxt_mp_alloc_small(mp, size);
+ }
+ }
+
+#endif
+
+ return nxt_mp_alloc_large(mp, alignment, size);
+ }
+
+ return NULL;
+}
+
+
+void *
+nxt_mp_zalign(nxt_mp_t *mp, size_t alignment, size_t size)
+{
+ void *p;
+
+ p = nxt_mp_align(mp, alignment, size);
+
+ if (nxt_fast_path(p != NULL)) {
+ memset(p, 0, size);
+ }
+
+ return p;
+}
+
+
+#if !(NXT_DEBUG_MEMORY)
+
+nxt_inline u_char *
+nxt_mp_page_addr(nxt_mp_t *mp, nxt_mp_page_t *page)
+{
+ size_t page_offset;
+ nxt_mp_block_t *block;
+
+ page_offset = page->number * sizeof(nxt_mp_page_t)
+ + offsetof(nxt_mp_block_t, pages);
+
+ block = (nxt_mp_block_t *) ((u_char *) page - page_offset);
+
+ return block->start + (page->number << mp->page_size_shift);
+}
+
+
+nxt_inline nxt_uint_t
+nxt_mp_chunk_pages_index(nxt_mp_t *mp, size_t size)
+{
+ nxt_int_t n, index;
+
+ index = 0;
+
+ if (size > 1) {
+ n = nxt_lg2(size - 1) + 1 - mp->chunk_size_shift;
+
+ if (n > 0) {
+ index = n;
+ }
+ }
+
+ return index;
+}
+
+
+static void *
+nxt_mp_alloc_small(nxt_mp_t *mp, size_t size)
+{
+ u_char *p;
+ nxt_uint_t n, index;
+ nxt_queue_t *chunk_pages;
+ nxt_mp_page_t *page;
+ nxt_queue_link_t *link;
+
+ p = NULL;
+
+ if (size <= mp->page_size / 2) {
+
+ index = nxt_mp_chunk_pages_index(mp, size);
+ chunk_pages = &mp->chunk_pages[index];
+
+ if (nxt_fast_path(!nxt_queue_is_empty(chunk_pages))) {
+
+ link = nxt_queue_first(chunk_pages);
+ page = nxt_queue_link_data(link, nxt_mp_page_t, link);
+
+ p = nxt_mp_page_addr(mp, page);
+
+ n = nxt_mp_chunk_get_free(page->u.map);
+ nxt_mp_chunk_set_busy(page->u.map, n);
+
+ p += ((n << index) << mp->chunk_size_shift);
+
+ page->chunks--;
+
+ if (page->chunks == 0) {
+ /*
+ * Remove full page from the pool chunk pages list
+ * of pages with free chunks.
+ */
+ nxt_queue_remove(&page->link);
+ }
+
+ } else {
+ page = nxt_mp_alloc_page(mp);
+
+ if (nxt_fast_path(page != NULL)) {
+ page->size = (1 << index);
+
+ n = mp->page_size_shift - (index + mp->chunk_size_shift);
+ page->chunks = (1 << n) - 1;
+
+ nxt_queue_insert_head(chunk_pages, &page->link);
+
+ /* Mark the first chunk as busy. */
+ page->u.map = 0xFFFFFFFE;
+
+ p = nxt_mp_page_addr(mp, page);
+ }
+ }
+
+ } else {
+ page = nxt_mp_alloc_page(mp);
+
+ if (nxt_fast_path(page != NULL)) {
+ page->size = mp->page_size >> mp->chunk_size_shift;
+
+ p = nxt_mp_page_addr(mp, page);
+ }
+ }
+
+ nxt_debug_alloc("mp chunk:%uz alloc: %p",
+ page->size << mp->chunk_size_shift, p);
+
+ return p;
+}
+
+
+static nxt_mp_page_t *
+nxt_mp_alloc_page(nxt_mp_t *mp)
+{
+ nxt_mp_page_t *page;
+ nxt_mp_block_t *cluster;
+ nxt_queue_link_t *link;
+
+ if (nxt_queue_is_empty(&mp->free_pages)) {
+ cluster = nxt_mp_alloc_cluster(mp);
+ if (nxt_slow_path(cluster == NULL)) {
+ return NULL;
+ }
+ }
+
+ link = nxt_queue_first(&mp->free_pages);
+ nxt_queue_remove(link);
+
+ page = nxt_queue_link_data(link, nxt_mp_page_t, link);
+
+ return page;
+}
+
+
+static nxt_mp_block_t *
+nxt_mp_alloc_cluster(nxt_mp_t *mp)
+{
+ nxt_uint_t n;
+ nxt_mp_block_t *cluster;
+
+ n = mp->cluster_size >> mp->page_size_shift;
+
+ cluster = nxt_zalloc(sizeof(nxt_mp_block_t) + n * sizeof(nxt_mp_page_t));
+
+ if (nxt_slow_path(cluster == NULL)) {
+ return NULL;
+ }
+
+ /* NXT_MP_CLUSTER_BLOCK type is zero. */
+
+ cluster->size = mp->cluster_size;
+
+ cluster->start = nxt_memalign(mp->page_alignment, mp->cluster_size);
+ if (nxt_slow_path(cluster->start == NULL)) {
+ nxt_free(cluster);
+ return NULL;
+ }
+
+ n--;
+ cluster->pages[n].number = n;
+ nxt_queue_insert_head(&mp->free_pages, &cluster->pages[n].link);
+
+ while (n != 0) {
+ n--;
+ cluster->pages[n].number = n;
+ nxt_queue_insert_before(&cluster->pages[n + 1].link,
+ &cluster->pages[n].link);
+ }
+
+ nxt_rbtree_insert(&mp->blocks, &cluster->node);
+
+ return cluster;
+}
+
+#endif
+
+
+static void *
+nxt_mp_alloc_large(nxt_mp_t *mp, size_t alignment, size_t size)
+{
+ u_char *p;
+ size_t aligned_size;
+ uint8_t type;
+ nxt_mp_block_t *block;
+
+ /* Allocation must be less than 4G. */
+ if (nxt_slow_path(size >= 0xFFFFFFFF)) {
+ return NULL;
+ }
+
+ if (nxt_is_power_of_two(size)) {
+ block = nxt_malloc(sizeof(nxt_mp_block_t));
+ if (nxt_slow_path(block == NULL)) {
+ return NULL;
+ }
+
+ p = nxt_memalign(alignment, size);
+ if (nxt_slow_path(p == NULL)) {
+ nxt_free(block);
+ return NULL;
+ }
+
+ type = NXT_MP_DISCRETE_BLOCK;
+
+ } else {
+ aligned_size = nxt_align_size(size, sizeof(uintptr_t));
+
+ p = nxt_memalign(alignment, aligned_size + sizeof(nxt_mp_block_t));
+ if (nxt_slow_path(p == NULL)) {
+ return NULL;
+ }
+
+ block = (nxt_mp_block_t *) (p + aligned_size);
+ type = NXT_MP_EMBEDDED_BLOCK;
+ }
+
+ block->type = type;
+ block->size = size;
+ block->start = p;
+
+ nxt_rbtree_insert(&mp->blocks, &block->node);
+
+ return p;
+}
+
+
+static intptr_t
+nxt_mp_rbtree_compare(nxt_rbtree_node_t *node1, nxt_rbtree_node_t *node2)
+{
+ nxt_mp_block_t *block1, *block2;
+
+ block1 = (nxt_mp_block_t *) node1;
+ block2 = (nxt_mp_block_t *) node2;
+
+ return (uintptr_t) block1->start - (uintptr_t) block2->start;
+}
+
+
+void
+nxt_mp_free(nxt_mp_t *mp, void *p)
+{
+ const char *err;
+ nxt_thread_t *thread;
+ nxt_mp_block_t *block;
+
+ nxt_debug_alloc("mp free %p", p);
+
+ block = nxt_mp_find_block(&mp->blocks, p);
+
+ if (nxt_fast_path(block != NULL)) {
+
+ if (block->type == NXT_MP_CLUSTER_BLOCK) {
+ err = nxt_mp_chunk_free(mp, block, p);
+
+ if (nxt_fast_path(err == NULL)) {
+ return;
+ }
+
+ } else if (nxt_fast_path(p == block->start)) {
+ nxt_rbtree_delete(&mp->blocks, &block->node);
+
+ if (block->type == NXT_MP_DISCRETE_BLOCK) {
+ nxt_free(block);
+ }
+
+ nxt_free(p);
+
+ return;
+
+ } else {
+ err = "freed pointer points to middle of block: %p";
+ }
+
+ } else {
+ err = "freed pointer is out of pool: %p";
+ }
+
+ thread = nxt_thread();
+
+ nxt_log(thread->task, NXT_LOG_CRIT, err, p);
+}
+
+
+static nxt_mp_block_t *
+nxt_mp_find_block(nxt_rbtree_t *tree, u_char *p)
+{
+ nxt_mp_block_t *block;
+ nxt_rbtree_node_t *node, *sentinel;
+
+ node = nxt_rbtree_root(tree);
+ sentinel = nxt_rbtree_sentinel(tree);
+
+ while (node != sentinel) {
+
+ block = (nxt_mp_block_t *) node;
+
+ if (p < block->start) {
+ node = node->left;
+
+ } else if (p >= block->start + block->size) {
+ node = node->right;
+
+ } else {
+ return block;
+ }
+ }
+
+ return NULL;
+}
+
+
+static const char *
+nxt_mp_chunk_free(nxt_mp_t *mp, nxt_mp_block_t *cluster, u_char *p)
+{
+ u_char *start;
+ uintptr_t offset;
+ nxt_uint_t n, size, chunk;
+ nxt_queue_t *chunk_pages;
+ nxt_mp_page_t *page;
+
+ n = (p - cluster->start) >> mp->page_size_shift;
+ start = cluster->start + (n << mp->page_size_shift);
+
+ page = &cluster->pages[n];
+
+ if (nxt_slow_path(page->size == 0)) {
+ return "freed pointer points to already free page: %p";
+ }
+
+ if (nxt_slow_path(page->size == 0xFF)) {
+ return "freed pointer points to non-freeble page: %p";
+ }
+
+ size = page->size << mp->chunk_size_shift;
+
+ if (size != mp->page_size) {
+
+ offset = (uintptr_t) (p - start) & (mp->page_size - 1);
+ chunk = offset / size;
+
+ if (nxt_slow_path(offset != chunk * size)) {
+ return "freed pointer points to wrong chunk: %p";
+ }
+
+ if (nxt_slow_path(nxt_mp_chunk_is_free(page->u.map, chunk))) {
+ return "freed pointer points to already free chunk: %p";
+ }
+
+ nxt_mp_chunk_set_free(page->u.map, chunk);
+
+ if (page->u.map != 0xFFFFFFFF) {
+ page->chunks++;
+
+ if (page->chunks == 1) {
+ /*
+ * Add the page to the head of pool chunk pages list
+ * of pages with free chunks.
+ */
+ n = nxt_mp_chunk_pages_index(mp, size);
+ chunk_pages = &mp->chunk_pages[n];
+
+ nxt_queue_insert_head(chunk_pages, &page->link);
+ }
+
+ nxt_mp_free_junk(p, size);
+
+ return NULL;
+
+ } else {
+ /*
+ * All chunks are free, remove the page from pool
+ * chunk pages list of pages with free chunks.
+ */
+ nxt_queue_remove(&page->link);
+ }
+
+ } else if (nxt_slow_path(p != start)) {
+ return "invalid pointer to chunk: %p";
+ }
+
+ /* Add the free page to the pool's free pages tree. */
+
+ page->size = 0;
+ nxt_queue_insert_head(&mp->free_pages, &page->link);
+
+ nxt_mp_free_junk(p, size);
+
+ /* Test if all pages in the cluster are free. */
+
+ n = mp->cluster_size >> mp->page_size_shift;
+ page = cluster->pages;
+
+ do {
+ if (page->size != 0) {
+ return NULL;
+ }
+
+ page++;
+ n--;
+ } while (n != 0);
+
+ /* Free cluster. */
+
+ n = mp->cluster_size >> mp->page_size_shift;
+ page = cluster->pages;
+
+ do {
+ nxt_queue_remove(&page->link);
+ page++;
+ n--;
+ } while (n != 0);
+
+ nxt_rbtree_delete(&mp->blocks, &cluster->node);
+
+ p = cluster->start;
+
+ nxt_free(cluster);
+ nxt_free(p);
+
+ return NULL;
+}
+
+
+void *
+nxt_mp_retain(nxt_mp_t *mp, size_t size)
+{
+ void *p;
+
+ p = nxt_mp_alloc(mp, size);
+
+ if (nxt_fast_path(p != NULL)) {
+ mp->retain++;
+ nxt_debug_alloc("mp retain: %uD", mp->retain);
+ }
+
+ return p;
+}
+
+
+void
+nxt_mp_release(nxt_mp_t *mp, void *p)
+{
+ nxt_mp_free(mp, p);
+
+ mp->retain--;
+
+ nxt_debug_alloc("mp release: %uD", mp->retain);
+
+ if (mp->retain == 0) {
+ nxt_mp_destroy(mp);
+ }
+}
+
+
+void *
+nxt_mp_nget(nxt_mp_t *mp, size_t size)
+{
+ nxt_debug_alloc("mp nget: %uz", size);
+
+#if !(NXT_DEBUG_MEMORY)
+
+ if (size <= mp->page_size) {
+ return nxt_mp_get_small(mp, &mp->nget_pages, size);
+ }
+
+#endif
+
+ return nxt_mp_alloc_large(mp, NXT_MAX_ALIGNMENT, size);
+}
+
+
+void *
+nxt_mp_get(nxt_mp_t *mp, size_t size)
+{
+ nxt_debug_alloc("mp get: %uz", size);
+
+#if !(NXT_DEBUG_MEMORY)
+
+ if (size <= mp->page_size) {
+ size = nxt_max(size, NXT_MAX_ALIGNMENT);
+ return nxt_mp_get_small(mp, &mp->get_pages, size);
+ }
+
+#endif
+
+ return nxt_mp_alloc_large(mp, NXT_MAX_ALIGNMENT, size);
+}
+
+
+void *
+nxt_mp_zget(nxt_mp_t *mp, size_t size)
+{
+ void *p;
+
+ p = nxt_mp_get(mp, size);
+
+ if (nxt_fast_path(p != NULL)) {
+ memset(p, 0, size);
+ }
+
+ return p;
+}
+
+
+static void *
+nxt_mp_get_small(nxt_mp_t *mp, nxt_queue_t *pages, size_t size)
+{
+ u_char *p;
+ uint32_t available;
+ nxt_mp_page_t *page;
+ nxt_queue_link_t *link, *next;
+
+ for (link = nxt_queue_first(pages);
+ link != nxt_queue_tail(pages);
+ link = next)
+ {
+ next = nxt_queue_next(link);
+ page = nxt_queue_link_data(link, nxt_mp_page_t, link);
+
+ available = mp->page_size - page->u.taken;
+
+ if (size <= available) {
+ goto found;
+ }
+
+ if (available == 0 || page->fails++ > 100) {
+ nxt_queue_remove(link);
+ }
+ }
+
+ page = nxt_mp_alloc_page(mp);
+
+ if (nxt_slow_path(page == NULL)) {
+ return page;
+ }
+
+ nxt_queue_insert_head(pages, &page->link);
+
+ page->size = 0xFF;
+
+found:
+
+ p = nxt_mp_page_addr(mp, page);
+
+ p += page->u.taken;
+ page->u.taken += size;
+
+ nxt_debug_alloc("mp get: %p", p);
+
+ return p;
+}