Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH 0/3] Safe allocation on truncation and deletion
@ 2019-12-13  7:05 Ilya Kosarev
  2019-12-13  7:05 ` [Tarantool-patches] [PATCH 1/3] b-tree: return NULL on matras_alloc fail Ilya Kosarev
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Ilya Kosarev @ 2019-12-13  7:05 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

space:truncate() and space:delete() could fail on memory allocations
when reaching memtx_memory limit. As far as it is quite an ill
behaviour, it is fixed in this patchset through denial of extra
memory reservation and unconstrained allocation of service tuples
in BOX_TRUNCATE_ID space.

Branch: https://github.com/tarantool/tarantool/tree/i.kosarev/gh-3807-safe-alloc-on-truncation
Issue: https://github.com/tarantool/tarantool/issues/3807

Ilya Kosarev (3):
  b-tree: return NULL on matras_alloc fail
  memtx: don't reserve extra memory if not needed
  tuple: use calloc for service truncation tuples

 src/box/memtx_engine.c   | 50 ++++++++++++++++++++++++++--------------
 src/box/memtx_engine.h   |  2 +-
 src/box/memtx_space.c    | 34 ++++++++++++++++++++-------
 src/box/tuple.c          | 16 +++++++++----
 src/box/tuple.h          |  2 +-
 src/box/tuple_format.c   |  1 +
 src/box/tuple_format.h   | 10 +++++++-
 src/box/vy_stmt.c        |  3 ++-
 src/lib/salad/bps_tree.h |  2 ++
 9 files changed, 87 insertions(+), 33 deletions(-)

-- 
2.17.1

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [Tarantool-patches] [PATCH 1/3] b-tree: return NULL on matras_alloc fail
  2019-12-13  7:05 [Tarantool-patches] [PATCH 0/3] Safe allocation on truncation and deletion Ilya Kosarev
@ 2019-12-13  7:05 ` Ilya Kosarev
  2019-12-19  0:32   ` Vladislav Shpilevoy
  2019-12-13  7:05 ` [Tarantool-patches] [PATCH 2/3] memtx: don't reserve extra memory if not needed Ilya Kosarev
  2019-12-13  7:05 ` [Tarantool-patches] [PATCH 3/3] tuple: use calloc for service truncation tuples Ilya Kosarev
  2 siblings, 1 reply; 7+ messages in thread
From: Ilya Kosarev @ 2019-12-13  7:05 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

In bps_tree_create_leaf we use matras_alloc in case
bps_tree_garbage_pop didn't work out. However it also might not
succeed. Then we need to return NULL instead of dereferencing NULL
pointer.

Prerequisites: #3807
---
 src/lib/salad/bps_tree.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/lib/salad/bps_tree.h b/src/lib/salad/bps_tree.h
index d28b53f53c8..51f5c8b5556 100644
--- a/src/lib/salad/bps_tree.h
+++ b/src/lib/salad/bps_tree.h
@@ -2149,6 +2149,8 @@ bps_tree_create_leaf(struct bps_tree *tree, bps_tree_block_id_t *id)
 			       bps_tree_garbage_pop(tree, id);
 	if (!res)
 		res = (struct bps_leaf *)matras_alloc(&tree->matras, id);
+	if (!res)
+		return NULL;
 	res->header.type = BPS_TREE_BT_LEAF;
 	tree->leaf_count++;
 	return res;
-- 
2.17.1

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [Tarantool-patches] [PATCH 2/3] memtx: don't reserve extra memory if not needed
  2019-12-13  7:05 [Tarantool-patches] [PATCH 0/3] Safe allocation on truncation and deletion Ilya Kosarev
  2019-12-13  7:05 ` [Tarantool-patches] [PATCH 1/3] b-tree: return NULL on matras_alloc fail Ilya Kosarev
@ 2019-12-13  7:05 ` Ilya Kosarev
  2019-12-19  0:31   ` Vladislav Shpilevoy
  2019-12-13  7:05 ` [Tarantool-patches] [PATCH 3/3] tuple: use calloc for service truncation tuples Ilya Kosarev
  2 siblings, 1 reply; 7+ messages in thread
From: Ilya Kosarev @ 2019-12-13  7:05 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

memtx_index_extent_reserve was always being called from
memtx_space_replace_all_keys. It could lead to slab allocator failure
while reaching memtx_memory limit, which is especially distressing on
space:truncate() and space:delete() invocations. we  Now it is not
being called if not needed, including space:truncate() and
space:delete() cases.

Part of #3807
---
 src/box/memtx_space.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c
index 6ef84e04564..1e8759778e7 100644
--- a/src/box/memtx_space.c
+++ b/src/box/memtx_space.c
@@ -260,10 +260,18 @@ memtx_space_replace_all_keys(struct space *space, struct tuple *old_tuple,
 	 * Ensure we have enough slack memory to guarantee
 	 * successful statement-level rollback.
 	 */
-	if (memtx_index_extent_reserve(memtx, new_tuple != NULL ?
-				       RESERVE_EXTENTS_BEFORE_REPLACE :
-				       RESERVE_EXTENTS_BEFORE_DELETE) != 0)
+	if (space->index_count > 1 && new_tuple != NULL) {
+		if (memtx_index_extent_reserve(memtx, new_tuple != NULL ?
+					       RESERVE_EXTENTS_BEFORE_REPLACE :
+					       RESERVE_EXTENTS_BEFORE_DELETE) != 0)
+			return -1;
+	}
+	ERROR_INJECT(ERRINJ_INDEX_ALLOC, {
+		/* same error as in mempool_alloc */
+		diag_set(OutOfMemory, MEMTX_EXTENT_SIZE,
+			 "mempool", "new slab");
 		return -1;
+	});
 
 	uint32_t i = 0;
 
-- 
2.17.1

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [Tarantool-patches] [PATCH 3/3] tuple: use calloc for service truncation tuples
  2019-12-13  7:05 [Tarantool-patches] [PATCH 0/3] Safe allocation on truncation and deletion Ilya Kosarev
  2019-12-13  7:05 ` [Tarantool-patches] [PATCH 1/3] b-tree: return NULL on matras_alloc fail Ilya Kosarev
  2019-12-13  7:05 ` [Tarantool-patches] [PATCH 2/3] memtx: don't reserve extra memory if not needed Ilya Kosarev
@ 2019-12-13  7:05 ` Ilya Kosarev
  2019-12-19  0:31   ` Vladislav Shpilevoy
  2 siblings, 1 reply; 7+ messages in thread
From: Ilya Kosarev @ 2019-12-13  7:05 UTC (permalink / raw)
  To: tarantool-patches; +Cc: v.shpilevoy

Trying to perform space:truncate() while reaching memtx_memory limit we
could experience slab allocator failure on allocation for service tuple
being upserted into service space to keep record of truncations. This
behavior seems to be quite surprising for users. Now we are using
calloc() to allocate tuples in BOX_TRUNCATE_ID space.

Closes: #3807
---
 src/box/memtx_engine.c | 50 ++++++++++++++++++++++++++++--------------
 src/box/memtx_engine.h |  2 +-
 src/box/memtx_space.c  | 20 ++++++++++++-----
 src/box/tuple.c        | 16 ++++++++++----
 src/box/tuple.h        |  2 +-
 src/box/tuple_format.c |  1 +
 src/box/tuple_format.h | 10 ++++++++-
 src/box/vy_stmt.c      |  3 ++-
 8 files changed, 74 insertions(+), 30 deletions(-)

diff --git a/src/box/memtx_engine.c b/src/box/memtx_engine.c
index 23ccc4703fe..59148059f2b 100644
--- a/src/box/memtx_engine.c
+++ b/src/box/memtx_engine.c
@@ -1113,7 +1113,7 @@ memtx_leave_delayed_free_mode(struct memtx_engine *memtx)
 }
 
 struct tuple *
-memtx_tuple_new(struct tuple_format *format, const char *data, const char *end)
+memtx_tuple_new(struct tuple_format *format, const char *data, const char *end, bool use_calloc)
 {
 	struct memtx_engine *memtx = (struct memtx_engine *)format->engine;
 	assert(mp_typeof(*data) == MP_ARRAY);
@@ -1139,15 +1139,27 @@ memtx_tuple_new(struct tuple_format *format, const char *data, const char *end)
 	}
 
 	struct memtx_tuple *memtx_tuple;
-	while ((memtx_tuple = smalloc(&memtx->alloc, total)) == NULL) {
-		bool stop;
-		memtx_engine_run_gc(memtx, &stop);
-		if (stop)
-			break;
-	}
-	if (memtx_tuple == NULL) {
-		diag_set(OutOfMemory, total, "slab allocator", "memtx_tuple");
-		goto end;
+	if (use_calloc) {
+		memtx_tuple = calloc(1, total);
+		if (memtx_tuple == NULL) {
+			diag_set(OutOfMemory, total, "malloc",
+						     "memtx_tuple");
+			goto end;
+		}
+		format->is_malloced = true;
+	} else {
+		while ((memtx_tuple = smalloc(&memtx->alloc,
+					      total)) == NULL) {
+			bool stop;
+			memtx_engine_run_gc(memtx, &stop);
+			if (stop)
+				break;
+		}
+		if (memtx_tuple == NULL) {
+			diag_set(OutOfMemory, total, "slab allocator",
+						     "memtx_tuple");
+			goto end;
+		}
 	}
 	tuple = &memtx_tuple->base;
 	tuple->refs = 0;
@@ -1177,16 +1189,20 @@ memtx_tuple_delete(struct tuple_format *format, struct tuple *tuple)
 	struct memtx_engine *memtx = (struct memtx_engine *)format->engine;
 	say_debug("%s(%p)", __func__, tuple);
 	assert(tuple->refs == 0);
-	tuple_format_unref(format);
 	struct memtx_tuple *memtx_tuple =
 		container_of(tuple, struct memtx_tuple, base);
 	size_t total = tuple_size(tuple) + offsetof(struct memtx_tuple, base);
-	if (memtx->alloc.free_mode != SMALL_DELAYED_FREE ||
-	    memtx_tuple->version == memtx->snapshot_version ||
-	    format->is_temporary)
-		smfree(&memtx->alloc, memtx_tuple, total);
-	else
-		smfree_delayed(&memtx->alloc, memtx_tuple, total);
+	if (format->is_malloced) {
+		free(memtx_tuple);
+	} else {
+		if (memtx->alloc.free_mode != SMALL_DELAYED_FREE ||
+		    memtx_tuple->version == memtx->snapshot_version ||
+		    format->is_temporary)
+			smfree(&memtx->alloc, memtx_tuple, total);
+		else
+			smfree_delayed(&memtx->alloc, memtx_tuple, total);
+	}
+	tuple_format_unref(format);
 }
 
 void
diff --git a/src/box/memtx_engine.h b/src/box/memtx_engine.h
index f562c66df4f..a2ae8134d30 100644
--- a/src/box/memtx_engine.h
+++ b/src/box/memtx_engine.h
@@ -235,7 +235,7 @@ memtx_leave_delayed_free_mode(struct memtx_engine *memtx);
 
 /** Allocate a memtx tuple. @sa tuple_new(). */
 struct tuple *
-memtx_tuple_new(struct tuple_format *format, const char *data, const char *end);
+memtx_tuple_new(struct tuple_format *format, const char *data, const char *end, bool use_calloc);
 
 /** Free a memtx tuple. @sa tuple_delete(). */
 void
diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c
index 1e8759778e7..c413fd9b1e3 100644
--- a/src/box/memtx_space.c
+++ b/src/box/memtx_space.c
@@ -332,7 +332,9 @@ memtx_space_execute_replace(struct space *space, struct txn *txn,
 	struct txn_stmt *stmt = txn_current_stmt(txn);
 	enum dup_replace_mode mode = dup_replace_mode(request->type);
 	stmt->new_tuple = memtx_tuple_new(space->format, request->tuple,
-					  request->tuple_end);
+					  request->tuple_end,
+					  request->space_id
+					  == BOX_TRUNCATE_ID);
 	if (stmt->new_tuple == NULL)
 		return -1;
 	tuple_ref(stmt->new_tuple);
@@ -406,7 +408,9 @@ memtx_space_execute_update(struct space *space, struct txn *txn,
 		return -1;
 
 	stmt->new_tuple = memtx_tuple_new(format, new_data,
-					  new_data + new_size);
+					  new_data + new_size,
+					  request->space_id
+					  == BOX_TRUNCATE_ID);
 	if (stmt->new_tuple == NULL)
 		return -1;
 	tuple_ref(stmt->new_tuple);
@@ -475,7 +479,9 @@ memtx_space_execute_upsert(struct space *space, struct txn *txn,
 			return -1;
 		}
 		stmt->new_tuple = memtx_tuple_new(format, request->tuple,
-						  request->tuple_end);
+						  request->tuple_end,
+						  request->space_id
+						  == BOX_TRUNCATE_ID);
 		if (stmt->new_tuple == NULL)
 			return -1;
 		tuple_ref(stmt->new_tuple);
@@ -499,7 +505,9 @@ memtx_space_execute_upsert(struct space *space, struct txn *txn,
 			return -1;
 
 		stmt->new_tuple = memtx_tuple_new(format, new_data,
-						  new_data + new_size);
+						  new_data + new_size,
+						  request->space_id
+						  == BOX_TRUNCATE_ID);
 		if (stmt->new_tuple == NULL)
 			return -1;
 		tuple_ref(stmt->new_tuple);
@@ -548,7 +556,9 @@ memtx_space_ephemeral_replace(struct space *space, const char *tuple,
 {
 	struct memtx_space *memtx_space = (struct memtx_space *)space;
 	struct tuple *new_tuple = memtx_tuple_new(space->format, tuple,
-						  tuple_end);
+						  tuple_end,
+						  space->def->id
+						  == BOX_TRUNCATE_ID);
 	if (new_tuple == NULL)
 		return -1;
 	struct tuple *old_tuple;
diff --git a/src/box/tuple.c b/src/box/tuple.c
index 4d676b090c2..bafa8e0cb5c 100644
--- a/src/box/tuple.c
+++ b/src/box/tuple.c
@@ -60,7 +60,7 @@ static void
 runtime_tuple_delete(struct tuple_format *format, struct tuple *tuple);
 
 static struct tuple *
-runtime_tuple_new(struct tuple_format *format, const char *data, const char *end);
+runtime_tuple_new(struct tuple_format *format, const char *data, const char *end, bool use_calloc);
 
 /** A virtual method table for tuple_format_runtime */
 static struct tuple_format_vtab tuple_format_runtime_vtab = {
@@ -71,7 +71,7 @@ static struct tuple_format_vtab tuple_format_runtime_vtab = {
 };
 
 static struct tuple *
-runtime_tuple_new(struct tuple_format *format, const char *data, const char *end)
+runtime_tuple_new(struct tuple_format *format, const char *data, const char *end, bool use_calloc)
 {
 	assert(format->vtab.tuple_delete == tuple_format_runtime_vtab.tuple_delete);
 
@@ -86,8 +86,13 @@ runtime_tuple_new(struct tuple_format *format, const char *data, const char *end
 
 	size_t data_len = end - data;
 	size_t total = sizeof(struct tuple) + field_map_size + data_len;
-	tuple = (struct tuple *) smalloc(&runtime_alloc, total);
+	if (use_calloc) {
+		tuple = (struct tuple *) calloc(1, total);
+		format->is_malloced = true;
+	} else
+		tuple = (struct tuple *) smalloc(&runtime_alloc, total);
 	if (tuple == NULL) {
+		format->is_malloced = false;
 		diag_set(OutOfMemory, (unsigned) total,
 			 "malloc", "tuple");
 		goto end;
@@ -114,8 +119,11 @@ runtime_tuple_delete(struct tuple_format *format, struct tuple *tuple)
 	say_debug("%s(%p)", __func__, tuple);
 	assert(tuple->refs == 0);
 	size_t total = tuple_size(tuple);
+	if (format->is_malloced)
+		free(tuple);
+	else
+		smfree(&runtime_alloc, tuple, total);
 	tuple_format_unref(format);
-	smfree(&runtime_alloc, tuple, total);
 }
 
 int
diff --git a/src/box/tuple.h b/src/box/tuple.h
index 9a887721986..7fa62659acc 100644
--- a/src/box/tuple.h
+++ b/src/box/tuple.h
@@ -431,7 +431,7 @@ tuple_format(struct tuple *tuple)
 static inline struct tuple *
 tuple_new(struct tuple_format *format, const char *data, const char *end)
 {
-	return format->vtab.tuple_new(format, data, end);
+	return format->vtab.tuple_new(format, data, end, false);
 }
 
 /**
diff --git a/src/box/tuple_format.c b/src/box/tuple_format.c
index 312c96621fb..d0e60acd334 100644
--- a/src/box/tuple_format.c
+++ b/src/box/tuple_format.c
@@ -789,6 +789,7 @@ tuple_format_new(struct tuple_format_vtab *vtab, void *engine,
 		memset(&format->vtab, 0, sizeof(format->vtab));
 	format->engine = engine;
 	format->is_temporary = is_temporary;
+	format->is_malloced = false;
 	format->is_ephemeral = is_ephemeral;
 	format->exact_field_count = exact_field_count;
 	format->epoch = ++formats_epoch;
diff --git a/src/box/tuple_format.h b/src/box/tuple_format.h
index 021072d3d4c..cb9f0a0407d 100644
--- a/src/box/tuple_format.h
+++ b/src/box/tuple_format.h
@@ -82,7 +82,7 @@ struct tuple_format_vtab {
 	 */
 	struct tuple*
 	(*tuple_new)(struct tuple_format *format, const char *data,
-	             const char *end);
+		     const char *end, bool use_calloc);
 	/**
 	 * Free a tuple_chunk allocated for given tuple and
 	 * data.
@@ -188,6 +188,14 @@ struct tuple_format {
 	 * in progress.
 	 */
 	bool is_temporary;
+	/**
+	 * Tuple is being malloced if we use calloc instead of
+	 * traditional smalloc to allocate it. It is happening
+	 * if the tuple is being upserted into BOX_TRUNCATE_ID
+	 * space as far as we want to perform space:truncate()
+	 * even if memtx_memory limit is reached.
+	 */
+	bool is_malloced;
 	/**
 	 * This format belongs to ephemeral space and thus might
 	 * be shared with other ephemeral spaces.
diff --git a/src/box/vy_stmt.c b/src/box/vy_stmt.c
index 518a24f43d7..b6af0b00e5d 100644
--- a/src/box/vy_stmt.c
+++ b/src/box/vy_stmt.c
@@ -82,8 +82,9 @@ vy_stmt_persistent_flags(struct tuple *stmt, bool is_primary)
 }
 
 static struct tuple *
-vy_tuple_new(struct tuple_format *format, const char *data, const char *end)
+vy_tuple_new(struct tuple_format *format, const char *data, const char *end, bool use_calloc)
 {
+	(void)use_calloc;
 	if (tuple_validate_raw(format, data) != 0)
 		return NULL;
 
-- 
2.17.1

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [Tarantool-patches] [PATCH 3/3] tuple: use calloc for service truncation tuples
  2019-12-13  7:05 ` [Tarantool-patches] [PATCH 3/3] tuple: use calloc for service truncation tuples Ilya Kosarev
@ 2019-12-19  0:31   ` Vladislav Shpilevoy
  0 siblings, 0 replies; 7+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-19  0:31 UTC (permalink / raw)
  To: Ilya Kosarev, tarantool-patches

Thanks for the patch!

See 3 comments below.

On 13/12/2019 08:05, Ilya Kosarev wrote:
> Trying to perform space:truncate() while reaching memtx_memory limit we
> could experience slab allocator failure on allocation for service tuple
> being upserted into service space to keep record of truncations. This
> behavior seems to be quite surprising for users. Now we are using
> calloc() to allocate tuples in BOX_TRUNCATE_ID space.
> 
> Closes: #3807
> ---
>  src/box/memtx_engine.c | 50 ++++++++++++++++++++++++++++--------------
>  src/box/memtx_engine.h |  2 +-
>  src/box/memtx_space.c  | 20 ++++++++++++-----
>  src/box/tuple.c        | 16 ++++++++++----
>  src/box/tuple.h        |  2 +-
>  src/box/tuple_format.c |  1 +
>  src/box/tuple_format.h | 10 ++++++++-
>  src/box/vy_stmt.c      |  3 ++-
>  8 files changed, 74 insertions(+), 30 deletions(-)
> 
> diff --git a/src/box/memtx_engine.c b/src/box/memtx_engine.c
> index 23ccc4703fe..59148059f2b 100644
> --- a/src/box/memtx_engine.c
> +++ b/src/box/memtx_engine.c
> @@ -1113,7 +1113,7 @@ memtx_leave_delayed_free_mode(struct memtx_engine *memtx)
>  }
>  
>  struct tuple *
> -memtx_tuple_new(struct tuple_format *format, const char *data, const char *end)
> +memtx_tuple_new(struct tuple_format *format, const char *data, const char *end, bool use_calloc)

1. Oh. No. Please. The whole point of format vtable is to avoid that.

>  {
>  	struct memtx_engine *memtx = (struct memtx_engine *)format->engine;
>  	assert(mp_typeof(*data) == MP_ARRAY);
> @@ -1139,15 +1139,27 @@ memtx_tuple_new(struct tuple_format *format, const char *data, const char *end)
>  	}
>  
>  	struct memtx_tuple *memtx_tuple;
> -	while ((memtx_tuple = smalloc(&memtx->alloc, total)) == NULL) {
> -		bool stop;
> -		memtx_engine_run_gc(memtx, &stop);
> -		if (stop)
> -			break;
> -	}
> -	if (memtx_tuple == NULL) {
> -		diag_set(OutOfMemory, total, "slab allocator", "memtx_tuple");
> -		goto end;
> +	if (use_calloc) {
> +		memtx_tuple = calloc(1, total);
> +		if (memtx_tuple == NULL) {
> +			diag_set(OutOfMemory, total, "malloc",
> +						     "memtx_tuple");
> +			goto end;
> +		}
> +		format->is_malloced = true;

2. Sorry, we can't change format here. The only reason why format
is not const in that function is because we need increment its
reference counter. A tuple can't change its own format. Especially
in such a hot path.

> +	} else {
> +		while ((memtx_tuple = smalloc(&memtx->alloc,
> +					      total)) == NULL) {
> +			bool stop;
> +			memtx_engine_run_gc(memtx, &stop);
> +			if (stop)
> +				break;
> +		}
> +		if (memtx_tuple == NULL) {
> +			diag_set(OutOfMemory, total, "slab allocator",
> +						     "memtx_tuple");
> +			goto end;
> +		}
>  	}
>  	tuple = &memtx_tuple->base;
>  	tuple->refs = 0;
> @@ -1177,16 +1189,20 @@ memtx_tuple_delete(struct tuple_format *format, struct tuple *tuple)
>  	struct memtx_engine *memtx = (struct memtx_engine *)format->engine;
>  	say_debug("%s(%p)", __func__, tuple);
>  	assert(tuple->refs == 0);
> -	tuple_format_unref(format);
>  	struct memtx_tuple *memtx_tuple =
>  		container_of(tuple, struct memtx_tuple, base);
>  	size_t total = tuple_size(tuple) + offsetof(struct memtx_tuple, base);
> -	if (memtx->alloc.free_mode != SMALL_DELAYED_FREE ||
> -	    memtx_tuple->version == memtx->snapshot_version ||
> -	    format->is_temporary)
> -		smfree(&memtx->alloc, memtx_tuple, total);
> -	else
> -		smfree_delayed(&memtx->alloc, memtx_tuple, total);
> +	if (format->is_malloced) {
> +		free(memtx_tuple);

3. There is a certain purpose for these free_mode, version, blah
blah. These attributes create a read view of the whole memtx engine
to make a snapshot, but do not stop new requests. Tuples are not
deleted during snapshot.

Having ignored the free mode you get this:

====================================================================

    box.cfg{}
    errinj = box.error.injection
    fiber = require('fiber')

    s = box.schema.create_space('test')
    pk = s:create_index('pk')
    s:truncate()
    s:replace{1}
    box.error.injection.set('ERRINJ_SNAP_WRITE_DELAY', true)
    f = fiber.create(box.snapshot)
    box.space._truncate:delete{s.id}

-- The code below improves reproducibility. It creates and
-- deletes several tuples to unref the blessed tuple
-- (tuple_bless), rewrite some recently freed heap chunks.

    s:get{1}
    collectgarbage('collect')
    s:replace{1}
    s:get{1}
    collectgarbage('collect')
    s:replace{1}
    s:get{1}
    collectgarbage('collect')
    s:replace{1}
    s:get{1}
    collectgarbage('collect')

--

    box.error.injection.set('ERRINJ_SNAP_WRITE_DELAY', false)

    tarantool> Process 94390 stopped
    * thread #2, name = 'snapshot', stop reason = EXC_BAD_ACCESS (code=2, address=0x105481000)
        frame #0: 0x00007fff5a964ce9 libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell + 41
    libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell:
    ->  0x7fff5a964ce9 <+41>: rep    movsb  (%rsi), %es:(%rdi)
        0x7fff5a964ceb <+43>: popq   %rbp
        0x7fff5a964cec <+44>: retq   
        0x7fff5a964ced <+45>: cmpq   %rdi, %rsi
    Target 0: (tarantool) stopped.
    (lldb) bt
    * thread #2, name = 'snapshot', stop reason = EXC_BAD_ACCESS (code=2, address=0x105481000)
      * frame #0: 0x00007fff5a964ce9 libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell + 41
        frame #1: 0x00000001003f56ba tarantool`obuf_dup(buf=0x000000010f0ff8b0, data=0x0000000105157aa6, size=85071264) at obuf.c:161:2
        frame #2: 0x00000001003634e8 tarantool`xlog_write_row(log=0x000000010f0ff208, packet=0x000000010f0ff0d8) at xlog.c:1320:7
        frame #3: 0x0000000100038aef tarantool`checkpoint_write_row(l=0x000000010f0ff208, row=0x000000010f0ff0d8) at memtx_engine.c:431:20
        frame #4: 0x0000000100038a2e tarantool`checkpoint_write_tuple(l=0x000000010f0ff208, space_id=330, group_id=0, data="", size=85071264) at memtx_engine.c:459:9
        frame #5: 0x0000000100038811 tarantool`checkpoint_f(ap=0x000000010f000278) at memtx_engine.c:603:8
        frame #6: 0x0000000100005e5a tarantool`fiber_cxx_invoke(f=(tarantool`checkpoint_f at memtx_engine.c:577), ap=0x000000010f000278)(__va_list_tag*), __va_list_tag*) at fiber.h:762:10
        frame #7: 0x000000010018168b tarantool`fiber_loop(data=0x0000000000000000) at fiber.c:830:18
        frame #8: 0x00000001004165e7 tarantool`coro_init at coro.c:110:3

====================================================================

Snapshoting means that you can't solve the issue on the format
level, or tuple level. You need to patch the engine level (IMO).

Honestly, I thought about this issue for quite a long time already,
and I don't have a plan how to fix it. All ways look bad. All of them
assume special patching of _truncate space insertion and crutches for
deletion. For me the most promising way was to introduce an analogue
of vy_quota_force_use() somehow, for _truncate, and use it in a
before_replace trigger of _truncate. Forced use of quota would allow
to temporary overflow the memory limit. But it won't fix deletion.
Maybe the key is in introduction of quota overuse for deletions. I
don't know.

Personally, I don't think the issue is worth fixing at all. If a
user wants to delete/truncate, he can increase memtx memory, do
cleanup, and decrease it back.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [Tarantool-patches] [PATCH 2/3] memtx: don't reserve extra memory if not needed
  2019-12-13  7:05 ` [Tarantool-patches] [PATCH 2/3] memtx: don't reserve extra memory if not needed Ilya Kosarev
@ 2019-12-19  0:31   ` Vladislav Shpilevoy
  0 siblings, 0 replies; 7+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-19  0:31 UTC (permalink / raw)
  To: Ilya Kosarev, tarantool-patches

Thanks for the patch!

On 13/12/2019 08:05, Ilya Kosarev wrote:
> memtx_index_extent_reserve was always being called from
> memtx_space_replace_all_keys. It could lead to slab allocator failure
> while reaching memtx_memory limit, which is especially distressing on
> space:truncate() and space:delete() invocations. we  Now it is not
> being called if not needed, including space:truncate() and
> space:delete() cases.

The thing is that delete also can cause tree rebalancing. Maybe
that is why reservation was done for all cases.

> 
> Part of #3807
> ---
>  src/box/memtx_space.c | 14 +++++++++++---
>  1 file changed, 11 insertions(+), 3 deletions(-)
> 
> diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c
> index 6ef84e04564..1e8759778e7 100644
> --- a/src/box/memtx_space.c
> +++ b/src/box/memtx_space.c
> @@ -260,10 +260,18 @@ memtx_space_replace_all_keys(struct space *space, struct tuple *old_tuple,
>  	 * Ensure we have enough slack memory to guarantee
>  	 * successful statement-level rollback.
>  	 */
> -	if (memtx_index_extent_reserve(memtx, new_tuple != NULL ?
> -				       RESERVE_EXTENTS_BEFORE_REPLACE :
> -				       RESERVE_EXTENTS_BEFORE_DELETE) != 0)
> +	if (space->index_count > 1 && new_tuple != NULL) {
> +		if (memtx_index_extent_reserve(memtx, new_tuple != NULL ?
> +					       RESERVE_EXTENTS_BEFORE_REPLACE :
> +					       RESERVE_EXTENTS_BEFORE_DELETE) != 0)
> +			return -1;

Why does it work only when index count > 1? Why delete don't need
a reservation (I pointed out above that it needs)? And why do you
check for new_tuple != NULL second time after having it already
checked in the if's condition?

> +	}
> +	ERROR_INJECT(ERRINJ_INDEX_ALLOC, {
> +		/* same error as in mempool_alloc */
> +		diag_set(OutOfMemory, MEMTX_EXTENT_SIZE,
> +			 "mempool", "new slab");
>  		return -1;
> +	});
>  
>  	uint32_t i = 0;
>  
> 

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [Tarantool-patches] [PATCH 1/3] b-tree: return NULL on matras_alloc fail
  2019-12-13  7:05 ` [Tarantool-patches] [PATCH 1/3] b-tree: return NULL on matras_alloc fail Ilya Kosarev
@ 2019-12-19  0:32   ` Vladislav Shpilevoy
  0 siblings, 0 replies; 7+ messages in thread
From: Vladislav Shpilevoy @ 2019-12-19  0:32 UTC (permalink / raw)
  To: Ilya Kosarev, tarantool-patches

Hi! Thanks for the patch!

On 13/12/2019 08:05, Ilya Kosarev wrote:
> In bps_tree_create_leaf we use matras_alloc in case
> bps_tree_garbage_pop didn't work out. However it also might not
> succeed. Then we need to return NULL instead of dereferencing NULL
> pointer.
> 
> Prerequisites: #3807
> ---
>  src/lib/salad/bps_tree.h | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/src/lib/salad/bps_tree.h b/src/lib/salad/bps_tree.h
> index d28b53f53c8..51f5c8b5556 100644
> --- a/src/lib/salad/bps_tree.h
> +++ b/src/lib/salad/bps_tree.h
> @@ -2149,6 +2149,8 @@ bps_tree_create_leaf(struct bps_tree *tree, bps_tree_block_id_t *id)
>  			       bps_tree_garbage_pop(tree, id);
>  	if (!res)
>  		res = (struct bps_leaf *)matras_alloc(&tree->matras, id);
> +	if (!res)
> +		return NULL;

In case res was not NULL (what is 99.(9)% of all cases), you
will double check it. Please, move the second check into first
'if's body after matras_alloc.

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2019-12-19  0:32 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-12-13  7:05 [Tarantool-patches] [PATCH 0/3] Safe allocation on truncation and deletion Ilya Kosarev
2019-12-13  7:05 ` [Tarantool-patches] [PATCH 1/3] b-tree: return NULL on matras_alloc fail Ilya Kosarev
2019-12-19  0:32   ` Vladislav Shpilevoy
2019-12-13  7:05 ` [Tarantool-patches] [PATCH 2/3] memtx: don't reserve extra memory if not needed Ilya Kosarev
2019-12-19  0:31   ` Vladislav Shpilevoy
2019-12-13  7:05 ` [Tarantool-patches] [PATCH 3/3] tuple: use calloc for service truncation tuples Ilya Kosarev
2019-12-19  0:31   ` Vladislav Shpilevoy

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox