From 19cda0fca86d791cf0fe829be607551cd9284423 Mon Sep 17 00:00:00 2001
From: GeorgyKirichenko <kirichenkoga@gmail.com>
Date: Mon, 22 May 2017 18:29:38 +0300
Subject: [PATCH] Add fiber attributes

Fiber attributes are a way to specify parameters that is different
from the default. When a fiber is created using fiber_new_ex(),
an attribute object can be specified to configure custom values for
some options. Attributes are specified only at fiber creation time;
they cannot be altered while the fiber is being used.

Currently only stack_size attribute is supported. Fibers with
non-default stack size won't be recycled via fiber pool.

API overview:

* fiber_new_ex() creates a fiber with custom attributes.
* fiber_attr_new()/fiber_attr_delete() creates/destroys attributes.
* fiber_attr_setstacksize()/fiber_attr_getstacksize() sets/gets
  the fiber stack size.
* fiber_self() returns current running fiber.

All new functions are available from public C API for modules.

See #2438
---
 extra/exports          |   6 ++
 src/fiber.c            | 126 ++++++++++++++++++++++++++++++++++-------
 src/fiber.h            |  86 ++++++++++++++++++++++++++++
 test/unit/fiber.cc     |  36 ++++++++++++
 test/unit/fiber.result |   1 +
 5 files changed, 234 insertions(+), 21 deletions(-)

diff --git a/extra/exports b/extra/exports
index ffbc4de7d6..22ba8e5341 100644
--- a/extra/exports
+++ b/extra/exports
@@ -80,7 +80,13 @@ tnt_HMAC_CTX_free
 # Module API
 
 _say
+fiber_attr_new
+fiber_attr_delete
+fiber_attr_setstacksize
+fiber_attr_getstacksize
+fiber_self
 fiber_new
+fiber_new_ex
 fiber_yield
 fiber_start
 fiber_wakeup
diff --git a/src/fiber.c b/src/fiber.c
index 9366f563fb..9aa8524298 100644
--- a/src/fiber.c
+++ b/src/fiber.c
@@ -86,11 +86,6 @@ pthread_t main_thread_id;
 static size_t page_size;
 static int stack_direction;
 
-enum {
-	/** The number of pages to use for fiber stack */
-	FIBER_STACK_PAGES = 16,
-};
-
 static void
 update_last_stack_frame(struct fiber *fiber)
 {
@@ -102,9 +97,74 @@ update_last_stack_frame(struct fiber *fiber)
 
 }
 
+enum {
+	/* The minimum allowable fiber stack size in bytes */
+	FIBER_STACK_SIZE_MINIMAL = 16384,
+	/* Default fiber stack size in bytes */
+	FIBER_STACK_SIZE_DEFAULT = 65536
+};
+
+/** Default fiber attributes */
+static const struct fiber_attr fiber_attr_default = {
+       .stack_size = FIBER_STACK_SIZE_DEFAULT,
+       .flags = FIBER_DEFAULT_FLAGS
+};
+
+void
+fiber_attr_create(struct fiber_attr *fiber_attr)
+{
+       *fiber_attr = fiber_attr_default;
+}
+
+struct fiber_attr *
+fiber_attr_new()
+{
+	struct fiber_attr *fiber_attr = malloc(sizeof(*fiber_attr));
+	if (fiber_attr == NULL)  {
+		diag_set(OutOfMemory, sizeof(*fiber_attr),
+			 "runtime", "fiber attr");
+		return NULL;
+	}
+	fiber_attr_create(fiber_attr);
+	return fiber_attr;
+}
+
+void
+fiber_attr_delete(struct fiber_attr *fiber_attr)
+{
+	free(fiber_attr);
+}
+
+int
+fiber_attr_setstacksize(struct fiber_attr *fiber_attr, size_t stack_size)
+{
+	if (stack_size < FIBER_STACK_SIZE_MINIMAL) {
+		errno = EINVAL;
+		diag_set(SystemError, "stack size is too small");
+		return -1;
+	}
+	fiber_attr->stack_size = stack_size;
+	if (stack_size != FIBER_STACK_SIZE_DEFAULT) {
+		fiber_attr->flags |= FIBER_CUSTOM_STACK;
+	} else {
+		fiber_attr->flags &= ~FIBER_CUSTOM_STACK;
+	}
+	return 0;
+}
+
+size_t
+fiber_attr_getstacksize(struct fiber_attr *fiber_attr)
+{
+	return fiber_attr != NULL ? fiber_attr->stack_size :
+				    fiber_attr_default.stack_size;
+}
+
 static void
 fiber_recycle(struct fiber *fiber);
 
+static void
+fiber_destroy(struct cord *cord, struct fiber *f);
+
 /**
  * Transfer control to callee fiber.
  */
@@ -521,6 +581,12 @@ unregister_fid(struct fiber *fiber)
 	mh_i32ptr_remove(cord()->fiber_registry, &node, NULL);
 }
 
+struct fiber *
+fiber_self()
+{
+	return fiber();
+}
+
 void
 fiber_gc(void)
 {
@@ -549,6 +615,7 @@ fiber_recycle(struct fiber *fiber)
 	assert(diag_is_empty(&fiber->diag));
 	/* no pending wakeup */
 	assert(rlist_empty(&fiber->state));
+	bool has_custom_stack = fiber->flags & FIBER_CUSTOM_STACK;
 	fiber_reset(fiber);
 	fiber->gc.name[0] = '\0';
 	fiber->f = NULL;
@@ -556,7 +623,11 @@ fiber_recycle(struct fiber *fiber)
 	unregister_fid(fiber);
 	fiber->fid = 0;
 	region_free(&fiber->gc);
-	rlist_move_entry(&cord()->dead, fiber, link);
+	if (!has_custom_stack) {
+		rlist_move_entry(&cord()->dead, fiber, link);
+	} else {
+		fiber_destroy(cord(), fiber);
+	}
 }
 
 static void
@@ -704,24 +775,18 @@ fiber_stack_destroy(struct fiber *fiber, struct slab_cache *slabc)
 	}
 }
 
-/**
- * Create a new fiber.
- *
- * Takes a fiber from fiber cache, if it's not empty.
- * Can fail only if there is not enough memory for
- * the fiber structure or fiber stack.
- *
- * The created fiber automatically returns itself
- * to the fiber cache when its "main" function
- * completes.
- */
 struct fiber *
-fiber_new(const char *name, fiber_func f)
+fiber_new_ex(const char *name, const struct fiber_attr *fiber_attr,
+	     fiber_func f)
 {
 	struct cord *cord = cord();
 	struct fiber *fiber = NULL;
+	if (fiber_attr == NULL)
+		fiber_attr = &fiber_attr_default;
 
-	if (! rlist_empty(&cord->dead)) {
+	/* Now we can not reuse fiber if custom attribute was set */
+	if (!(fiber_attr->flags & FIBER_CUSTOM_STACK) &&
+	    !rlist_empty(&cord->dead)) {
 		fiber = rlist_first_entry(&cord->dead,
 					  struct fiber, link);
 		rlist_move_entry(&cord->alive, fiber, link);
@@ -735,7 +800,7 @@ fiber_new(const char *name, fiber_func f)
 		}
 		memset(fiber, 0, sizeof(struct fiber));
 
-		if (fiber_stack_create(fiber, FIBER_STACK_PAGES * page_size)) {
+		if (fiber_stack_create(fiber, fiber_attr->stack_size)) {
 			mempool_free(&cord->fiber_mempool, fiber);
 			return NULL;
 		}
@@ -749,6 +814,7 @@ fiber_new(const char *name, fiber_func f)
 		rlist_create(&fiber->wake);
 		diag_create(&fiber->diag);
 		fiber_reset(fiber);
+		fiber->flags = fiber_attr->flags;
 
 		rlist_add_entry(&cord->alive, fiber, link);
 	}
@@ -762,6 +828,24 @@ fiber_new(const char *name, fiber_func f)
 	register_fid(fiber);
 
 	return fiber;
+
+}
+
+/**
+ * Create a new fiber.
+ *
+ * Takes a fiber from fiber cache, if it's not empty.
+ * Can fail only if there is not enough memory for
+ * the fiber structure or fiber stack.
+ *
+ * The created fiber automatically returns itself
+ * to the fiber cache when its "main" function
+ * completes.
+ */
+struct fiber *
+fiber_new(const char *name, fiber_func f)
+{
+	return fiber_new_ex(name, NULL, f);
 }
 
 /**
@@ -770,7 +854,7 @@ fiber_new(const char *name, fiber_func f)
  * Sic: cord()->sched needs manual destruction in
  * cord_destroy().
  */
-void
+static void
 fiber_destroy(struct cord *cord, struct fiber *f)
 {
 	if (f == fiber()) {
diff --git a/src/fiber.h b/src/fiber.h
index 4398419c9f..b6add8a25d 100644
--- a/src/fiber.h
+++ b/src/fiber.h
@@ -85,6 +85,10 @@ enum {
 	 * the fiber is recycled.
 	 */
 	FIBER_IS_DEAD		= 1 << 4,
+	/**
+	 * This flag is set when fiber uses custom stack size.
+	 */
+	FIBER_CUSTOM_STACK	= 1 << 5,
 	FIBER_DEFAULT_FLAGS = FIBER_IS_CANCELLABLE
 };
 
@@ -105,6 +109,47 @@ enum fiber_key {
 
 /** \cond public */
 
+/**
+ * Fiber attributes container
+ */
+struct fiber_attr;
+
+/**
+ * Create a new fiber attribute container and initialize it
+ * with default parameters.
+ * Can be used for many fibers creation, corresponding fibers
+ * will not take ownership.
+ */
+API_EXPORT struct fiber_attr *
+fiber_attr_new();
+
+/**
+ * Delete the fiber_attr and free all allocated resources.
+ * This is safe when fibers created with this attribute still exist.
+ *
+ *\param fiber_attr fiber attribute
+ */
+API_EXPORT void
+fiber_attr_delete(struct fiber_attr *fiber_attr);
+
+/**
+ * Set stack size for the fiber attribute.
+ *
+ * \param fiber_attribute fiber attribute container
+ * \param stacksize stack size for new fibers
+ */
+API_EXPORT int
+fiber_attr_setstacksize(struct fiber_attr *fiber_attr, size_t stack_size);
+
+/**
+ * Get stack size from the fiber attribute.
+ *
+ * \param fiber_attribute fiber attribute container or NULL for default
+ * \retval stack size
+ */
+API_EXPORT size_t
+fiber_attr_getstacksize(struct fiber_attr *fiber_attr);
+
 struct fiber;
 /**
  * Fiber - contains information about fiber
@@ -112,6 +157,12 @@ struct fiber;
 
 typedef int (*fiber_func)(va_list);
 
+/**
+ * Return the current fiber
+ */
+API_EXPORT struct fiber *
+fiber_self();
+
 /**
  * Create a new fiber.
  *
@@ -131,6 +182,25 @@ typedef int (*fiber_func)(va_list);
 API_EXPORT struct fiber *
 fiber_new(const char *name, fiber_func f);
 
+/**
+ * Create a new fiber with defined attributes.
+ *
+ * Can fail only if there is not enough memory for
+ * the fiber structure or fiber stack.
+ *
+ * The created fiber automatically returns itself
+ * to the fiber cache if has default stack size
+ * when its "main" function completes.
+ *
+ * \param name       string with fiber name
+ * \param fiber_attr fiber attributes
+ * \param fiber_func func for run inside fiber
+ *
+ * \sa fiber_start
+ */
+API_EXPORT struct fiber *
+fiber_new_ex(const char *name, const struct fiber_attr *fiber_attr, fiber_func f);
+
 /**
  * Return control to another fiber and wait until it'll be woken.
  *
@@ -244,6 +314,22 @@ cord_slab_cache(void);
 
 /** \endcond public */
 
+/**
+ * Fiber attribute container
+ */
+struct fiber_attr {
+	/** Fiber stack size. */
+	size_t stack_size;
+	/** Fiber flags. */
+	uint32_t flags;
+};
+
+/**
+ * Init fiber attr with default values
+ */
+void
+fiber_attr_create(struct fiber_attr *fiber_attr);
+
 struct fiber {
 	coro_context ctx;
 	/** Coro stack slab. */
diff --git a/test/unit/fiber.cc b/test/unit/fiber.cc
index b8341c0c8a..eee80df6ce 100644
--- a/test/unit/fiber.cc
+++ b/test/unit/fiber.cc
@@ -46,6 +46,26 @@ cancel_dead_f(va_list ap)
 	return 0;
 }
 
+static size_t fiber_stack_size_default;
+
+static void
+stack_expand(void *ptr)
+{
+	char buf[2048];
+	memset(buf, 0, 2048);
+	long int stack_diff = (long int)(buf - (char *)ptr);
+	if (abs(stack_diff) < (long int)fiber_stack_size_default)
+		stack_expand(ptr);
+}
+
+static int
+test_stack_f(va_list ap)
+{
+	char s;
+	stack_expand(&s);
+	return 0;
+}
+
 static void
 fiber_join_test()
 {
@@ -100,6 +120,22 @@ fiber_join_test()
 	fiber_cancel(fiber);
 	fiber_join(fiber);
 
+	struct fiber_attr *fiber_attr;
+	fiber_attr = fiber_attr_new();
+	fiber_stack_size_default = fiber_attr_getstacksize(fiber_attr);
+	fiber_attr_setstacksize(fiber_attr, fiber_stack_size_default * 2);
+	fiber = fiber_new_ex("test_stack", fiber_attr, test_stack_f);
+	fiber_attr_delete(fiber_attr);
+	if (fiber == NULL)
+		diag_raise();
+	fiber_set_joinable(fiber, true);
+	fiber_wakeup(fiber);
+	/** Let the fiber schedule */
+	fiber_wakeup(fiber());
+	fiber_yield();
+	note("big-stack fiber not crashed");
+	fiber_join(fiber);
+
 	footer();
 }
 
diff --git a/test/unit/fiber.result b/test/unit/fiber.result
index 3e8bd039a1..2cd3fbc23d 100644
--- a/test/unit/fiber.result
+++ b/test/unit/fiber.result
@@ -4,4 +4,5 @@ SystemError Failed to allocate 42 bytes in allocator for exception: Cannot alloc
 # exception propagated
 # cancel dead has started
 # by this time the fiber should be dead already
+# big-stack fiber not crashed
 	*** fiber_join_test: done ***
-- 
GitLab