WL#4976: Introduce timer-based InnoDB Thread Concurrency

Affects: Server-5.4   —   Status: Complete

Introduce timer-based InnoDB Thread Concurrency.
In  MySQL 5.4.0
In MySQL 5.4.0, add a new system variable: 
innodb_thread_concurrency_timer_based

innodb_thread_concurrency_timer_based: If enabled, use a lock-free timer-based
method of handling thread concurrency. If disabled, the original mutex-based
method is used. For the lock-free concurrency method to be used, two
requirements must be satisfied:
   1) The innodb_thread_concurrency system variable must be set to a number
greater than 0. The default value in MySQL 5.4 is 0, so it must be changed
to use the lock-free concurrency method.
   2) Atomic instructions must be available; that is, the
Innodb_have_sync_atomic status variable must be ON.

If innodb_thread_concurrency is 0 or Innodb_have_sync_atomic is OFF, enabling
innodb_thread_concurrency_timer_based has no effect.

Details:
   1) innodb_thread_concurrency_timer_based is a global system variable and 
does not have a session value. 
   2) The variable is not dynamic (cannot be changed at runtime). To assign 
values different from the defaults, users must set them at Server startup.
These patches introduced a new timer based innodb thread concurrency. 
A new parameter innodb_thread_concurrency_timer_based is used to
get this new feature (it is set by default). The new feature
is only available on platforms where atomic instructions are
available.

This patch introduces the timer based InnoDB Thread Concurrency.

=== modified file 'storage/innobase/handler/ha_innodb.cc'
--- storage/innobase/handler/ha_innodb.cc	2008-11-17 21:54:32 +0000
+++ storage/innobase/handler/ha_innodb.cc	2008-12-12 20:17:15 +0000
@@ -148,6 +148,9 @@ long innobase_max_merged_io = 64;
 /* Number of background IO threads for read and write. */
 long innobase_read_io_threads, innobase_write_io_threads;
 
+/* Use timer based InnoDB concurrency throttling flag */
+static my_bool innobase_thread_concurrency_timer_based;
+
 /* The following counter is used to convey information to InnoDB
 about server activity: in selects it is not sensible to call
 srv_active_wake_master_thread after each fetch or search, we only do
@@ -1602,6 +1605,9 @@ innobase_init(
 	srv_n_log_files = (ulint) innobase_log_files_in_group;
 	srv_log_file_size = (ulint) innobase_log_file_size;
 
+        srv_thread_concurrency_timer_based =
+          (ibool) innobase_thread_concurrency_timer_based;
+
 #ifdef UNIV_LOG_ARCHIVE
 	srv_log_archive_on = (ulint) innobase_log_archive;
 #endif /* UNIV_LOG_ARCHIVE */
@@ -8236,6 +8242,12 @@ static MYSQL_SYSVAR_ULONG(sync_spin_loop
   "Count of spin-loop rounds in InnoDB mutexes",
   NULL, NULL, 20L, 0L, ~0L, 0);
 
+static MYSQL_SYSVAR_BOOL(thread_concurrency_timer_based,
+                         innobase_thread_concurrency_timer_based,
+                         PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
+  "Use InnoDB timer based concurrency throttling. ",
+  NULL, NULL, TRUE);
+
 static MYSQL_SYSVAR_ULONG(thread_concurrency, srv_thread_concurrency,
   PLUGIN_VAR_RQCMDARG,
   "Helps in performance tuning in heavily concurrent environments. Sets the
maximum number of threads allowed inside InnoDB. Value 0 will disable the thread
throttling.",
@@ -8278,6 +8290,7 @@ static struct st_mysql_sys_var* innobase
   MYSQL_SYSVAR(read_io_threads),
   MYSQL_SYSVAR(write_io_threads),
   MYSQL_SYSVAR(max_merged_io),
+  MYSQL_SYSVAR(thread_concurrency_timer_based),
   MYSQL_SYSVAR(file_per_table),
   MYSQL_SYSVAR(flush_log_at_trx_commit),
   MYSQL_SYSVAR(flush_method),

=== modified file 'storage/innobase/include/srv0srv.h'
--- storage/innobase/include/srv0srv.h	2008-11-17 21:54:32 +0000
+++ storage/innobase/include/srv0srv.h	2008-12-12 20:17:15 +0000
@@ -89,6 +89,8 @@ extern ulint	srv_awe_window_size;
 extern ulint	srv_mem_pool_size;
 extern ulint	srv_lock_table_size;
 
+extern ibool    srv_thread_concurrency_timer_based;
+
 extern ulint	srv_n_file_io_threads;
 /* Number of background IO threads for read and write. Replaces
  * srv_n_file_io_threads. */

=== modified file 'storage/innobase/srv/srv0srv.c'
--- storage/innobase/srv/srv0srv.c	2008-12-10 17:54:23 +0000
+++ storage/innobase/srv/srv0srv.c	2008-12-12 20:17:15 +0000
@@ -171,6 +171,7 @@ ulint	srv_awe_window_size	= 0;		/* size 
 ulint	srv_mem_pool_size	= ULINT_MAX;	/* size in bytes */
 ulint	srv_lock_table_size	= ULINT_MAX;
 
+
 ulint   srv_io_capacity         = ULINT_MAX;    /* Number of IO operations per
                                                    second the server can do */
 
@@ -288,19 +289,20 @@ Value 10 should be good if there are les
 computer. Bigger computers need bigger values. Value 0 will disable the
 concurrency check. */
 
+ibool   srv_thread_concurrency_timer_based = TRUE;
 ulong	srv_thread_concurrency	= 0;
 ulong	srv_commit_concurrency	= 0;
 
 os_fast_mutex_t	srv_conc_mutex;		/* this mutex protects srv_conc data
 					structures */
-lint	srv_conc_n_threads	= 0;	/* number of OS threads currently
+lint	srv_conc_n_threads	= 0;    /* number of OS threads currently
 					inside InnoDB; it is not an error
 					if this drops temporarily below zero
 					because we do not demand that every
 					thread increments this, but a thread
 					waiting for a lock decrements this
 					temporarily */
-ulint	srv_conc_n_waiting_threads = 0;	/* number of OS threads waiting in the
+ulint	srv_conc_n_waiting_threads = 0; /* number of OS threads waiting in the
 					FIFO for a permission to enter InnoDB
 					*/
 
@@ -1061,6 +1063,91 @@ ulong	srv_max_purge_lag		= 0;
 Puts an OS thread to wait if there are too many concurrent threads
 (>= srv_thread_concurrency) inside InnoDB. The threads wait in a FIFO queue. */
 
+static void
+inc_srv_conc_n_threads(lint *n_threads)
+{
+  *n_threads = os_atomic_increment(&srv_conc_n_threads, 1);
+}
+
+static void
+dec_srv_conc_n_threads()
+{
+  os_atomic_increment(&srv_conc_n_threads, -1);
+}
+
+static void
+print_already_in_error(trx_t* trx)
+{
+	ut_print_timestamp(stderr);
+	fputs("  InnoDB: Error: trying to declare trx"
+	      " to enter InnoDB, but\n"
+	      "InnoDB: it already is declared.\n", stderr);
+	trx_print(stderr, trx, 0);
+	putc('\n', stderr);
+        return;
+}
+
+static void
+enter_innodb_with_tickets(trx_t* trx)
+{
+	trx->declared_to_be_inside_innodb = TRUE;
+	trx->n_tickets_to_enter_innodb = SRV_FREE_TICKETS_TO_ENTER;
+        return;
+}
+
+static void
+srv_conc_enter_innodb_timer_based(trx_t* trx)
+{
+        lint               conc_n_threads;
+        ibool              has_yielded = FALSE;
+        ulint              has_slept = 0;
+
+	if (trx->declared_to_be_inside_innodb) {
+                print_already_in_error(trx);
+        }
+retry:
+	if (srv_conc_n_threads < (lint) srv_thread_concurrency) {
+                inc_srv_conc_n_threads(&conc_n_threads);
+	        if (conc_n_threads <= srv_thread_concurrency) {
+                       enter_innodb_with_tickets(trx);
+                       return;
+                }
+                dec_srv_conc_n_threads(&conc_n_threads);
+       }
+       if (!has_yielded)
+       {
+               has_yielded = TRUE;
+               os_thread_yield();
+               goto retry;
+       }
+       if (trx->has_search_latch
+           || NULL != UT_LIST_GET_FIRST(trx->trx_locks)) {
+
+                inc_srv_conc_n_threads(&conc_n_threads);
+                enter_innodb_with_tickets(trx);
+                return;
+       }
+       if (has_slept < 2)
+       {
+               trx->op_info = "sleeping before entering InnoDB";
+               os_thread_sleep(10000);
+               trx->op_info = "";
+               has_slept++;
+       }
+       inc_srv_conc_n_threads(&conc_n_threads);
+       enter_innodb_with_tickets(trx);
+       return;
+}
+
+static void
+srv_conc_exit_innodb_timer_based(trx_t* trx)
+{
+        dec_srv_conc_n_threads();
+	trx->declared_to_be_inside_innodb = FALSE;
+	trx->n_tickets_to_enter_innodb = 0;
+        return;
+}
+
 void
 srv_conc_enter_innodb(
 /*==================*/
@@ -1091,15 +1178,17 @@ srv_conc_enter_innodb(
 		return;
 	}
 
+#ifdef UNIV_SYNC_ATOMIC
+        if (srv_thread_concurrency_timer_based) {
+          srv_conc_enter_innodb_timer_based(trx);
+          return;
+        }
+#endif
+
 	os_fast_mutex_lock(&srv_conc_mutex);
 retry:
 	if (trx->declared_to_be_inside_innodb) {
-		ut_print_timestamp(stderr);
-		fputs("  InnoDB: Error: trying to declare trx"
-		      " to enter InnoDB, but\n"
-		      "InnoDB: it already is declared.\n", stderr);
-		trx_print(stderr, trx, 0);
-		putc('\n', stderr);
+                print_already_in_error(trx);
 		os_fast_mutex_unlock(&srv_conc_mutex);
 
 		return;
@@ -1226,17 +1315,25 @@ srv_conc_force_enter_innodb(
 	trx_t*	trx)	/* in: transaction object associated with the
 			thread */
 {
+        lint               conc_n_threads;
+
 	if (UNIV_LIKELY(!srv_thread_concurrency)) {
 
 		return;
 	}
 
+#ifdef UNIV_SYNC_ATOMIC
+        if (srv_thread_concurrency_timer_based) {
+                inc_srv_conc_n_threads(&conc_n_threads);
+	        trx->declared_to_be_inside_innodb = TRUE;
+	        trx->n_tickets_to_enter_innodb = 1;
+                return;
+        }
+#endif
 	os_fast_mutex_lock(&srv_conc_mutex);
-
 	srv_conc_n_threads++;
 	trx->declared_to_be_inside_innodb = TRUE;
 	trx->n_tickets_to_enter_innodb = 1;
-
 	os_fast_mutex_unlock(&srv_conc_mutex);
 }
 
@@ -1268,6 +1365,14 @@ srv_conc_force_exit_innodb(
 		return;
 	}
 
+#ifdef UNIV_SYNC_ATOMIC
+        if (srv_thread_concurrency_timer_based)
+        {
+                srv_conc_exit_innodb_timer_based(trx);
+                return;
+        }
+#endif
+
 	os_fast_mutex_lock(&srv_conc_mutex);
 
 	srv_conc_n_threads--;