/*
 * twemproxy - A fast and lightweight proxy for memcached protocol.
 * Copyright (C) 2011 Twitter, Inc.
 * Copyright (C) 2014-2017 Phusion Holding B.V.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef _PSG_MBUF_BLOCK_H_
#define _PSG_MBUF_BLOCK_H_

#include <psg_sysqueue.h>
#include <algorithm>
#include <cstddef>
#include <cassert>
#include <cstring>
#include <oxt/macros.hpp>
#include <boost/cstdint.hpp>
#include <boost/move/core.hpp>

/** A memory buffer allocator system taken from twemproxy and modified to
 * suit our needs.
 *
 * In twemproxy, mbufs enables zero-copy because the same buffer on which a
 * request was received from the client is used for forwarding it to the
 * server. Similarly the same mbuf_block on which a response was received from
 * the server is used for forwarding it to the client.
 *
 * Furthermore, memory for mbufs is managed using a reuse pool. This means
 * that once mbuf_block is allocated, it is not deallocated, but just put back
 * into the reuse pool. By default each mbuf_block chunk is set to 16K bytes in
 * size. There is a trade-off between the mbuf_block size and number of concurrent
 * connections twemproxy can support. A large mbuf_block size reduces the number
 * of read syscalls made by twemproxy when reading requests or responses.
 * However, with large mbuf_block size, every active connection would use up 16K
 * bytes of buffer which might be an issue when twemproxy is handling large
 * number of concurrent connections from clients. When twemproxy is meant
 * to handle a large number of concurrent client connections, you should
 * set chunk size to a small value like 512 bytes.
 *
 * In Phusion Passenger, we modified the mbuf system so that we can take subsets
 * of an mbuf. The actual mbuf (now called mbuf_block in this modification) is
 * not actually put back on the freelist until all subsets are destroyed too.
 * This approach is similar to how Node.js manages buffer slices.
 * We also got rid of the global variables, and put them in an mbuf_pool
 * struct, which acts like a context structure.
 */

//#define MBUF_ENABLE_DEBUGGING
//#define MBUF_ENABLE_BACKTRACES

namespace Passenger {
namespace MemoryKit {

using namespace std;
using namespace boost;


struct mbuf_block;
struct mhdr;

typedef void (*mbuf_block_copy_t)(struct mbuf_block *, void *);

/* See _mbuf_block_init() for format description */
struct mbuf_block {
	boost::uint32_t    magic;     /* mbuf_block magic (const) */
	STAILQ_ENTRY(struct mbuf_block) next;         /* next free mbuf_block */
	#ifdef MBUF_ENABLE_DEBUGGING
		TAILQ_ENTRY(struct mbuf_block) active_q;  /* prev and next active mbuf_block */
	#endif
	#ifdef MBUF_ENABLE_BACKTRACES
		char *backtrace;
	#endif
	char              *start;     /* start of buffer (const) */
	char              *end;       /* end of buffer (const) */
	struct mbuf_pool  *pool;      /* containing pool (const) */
	boost::uint32_t    refcount;  /* number of references by mbuf subsets */
	boost::uint32_t    offset;    /* standalone mbuf_block data size */
};

STAILQ_HEAD(mhdr, struct mbuf_block);
#ifdef MBUF_ENABLE_DEBUGGING
	TAILQ_HEAD(active_mbuf_block_list, struct mbuf_block);
#endif

struct mbuf_pool {
	boost::uint32_t nfree_mbuf_blockq;   /* # free mbuf_block */
	boost::uint32_t nactive_mbuf_blockq; /* # active (non-free) mbuf_block */
	struct mhdr free_mbuf_blockq; /* free mbuf_block q */
	#ifdef MBUF_ENABLE_DEBUGGING
		struct active_mbuf_block_list active_mbuf_blockq; /* active mbuf_block q */
	#endif

	size_t mbuf_block_chunk_size; /* mbuf_block chunk size - header + data (const) */
	size_t mbuf_block_offset;     /* mbuf_block offset in chunk (const) */
};

#define MBUF_BLOCK_MAGIC      0xdeadbeef
#define MBUF_BLOCK_MIN_SIZE   512
#define MBUF_BLOCK_MAX_SIZE   16777216
#define MBUF_BLOCK_HSIZE      sizeof(struct mbuf_block)

#define MBUF_BLOCK_EMPTY(mbuf_block) ((mbuf_block)->pos  == (mbuf_block)->last)
#define MBUF_BLOCK_FULL(mbuf_block)  ((mbuf_block)->last == (mbuf_block)->end)

void mbuf_pool_init(struct mbuf_pool *pool);
void mbuf_pool_deinit(struct mbuf_pool *pool);
size_t mbuf_pool_data_size(struct mbuf_pool *pool);
unsigned int mbuf_pool_compact(struct mbuf_pool *pool);

struct mbuf_block *mbuf_block_get(struct mbuf_pool *pool);
struct mbuf_block *mbuf_block_new_standalone(struct mbuf_pool *pool, size_t size);
void mbuf_block_put(struct mbuf_block *mbuf_block);

void mbuf_block_ref(struct mbuf_block *mbuf_block);
void mbuf_block_unref(struct mbuf_block *mbuf_block);

void _mbuf_block_assert_refcount_at_least_two(struct mbuf_block *mbuf_block);


/* A subset of an mbuf_block. */
class mbuf {
private:
	BOOST_COPYABLE_AND_MOVABLE(mbuf)

	void initialize_with_block(unsigned int start, unsigned int len);
	void initialize_with_block_just_created(unsigned int start, unsigned int len);
	void initialize_with_mbuf(const mbuf &other, unsigned int start, unsigned int len);

public:
	struct just_created_t { };

	struct mbuf_block *mbuf_block; /* container block */
	char              *start;      /* start of subset (const) */
	char              *end;        /* end of subset (const) */

	mbuf()
		: mbuf_block(NULL),
		  start(NULL),
		  end(NULL)
		{ }

	explicit mbuf(struct mbuf_block *block, unsigned int start = 0)
		: mbuf_block(block)
	{
		initialize_with_block(start, block->end - block->start);
	}

	explicit mbuf(struct mbuf_block *block, unsigned int start, unsigned int len)
		: mbuf_block(block)
	{
		initialize_with_block(start, len);
	}

	explicit mbuf(struct mbuf_block *block, unsigned int start, unsigned int len,
		const just_created_t &t)
		: mbuf_block(block)
	{
		initialize_with_block_just_created(start, len);
	}

	// Create an mbuf as a dumb wrapper around a memory buffer.
	explicit mbuf(const char *data, unsigned int len)
		: mbuf_block(NULL),
		  start(const_cast<char *>(data)),
		  end(const_cast<char *>(data) + len)
		{ }

	explicit mbuf(const char *data)
		: mbuf_block(NULL),
		  start(const_cast<char *>(data)),
		  end(const_cast<char *>(data) + strlen(data))
		{ }

	// Copy constructor.
	mbuf(const mbuf &mbuf, unsigned int start = 0) {
		initialize_with_mbuf(mbuf, start, mbuf.end - mbuf.start);
	}

	// Take a subset of another mbuf.
	mbuf(const mbuf &mbuf, unsigned int start, unsigned int len) {
		initialize_with_mbuf(mbuf, start, len);
	}

	// Move constructor.
	explicit
	mbuf(BOOST_RV_REF(mbuf) mbuf)
		: mbuf_block(mbuf.mbuf_block),
		  start(mbuf.start),
		  end(mbuf.end)
	{
		mbuf.mbuf_block = NULL;
		mbuf.start = NULL;
		mbuf.end   = NULL;
	}

	~mbuf() {
		if (mbuf_block != NULL) {
			mbuf_block_unref(mbuf_block);
		}
	}

	// Copy assignment.
	mbuf &operator=(BOOST_COPY_ASSIGN_REF(mbuf) other) {
		if (&other != this) {
			#ifndef NDEBUG
				if (mbuf_block != NULL && mbuf_block == other.mbuf_block) {
					_mbuf_block_assert_refcount_at_least_two(mbuf_block);
				}
			#endif

			if (mbuf_block != NULL) {
				mbuf_block_unref(mbuf_block);
			}

			mbuf_block = other.mbuf_block;
			start      = other.start;
			end        = other.end;

			// We reference 'other.mbuf_block' instead of 'this->mbuf_block' as
			// a micro-optimization. This should decrease the number of data
			// dependencies and allow the CPU to reorder the instructions better.
			if (other.mbuf_block != NULL) {
				mbuf_block_ref(other.mbuf_block);
			}
		}
		return *this;
	}

	// Move assignment.
	mbuf &operator=(BOOST_RV_REF(mbuf) other) {
		if (&other != this) {
			#ifndef NDEBUG
				if (mbuf_block != NULL && mbuf_block == other.mbuf_block) {
					_mbuf_block_assert_refcount_at_least_two(mbuf_block);
				}
			#endif

			if (mbuf_block != NULL) {
				mbuf_block_unref(mbuf_block);
			}

			mbuf_block = other.mbuf_block;
			start      = other.start;
			end        = other.end;

			other.mbuf_block = NULL;
			other.start = NULL;
			other.end   = NULL;
		}
		return *this;
	}

	OXT_FORCE_INLINE
	size_t size() const {
		return end - start;
	}

	OXT_FORCE_INLINE
	bool empty() const {
		return start == end;
	}

	OXT_FORCE_INLINE
	bool is_null() const {
		return start == NULL;
	}
};

mbuf mbuf_block_subset(struct mbuf_block *mbuf_block, unsigned int start, unsigned int len);
mbuf mbuf_get(struct mbuf_pool *pool);
mbuf mbuf_get_with_size(struct mbuf_pool *pool, size_t size);


} // namespace MemoryKit
} // namespace Passenger

#endif /* _PSG_MBUF_BLOCK_H_ */
