[Tarantool-patches] [PATCH 2/3] box: introduce port_c

Nikita Pettik korablev at tarantool.org
Thu Mar 26 20:49:11 MSK 2020


On 08 Mar 18:47, Vladislav Shpilevoy wrote:

In general, patch is okay. Below some comments which can be
considered as nits.

I see that your branch doesn't pass CI testing:
https://travis-ci.org/github/tarantool/tarantool/jobs/659861432?utm_medium=notification&utm_source=github_status

I haven't managed to find box/alter_limits among existing flaky
tests. If this test fail is not really related to your patches,
could you please file an issue?

> If you (reviewers) want me to drop port_tuple in scope of this
> patchset, I can do that. At the moment of sending the patchset I
> decided to postpone it.

I'd do it as a follow-up in case you have time to do it.
Otherwise please just file an issue.

> diff --git a/src/box/port.c b/src/box/port.c
> index 6e2fe3a6e..925a4b2d5 100644
> --- a/src/box/port.c
> +++ b/src/box/port.c
> @@ -37,7 +37,12 @@
>  #include <fiber.h>
>  #include "errinj.h"
>  
> -static struct mempool port_tuple_entry_pool;

I'd add comment explaining that port_entry_pool holds both
port_c and port_tuple entries now. Moreover, as an optimization
some msgpacks stored in port_c also can be allocated in this
pool (as far as I understand).

> +static struct mempool port_entry_pool;
> +
> +enum {
> +	PORT_ENTRY_SIZE = MAX(sizeof(struct port_c_entry),
> +			      sizeof(struct port_tuple_entry)),
> +};
>  
> @@ -127,28 +132,194 @@ port_tuple_dump_msgpack(struct port *base, struct obuf *out)
>  extern void
>  port_tuple_dump_lua(struct port *base, struct lua_State *L, bool is_flat);
>  
> +static inline void
> +port_c_destroy_entry(struct port_c_entry *pe)
> +{

I'd add a brief reference like:

/* For allocation policy see port_c_add_tuple() and port_c_add_mp(). */

> +	if (pe->mp_size == 0)
> +		tuple_unref(pe->tuple);
> +	else if (pe->mp_size <= PORT_ENTRY_SIZE)
> +		mempool_free(&port_entry_pool, pe->mp);
> +	else
> +		free(pe->mp);
> +}
> +
> +static inline struct port_c_entry *
> +port_c_new_entry(struct port_c *port)
> +{
> +	struct port_c_entry *e;
> +	if (port->size == 0) {
> +		e = &port->first_entry;

I don't really get this 'first_entry' optimization. Why do you
need it at all?

> +		port->first = e;
> +		port->last = e;
> +	} else {
> +		e = mempool_alloc(&port_entry_pool);
> +		if (e == NULL) {
> +			diag_set(OutOfMemory, sizeof(*e), "mempool_alloc", "e");
> +			return NULL;
> +		}
> +		port->last->next = e;
> +		port->last = e;
> +	}
> +	e->next = NULL;
> +	++port->size;
> +	return e;
> +}
> +
> +int
> +port_c_add_tuple(struct port *base, struct tuple *tuple)
> +{
> +	struct port_c *port = (struct port_c *)base;
> +	struct port_c_entry *pe = port_c_new_entry(port);

I guess reverting condition would make code flow more straightforward:
if (pe == NULL)
	return -1;
...

> +	if (pe != NULL) {
> +		/* 0 mp_size means the entry stores a tuple. */
> +		pe->mp_size = 0;
> +		pe->tuple = tuple;
> +		tuple_ref(tuple);
> +		return 0;
> +	}
> +	return -1;
> +}
> +
> +int
> +port_c_add_mp(struct port *base, const char *mp, const char *mp_end)
> +{
> +	struct port_c *port = (struct port_c *)base;
> +	struct port_c_entry *pe;
> +	uint32_t size = mp_end - mp;
> +	char *dst;
> +	if (size <= PORT_ENTRY_SIZE) {
> +		/*
> +		 * Alloc on a mempool is several times faster than
> +		 * on the heap. And it perfectly fits any
> +		 * MessagePack number, a short string, a boolean.
> +		 */
> +		dst = mempool_alloc(&port_entry_pool);

Dubious optimization...I mean is this code complication really
worth it?

> +		if (dst == NULL) {
> +			diag_set(OutOfMemory, size, "mempool_alloc", "dst");
> +			return -1;
> +		}
> +
> +static int
> +port_c_dump_msgpack_16(struct port *base, struct obuf *out)
> +{
> +	struct port_c *port = (struct port_c *)base;
> +	struct port_c_entry *pe;
> +	for (pe = port->first; pe != NULL; pe = pe->next) {
> +		uint32_t size = pe->mp_size;
> +		if (size == 0) {
> +			if (tuple_to_obuf(pe->tuple, out) != 0)
> +				return -1;
> +		} else if (obuf_dup(out, pe->mp, size) != size) {
> +			diag_set(OutOfMemory, size, "obuf_dup", "data");
> +			return -1;
> +		}
> +		ERROR_INJECT(ERRINJ_PORT_DUMP, {
> +			diag_set(OutOfMemory,
> +				 size == 0 ? tuple_size(pe->tuple) : size,
> +				 "obuf_dup", "data");
> +			return -1;
> +		});

You added error injection, but did not test it. Could you provide test
(or point me at it please)?

> +	}
> +	return port->size;
> +}
> +
> diff --git a/src/box/port.h b/src/box/port.h
> index 9d3d02b3c..cd40d8a15 100644
> --- a/src/box/port.h
> +++ b/src/box/port.h
> @@ -130,6 +130,56 @@ void
>  port_vdbemem_create(struct port *base, struct sql_value *mem,
>  		    uint32_t mem_count);
>  
> +/**
> + * The struct is PACKED to avoid unnecessary 4 byte padding

Is this really profitable?

> + * after mp_size. These objects are never stored on stack, neither
> + * allocated as an array, so the padding is not needed.
> + */
> +struct PACKED port_c_entry {
> +	struct port_c_entry *next;
> +	union {
> +		/** Valid if mp_size is 0. */
> +		struct tuple *tuple;
> +		/**
> +		 * Valid if mp_size is > 0. MessagePack is
> +		 * allocated either on heap or on the port entry
> +		 * mempool, if it fits into a pool object.
> +		 */
> +		char *mp;
> +	};
> +	uint32_t mp_size;
> +};
> +
> +/**
> + * C port is used by C functions from the public API. They can
> + * return tuples and arbitrary MessagePack.
> + */
> +struct port_c {
> +	const struct port_vtab *vtab;
> +	struct port_c_entry *first;
> +	struct port_c_entry *last;
> +	struct port_c_entry first_entry;
> +	int size;

Why do you call it size? I'd name it entry_cnt or count
(imho port size = sum of all entries' sizes).

> +};
> +
> +static_assert(sizeof(struct port_c) <= sizeof(struct port),
> +	      "sizeof(struct port_c) must be <= sizeof(struct port)");
> +
>  port_init(void);
>  


More information about the Tarantool-patches mailing list