List:Commits« Previous MessageNext Message »
From:ahristov Date:April 20 2006 3:16pm
Subject:bk commit into 5.1 tree (andrey:1.2235) BUG#17619
View as plain text  
Below is the list of changes that have just been committed into a local
5.1 repository of andrey. When andrey does a push these changes will
be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html

ChangeSet
  1.2235 06/04/20 18:16:11 andrey@lmy004. +27 -0
  Fix for bug#17619 (Scheduler race conditions)
  This is preliminary patch intended to be reviewed.
  - Inlcudes full integration, even DROP DATABASE
  - With race condition of evex_create_event() + load_events_from_db() running
    in parallel. This will be addressed.
  - With renamed enums to shorten their names.
  - Not including basic + full initialization , will come in the next patch

  sql/event_scheduler.h
    1.1 06/04/20 18:16:01 andrey@lmy004. +311 -0

  sql/event_scheduler.h
    1.0 06/04/20 18:16:01 andrey@lmy004. +0 -0
    BitKeeper file /work/mysql-5.1-bug17619-new/sql/event_scheduler.h

  sql/event_scheduler.cc
    1.1 06/04/20 18:16:00 andrey@lmy004. +2186 -0

  sql/table.cc
    1.218 06/04/20 18:16:00 andrey@lmy004. +8 -2
    fix valgrind error

  sql/sql_yacc.yy
    1.485 06/04/20 18:16:00 andrey@lmy004. +10 -0
    add SHOW TEST_EVENTS, for internal testing

  sql/sql_show.cc
    1.322 06/04/20 18:16:00 andrey@lmy004. +4 -7
    fix problem with show events, for the tests

  sql/sql_parse.cc
    1.535 06/04/20 18:16:00 andrey@lmy004. +31 -5
    refactor kill_one_thread -> sql_kill calls kill_one_thread, which do not
    emit any error, but sql_kill does. This makes kill_one_thread more generic.

  sql/sql_lex.h
    1.225 06/04/20 18:16:00 andrey@lmy004. +1 -1
    new command for SHOW TEST_EVENTS

  sql/event_scheduler.cc
    1.0 06/04/20 18:16:00 andrey@lmy004. +0 -0
    BitKeeper file /work/mysql-5.1-bug17619-new/sql/event_scheduler.cc

  sql/sql_db.cc
    1.134 06/04/20 18:15:59 andrey@lmy004. +2 -1
    get the number of dropped events

  sql/share/errmsg.txt
    1.91 06/04/20 18:15:59 andrey@lmy004. +4 -0
    error message for the case there was an error during add to memory queue
    error message if there was an error during start/stop of the scheduler

  sql/set_var.cc
    1.181 06/04/20 18:15:59 andrey@lmy004. +32 -1
    - use the static member manager_working of Event_scheduler class,
    capsulation is better

  sql/mysqld.cc
    1.550 06/04/20 18:15:59 andrey@lmy004. +2 -2
    rename init/shutdown_events() to events_init() and events_shutdown.
    Don't init events if --skip-grant-tables, in this case the scheduler won't work
    but the one will be able to use DDL.

  sql/mysql_priv.h
    1.388 06/04/20 18:15:59 andrey@lmy004. +2 -1
    let kill_one_thread() return whether there was an error or not

  sql/lex.h
    1.157 06/04/20 18:15:59 andrey@lmy004. +1 -0
    new keyword to use with SHOW TEST_EVENTS to test events internally

  sql/event_timed.cc
    1.48 06/04/20 18:15:59 andrey@lmy004. +144 -24
    - Event_timed::kill_thread()
    - event_timed_name_equal
    - event_timed_db_equal
    - event_timed_definer_equal
    - event_timed_identifier_equal

  sql/event_priv.h
    1.21 06/04/20 18:15:59 andrey@lmy004. +11 -37
    export db_find_event() and db_create_event()

  sql/event.h
    1.28 06/04/20 18:15:59 andrey@lmy004. +41 -39
    move sortcmp_lex_string() so it is visible

  sql/event.cc
    1.38 06/04/20 18:15:59 andrey@lmy004. +149 -416
    - export db_create_event() and db_find_event()
    - integrate the code with the new scheduler

  sql/cmakelists.txt
    1.12 06/04/20 18:15:59 andrey@lmy004. +1 -1
    add a new file event_scheduler.cc to the build

  sql/Makefile.am
    1.134 06/04/20 18:15:59 andrey@lmy004. +3 -3
    add a new file event_scheduler.cc to the build

  mysql-test/t/events_stress.test
    1.2 06/04/20 18:15:59 andrey@lmy004. +76 -34
    rework the stress test

  mysql-test/t/events.test
    1.25 06/04/20 18:15:59 andrey@lmy004. +1 -11
    update test (shorten it)

  mysql-test/t/disabled.def
    1.113 06/04/20 18:15:59 andrey@lmy004. +3 -3
    enable these for testing

  mysql-test/r/events_stress.result
    1.2 06/04/20 18:15:59 andrey@lmy004. +41 -26
    update results

  mysql-test/r/events_bugs.result
    1.7 06/04/20 18:15:58 andrey@lmy004. +3 -3
    update results

  mysql-test/r/events.result
    1.30 06/04/20 18:15:58 andrey@lmy004. +2 -10
    update results

  libmysqld/Makefile.am
    1.81 06/04/20 18:15:58 andrey@lmy004. +1 -1
    event_executor.cc is no more

  BitKeeper/etc/ignore
    1.234 06/04/20 18:15:58 andrey@lmy004. +2 -0
    ignore these

# This is a BitKeeper patch.  What follows are the unified diffs for the
# set of deltas contained in the patch.  The rest of the patch, the part
# that BitKeeper cares about, is below these diffs.
# User:	andrey
# Host:	lmy004.
# Root:	/work/mysql-5.1-bug17619-new

--- 1.133/sql/Makefile.am	2006-03-20 21:16:49 +02:00
+++ 1.134/sql/Makefile.am	2006-04-20 18:15:59 +03:00
@@ -65,8 +65,8 @@ noinst_HEADERS =	item.h item_func.h item
 			sp_head.h sp_pcontext.h sp_rcontext.h sp.h sp_cache.h \
 			parse_file.h sql_view.h	sql_trigger.h \
 			sql_array.h sql_cursor.h event.h event_priv.h \
-			sql_plugin.h authors.h sql_partition.h \
-                        partition_info.h partition_element.h
+			event_scheduler.h sql_plugin.h authors.h sql_partition.h \
+                        partition_info.h partition_element.h event_scheduler.h
 mysqld_SOURCES =	sql_lex.cc sql_handler.cc sql_partition.cc \
 			item.cc item_sum.cc item_buff.cc item_func.cc \
 			item_cmpfunc.cc item_strfunc.cc item_timefunc.cc \
@@ -100,7 +100,7 @@ mysqld_SOURCES =	sql_lex.cc sql_handler.
 			tztime.cc my_time.c my_user.c my_decimal.cc\
 			sp_head.cc sp_pcontext.cc  sp_rcontext.cc sp.cc \
 			sp_cache.cc parse_file.cc sql_trigger.cc \
-                        event_executor.cc event.cc event_timed.cc \
+                        event.cc event_timed.cc event_scheduler.cc\
 			sql_plugin.cc sql_binlog.cc \
 			handlerton.cc sql_tablespace.cc partition_info.cc
 EXTRA_mysqld_SOURCES =	ha_innodb.cc ha_berkeley.cc ha_archive.cc \

--- 1.156/sql/lex.h	2006-03-20 21:36:13 +02:00
+++ 1.157/sql/lex.h	2006-04-20 18:15:59 +03:00
@@ -521,6 +521,7 @@ static SYMBOL symbols[] = {
   { "TEMPORARY",	SYM(TEMPORARY)},
   { "TEMPTABLE",	SYM(TEMPTABLE_SYM)},
   { "TERMINATED",	SYM(TERMINATED)},
+  { "TEST_EVENTS",	SYM(TEST_EVENTS_SYM)},
   { "TEXT",		SYM(TEXT_SYM)},
   { "THAN",             SYM(THAN_SYM)},
   { "THEN",		SYM(THEN_SYM)},

--- 1.387/sql/mysql_priv.h	2006-03-22 08:57:50 +02:00
+++ 1.388/sql/mysql_priv.h	2006-04-20 18:15:59 +03:00
@@ -83,7 +83,8 @@ char *sql_strmake_with_convert(const cha
 			       CHARSET_INFO *from_cs,
 			       uint32 max_res_length,
 			       CHARSET_INFO *to_cs, uint32 *result_length);
-void kill_one_thread(THD *thd, ulong id, bool only_kill_query);
+uint kill_one_thread(THD *thd, ulong id, bool only_kill_query);
+void sql_kill(THD *thd, ulong id, bool only_kill_query);
 bool net_request_file(NET* net, const char* fname);
 char* query_table_status(THD *thd,const char *db,const char *table_name);
 

--- 1.549/sql/mysqld.cc	2006-03-21 21:05:49 +02:00
+++ 1.550/sql/mysqld.cc	2006-04-20 18:15:59 +03:00
@@ -1165,6 +1165,7 @@ void clean_up(bool print_message)
   (void) ha_panic(HA_PANIC_CLOSE);	/* close all tables and logs */
   if (!opt_noacl)
   {
+    events_shutdown();
 #ifdef HAVE_DLOPEN
     udf_free();
 #endif
@@ -3053,7 +3054,6 @@ static int init_server_components()
 #ifdef HAVE_REPLICATION
   init_slave_list();
 #endif
-  init_events();
 
   /* Setup logs */
 
@@ -3630,6 +3630,7 @@ we force server id to 2, but this MySQL 
 
   if (!opt_noacl)
   {
+    events_init();
     plugin_load();
 #ifdef HAVE_DLOPEN
     udf_init();
@@ -3729,7 +3730,6 @@ we force server id to 2, but this MySQL 
   clean_up(1);
   wait_for_signal_thread_to_end();
   clean_up_mutexes();
-  shutdown_events();
   my_end(opt_endinfo ? MY_CHECK_ERROR | MY_GIVE_INFO : 0);
 
   exit(0);

--- 1.133/sql/sql_db.cc	2006-03-02 17:24:57 +02:00
+++ 1.134/sql/sql_db.cc	2006-04-20 18:15:59 +03:00
@@ -749,6 +749,7 @@ bool mysql_rm_db(THD *thd,char *db,bool 
 {
   long deleted=0;
   int error= 0;
+  uint dropped_events= 0;
   char	path[FN_REFLEN+16];
   MY_DIR *dirp;
   uint length;
@@ -871,7 +872,7 @@ bool mysql_rm_db(THD *thd,char *db,bool 
 
 exit:
   (void)sp_drop_db_routines(thd, db); /* QQ Ignore errors for now  */
-  (void)evex_drop_db_events(thd, db); /* QQ Ignore errors for now  */
+  error= evex_drop_schema_events(thd, db, &dropped_events);
   start_waiting_global_read_lock(thd);
   /*
     If this database was the client's selected database, we silently change the

--- 1.224/sql/sql_lex.h	2006-03-20 21:39:47 +02:00
+++ 1.225/sql/sql_lex.h	2006-04-20 18:16:00 +03:00
@@ -111,7 +111,7 @@ enum enum_sql_command {
   SQLCOM_SHOW_AUTHORS, SQLCOM_BINLOG_BASE64_EVENT,
   SQLCOM_SHOW_PLUGINS,
   SQLCOM_CREATE_EVENT, SQLCOM_ALTER_EVENT, SQLCOM_DROP_EVENT,
-  SQLCOM_SHOW_CREATE_EVENT, SQLCOM_SHOW_EVENTS,
+  SQLCOM_SHOW_CREATE_EVENT, SQLCOM_SHOW_EVENTS, SQLCOM_SHOW_EVENTS_TEST,
 
   /* This should be the last !!! */
 

--- 1.534/sql/sql_parse.cc	2006-03-21 14:10:09 +02:00
+++ 1.535/sql/sql_parse.cc	2006-04-20 18:16:00 +03:00
@@ -2030,7 +2030,7 @@ bool dispatch_command(enum enum_server_c
   {
     statistic_increment(thd->status_var.com_stat[SQLCOM_KILL], &LOCK_status);
     ulong id=(ulong) uint4korr(packet);
-    kill_one_thread(thd,id,false);
+    sql_kill(thd,id,false);
     break;
   }
   case COM_SET_OPTION:
@@ -3860,6 +3860,13 @@ end_with_restore_list:
     res= evex_show_create_event(thd, lex->spname, lex->et->definer);
     break;
   }
+#ifndef DBUG_OFF
+  case SQLCOM_SHOW_EVENTS_TEST:
+  {
+    res= Event_scheduler::tests_run(thd);
+    break;
+  }
+#endif
   case SQLCOM_CREATE_FUNCTION:                  // UDF function
   {
     if (check_access(thd,INSERT_ACL,"mysql",0,1,0,0))
@@ -4099,7 +4106,7 @@ end_with_restore_list:
 		 MYF(0));
       goto error;
     }
-    kill_one_thread(thd, (ulong)it->val_int(), lex->type & ONLY_KILL_QUERY);
+    sql_kill(thd, (ulong)it->val_int(), lex->type & ONLY_KILL_QUERY);
     break;
   }
 #ifndef NO_EMBEDDED_ACCESS_CHECKS
@@ -6860,19 +6867,21 @@ bool reload_acl_and_cache(THD *thd, ulon
  return result;
 }
 
+
 /*
-  kill on thread
+  kills a thread
 
   SYNOPSIS
     kill_one_thread()
     thd			Thread class
     id			Thread id
+    only_kill_query     Should it kill the query or the connection
 
   NOTES
     This is written such that we have a short lock on LOCK_thread_count
 */
 
-void kill_one_thread(THD *thd, ulong id, bool only_kill_query)
+uint kill_one_thread(THD *thd, ulong id, bool only_kill_query)
 {
   THD *tmp;
   uint error=ER_NO_SUCH_THREAD;
@@ -6902,7 +6911,24 @@ void kill_one_thread(THD *thd, ulong id,
     pthread_mutex_unlock(&tmp->LOCK_delete);
   }
 
-  if (!error)
+  return error;
+}
+
+
+/*
+  kills a thread and sends response
+
+  SYNOPSIS
+    sql_kill()
+    thd			Thread class
+    id			Thread id
+    only_kill_query     Should it kill the query or the connection
+*/
+
+void sql_kill(THD *thd, ulong id, bool only_kill_query)
+{
+  uint error;
+  if (!(error= kill_one_thread(thd, id, only_kill_query)))
     send_ok(thd);
   else
     my_error(error, MYF(0), id);

--- 1.321/sql/sql_show.cc	2006-03-21 14:10:10 +02:00
+++ 1.322/sql/sql_show.cc	2006-04-20 18:16:00 +03:00
@@ -4095,13 +4095,10 @@ int fill_schema_events(THD *thd, TABLE_L
   
   event_table->file->ha_index_init(0, 1);
 
-  /* 
-    see others' events only if you have PROCESS_ACL !!
-    thd->lex->verbose is set either if SHOW FULL EVENTS or
-    in case of SELECT FROM I_S.EVENTS
-  */
-  verbose= (thd->lex->verbose
-            && (thd->security_ctx->master_access & PROCESS_ACL));
+  /* see others' events only if you have PROCESS_ACL !! */
+  verbose= ((thd->lex->verbose ||
+             thd->lex->orig_sql_command != SQLCOM_SHOW_EVENTS) &&
+            (thd->security_ctx->master_access & PROCESS_ACL));
 
   if (verbose && thd->security_ctx->user)
   {    

--- 1.484/sql/sql_yacc.yy	2006-03-21 14:10:10 +02:00
+++ 1.485/sql/sql_yacc.yy	2006-04-20 18:16:00 +03:00
@@ -630,6 +630,7 @@ bool my_yyoverflow(short **a, YYSTYPE **
 %token  TEMPORARY
 %token  TEMPTABLE_SYM
 %token  TERMINATED
+%token  TEST_EVENTS_SYM
 %token  TEXT_STRING
 %token  TEXT_SYM
 %token  TIMESTAMP
@@ -8150,6 +8151,15 @@ show_param:
              if (prepare_schema_table(YYTHD, lex, 0, SCH_EVENTS))
                YYABORT;
            }
+         | TEST_EVENTS_SYM
+           {
+#ifndef DBUG_OFF
+             Lex->sql_command= SQLCOM_SHOW_EVENTS_TEST;
+#else
+             yyerror(ER(ER_SYNTAX_ERROR));
+             YYABORT;           
+#endif
+           }         
          | TABLE_SYM STATUS_SYM opt_db wild_and_where
            {
              LEX *lex= Lex;

--- 1.217/sql/table.cc	2006-03-22 13:10:33 +02:00
+++ 1.218/sql/table.cc	2006-04-20 18:16:00 +03:00
@@ -2368,6 +2368,12 @@ table_check_intact(TABLE *table, uint ta
     for (i=0 ;i < table_f_count; ++i, ++table_def)
     {      
       Field *field= table->field[i];
+      /*
+        QQ : Maybe use set(const char*, uint32, CHARSET_INFO) instead of
+             constructing a new object on the stack on every iteration?
+             The buffer is although big enough so String does not do any
+             realloc().
+      */
       String sql_type(buffer, sizeof(buffer), system_charset_info);
       sql_type.length(0);
       /*
@@ -2395,12 +2401,12 @@ table_check_intact(TABLE *table, uint ta
              table running on a old server will be valid.
         */ 
         field->sql_type(sql_type);
-        if (strncmp(sql_type.c_ptr(), table_def->type.str,
+        if (strncmp(sql_type.c_ptr_safe(), table_def->type.str,
                     table_def->type.length - 1))
         {
           sql_print_error("(%s) Expected field %s at position %d to have type "
                           "%s, found %s", table->alias, table_def->name.str,
-                          i, table_def->type.str, sql_type.c_ptr()); 
+                          i, table_def->type.str, sql_type.c_ptr_safe()); 
           error= TRUE;
         }
         else if (table_def->cset.str && !field->has_charset())

--- 1.29/mysql-test/r/events.result	2006-03-24 18:48:43 +02:00
+++ 1.30/mysql-test/r/events.result	2006-04-20 18:15:58 +03:00
@@ -260,10 +260,8 @@ ALTER TABLE mysql.event MODIFY db char(6
 "This should work"
 SHOW EVENTS;
 Db	Name	Definer	Type	Execute at	Interval value	Interval field	Starts	Ends	Status
-events_test                                                 	intact_check	root@localhost	RECURRING	NULL	10	HOUR	#	#	ENABLED
+events_test	intact_check	root@localhost	RECURRING	NULL	10	HOUR	#	#	ENABLED
 ALTER TABLE mysql.event MODIFY db char(64) character set cp1251 default '';
-Warnings:
-Warning	1265	Data truncated for column 'db' at row 1
 SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
 ERROR HY000: Cannot load from mysql.event. Table probably corrupted. See error log.
 ALTER TABLE mysql.event MODIFY db varchar(64) character set utf8 collate utf8_bin default '';
@@ -425,19 +423,13 @@ select get_lock("test_lock2_1", 20);
 get_lock("test_lock2_1", 20)
 1
 create event закачка21 on schedule every 10 hour do select get_lock("test_lock2_1", 20);
-"Should see 2 processes, one locked on get_lock("
-"Shutting down the scheduler, it should wait for the running event"
-set global event_scheduler=0;
 "Should have only 3 processes: the scheduler, our conn and the locked event"
 show processlist;
 Id	User	Host	db	Command	Time	State	Info
 #	root	localhost	events_test	Query	#	NULL	show processlist
 #	event_scheduler	localhost	NULL	Connect	#	Sleeping	NULL
 #	root	localhost	events_test	Connect	#	User lock	select get_lock("test_lock2_1", 20)
-"Release the lock so the child process should finish. Hence the scheduler also"
-select release_lock("test_lock2_1");
-release_lock("test_lock2_1")
-1
+set global event_scheduler=0;
 "Should have only our process now:"
 show processlist;
 Id	User	Host	db	Command	Time	State	Info

--- 1.6/mysql-test/r/events_bugs.result	2006-03-24 18:58:19 +02:00
+++ 1.7/mysql-test/r/events_bugs.result	2006-04-20 18:15:58 +03:00
@@ -5,10 +5,10 @@ CREATE EVENT Lower_case ON SCHEDULE EVER
 ERROR HY000: Event 'Lower_case' already exists
 DROP EVENT Lower_case;
 SET NAMES cp1251;
-CREATE EVENT äîëåí_ðåãèñòúð_1251 ON SCHEDULE EVERY 1 YEAR DO SELECT 100;
-CREATE EVENT ÄîËåÍ_ðåãèñòúð_1251 ON SCHEDULE EVERY 2 YEAR DO SELECT 200;
 ERROR HY000: Event 'ДоЛеН_регистър_1251' already exists
-DROP EVENT ÄîËåÍ_ðåãèñòúð_1251;
 SET NAMES utf8;
 CREATE EVENT долен_регистър_утф8 ON SCHEDULE EVERY 3 YEAR DO SELECT 300;
 CREATE EVENT ДОЛЕН_регистър_утф8 ON SCHEDULE EVERY 4 YEAR DO SELECT 400;

--- 1.1/mysql-test/r/events_stress.result	2006-02-16 01:43:03 +02:00
+++ 1.2/mysql-test/r/events_stress.result	2006-04-20 18:15:59 +03:00
@@ -1,46 +1,61 @@
 CREATE DATABASE IF NOT EXISTS events_test;
-CREATE DATABASE events_test2;
-USE events_test2;
+CREATE DATABASE events_conn1_test2;
+CREATE TABLE events_test.fill_it(test_name varchar(20), occur datetime);
+CREATE USER event_user2@localhost;
+CREATE DATABASE events_conn2_db;
+GRANT ALL ON *.* TO event_user2@localhost;
+CREATE USER event_user3@localhost;
+CREATE DATABASE events_conn3_db;
+GRANT ALL ON *.* TO event_user3@localhost;
+"In the second connection we create some events which won't be dropped till the end"
+"In the second connection we create some events which won't be dropped till the end"
+USE events_conn1_test2;
 CREATE EVENT ev_drop1 ON SCHEDULE EVERY 10 MINUTE DISABLE DO SELECT 1;
 CREATE EVENT ev_drop2 ON SCHEDULE EVERY 10 MINUTE DISABLE DO SELECT 1;
 CREATE EVENT ev_drop3 ON SCHEDULE EVERY 10 MINUTE DISABLE DO SELECT 1;
 USE events_test;
-SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2';
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS;
+COUNT(*)
+603
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2';
 COUNT(*)
 3
-DROP DATABASE events_test2;
-SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2';
+DROP DATABASE events_conn1_test2;
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2';
 COUNT(*)
 0
 "Now testing stability - dropping db -> events while they are running"
-CREATE DATABASE events_test2;
-USE events_test2;
-SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2';
+CREATE DATABASE events_conn1_test2;
+USE events_conn1_test2;
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2';
 COUNT(*)
-1000
+500
 SET GLOBAL event_scheduler=1;
-DROP DATABASE events_test2;
+DROP DATABASE events_conn1_test2;
 SET GLOBAL event_scheduler=0;
-SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2';
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2';
 COUNT(*)
 0
-CREATE DATABASE events_test3;
-USE events_test3;
-SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test3';
-COUNT(*)
-950
-CREATE DATABASE events_test4;
-USE events_test4;
-CREATE DATABASE events_test2;
-USE events_test2;
-SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2';
+CREATE DATABASE events_conn1_test3;
+USE events_conn1_test3;
+SET GLOBAL event_scheduler=1;
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test3';
 COUNT(*)
-1050
-DROP DATABASE events_test2;
+600
+CREATE DATABASE events_conn1_test4;
+USE events_conn1_test4;
+CREATE DATABASE events_conn1_test2;
+USE events_conn1_test2;
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2';
+COUNT(*)
+400
+DROP DATABASE events_conn2_db;
+DROP DATABASE events_conn3_db;
+DROP DATABASE events_conn1_test2;
+DROP DATABASE events_conn1_test3;
 SET GLOBAL event_scheduler=0;
-DROP DATABASE events_test3;
-SET GLOBAL event_scheduler=1;
-DROP DATABASE events_test4;
+DROP DATABASE events_conn1_test4;
 SET GLOBAL event_scheduler=1;
 USE events_test;
+DROP TABLE fill_it;
 DROP DATABASE events_test;

--- 1.24/mysql-test/t/events.test	2006-03-17 12:01:35 +02:00
+++ 1.25/mysql-test/t/events.test	2006-04-20 18:15:59 +03:00
@@ -19,7 +19,6 @@ SET GLOBAL event_scheduler=1;
 SHOW DATABASES LIKE 'db_x';
 SHOW TABLES FROM db_x;
 SET GLOBAL event_scheduler=0;
---sleep 1
 connection priv_conn;
 DROP EVENT e_x1;
 DROP EVENT e_x2;
@@ -32,7 +31,6 @@ USE events_test;
 # END:    BUG #17289 Events: missing privilege check for drop database
 #
 SET GLOBAL event_scheduler=0;
---sleep 1
 drop event if exists event1;
 create event event1 on schedule every 15 minute starts now() ends date_add(now(), interval 5 hour) DO begin end;
 alter event event1 rename to event2 enable;
@@ -378,18 +376,10 @@ set global event_scheduler=1;
 select get_lock("test_lock2_1", 20);
 create event закачка21 on schedule every 10 hour do select get_lock("test_lock2_1", 20);
 --sleep 1
---echo "Should see 2 processes, one locked on get_lock("
-#--replace_column 1 # 6 #
-#show processlist;
---echo "Shutting down the scheduler, it should wait for the running event"
-set global event_scheduler=0;
---sleep 1
 --echo "Should have only 3 processes: the scheduler, our conn and the locked event"
 --replace_column 1 # 6 #
 show processlist;
---echo "Release the lock so the child process should finish. Hence the scheduler also"
-select release_lock("test_lock2_1");
---sleep 1
+set global event_scheduler=0;
 --echo "Should have only our process now:"
 --replace_column 1 # 6 #
 show processlist;

--- 1.1/mysql-test/t/events_stress.test	2006-02-16 01:43:03 +02:00
+++ 1.2/mysql-test/t/events_stress.test	2006-04-20 18:15:59 +03:00
@@ -2,78 +2,120 @@ CREATE DATABASE IF NOT EXISTS events_tes
 #
 # DROP DATABASE test start (bug #16406)
 #
-CREATE DATABASE events_test2;
-USE events_test2;
+CREATE DATABASE events_conn1_test2;
+CREATE TABLE events_test.fill_it(test_name varchar(20), occur datetime);
+CREATE USER event_user2@localhost;
+CREATE DATABASE events_conn2_db;
+GRANT ALL ON *.* TO event_user2@localhost;
+CREATE USER event_user3@localhost;
+CREATE DATABASE events_conn3_db;
+GRANT ALL ON *.* TO event_user3@localhost;
+connect (conn2,localhost,event_user2,,events_conn2_db);
+--echo "In the second connection we create some events which won't be dropped till the end"
+--disable_query_log
+let $1= 300;
+while ($1)
+{
+  eval CREATE EVENT conn2_ev$1 ON SCHEDULE EVERY 1 SECOND DO INSERT INTO events_test.fill_it VALUES("conn2_ev$1", NOW());
+  dec $1;
+}
+--enable_query_log
+connect (conn3,localhost,event_user3,,events_conn3_db);
+--echo "In the second connection we create some events which won't be dropped till the end"
+--disable_query_log
+let $1= 300;
+while ($1)
+{
+  eval CREATE EVENT conn3_ev$1 ON SCHEDULE EVERY 1 SECOND DO INSERT INTO events_test.fill_it VALUES("conn3_ev$1", NOW());
+  dec $1;
+}
+--enable_query_log
+connection default;
+USE events_conn1_test2;
 CREATE EVENT ev_drop1 ON SCHEDULE EVERY 10 MINUTE DISABLE DO SELECT 1;
 CREATE EVENT ev_drop2 ON SCHEDULE EVERY 10 MINUTE DISABLE DO SELECT 1;
 CREATE EVENT ev_drop3 ON SCHEDULE EVERY 10 MINUTE DISABLE DO SELECT 1;
 USE events_test;
-SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2';
-DROP DATABASE events_test2;
-SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2';
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS;
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2';
+DROP DATABASE events_conn1_test2;
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2';
 
 --echo "Now testing stability - dropping db -> events while they are running"
-CREATE DATABASE events_test2;
-USE events_test2;
+CREATE DATABASE events_conn1_test2;
+USE events_conn1_test2;
 --disable_query_log
-let $1= 1000;
+let $1= 500;
 while ($1)
 {
-  eval CREATE EVENT ev_drop$1 ON SCHEDULE EVERY 1 SECOND DO SELECT $1;
+  eval CREATE EVENT conn1_round1_ev$1 ON SCHEDULE EVERY 1 SECOND DO INSERT INTO events_test.fill_it VALUES("conn1_round1_ev$1", NOW());
   dec $1;
 }
 --enable_query_log
-SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2';
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2';
 SET GLOBAL event_scheduler=1;
---sleep 4
-DROP DATABASE events_test2;
+--sleep 6
+DROP DATABASE events_conn1_test2;
 
 SET GLOBAL event_scheduler=0;
---sleep 2
-SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2';
-CREATE DATABASE events_test3;
-USE events_test3;
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2';
+CREATE DATABASE events_conn1_test3;
+USE events_conn1_test3;
 --disable_query_log
-let $1= 950;
+let $1= 600;
 while ($1)
 {
-  eval CREATE EVENT ev_drop$1 ON SCHEDULE EVERY 1 SECOND DO SELECT $1;
+  eval CREATE EVENT conn1_round2_ev$1 ON SCHEDULE EVERY 1 SECOND DO INSERT INTO events_test.fill_it VALUES("conn1_round2_ev$1", NOW());
   dec $1;
 }
 --enable_query_log
-SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test3';
---sleep 3
-CREATE DATABASE events_test4;
-USE events_test4;
+SET GLOBAL event_scheduler=1;
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test3';
+CREATE DATABASE events_conn1_test4;
+USE events_conn1_test4;
 --disable_query_log
-let $1= 860;
+let $1= 300;
 while ($1)
 {
-  eval CREATE EVENT ev_drop$1 ON SCHEDULE EVERY 1 SECOND DO SELECT $1;
+  eval CREATE EVENT conn1_round3_ev$1 ON SCHEDULE EVERY 1 SECOND DO INSERT INTO events_test.fill_it VALUES("conn1_round3_ev$1", NOW());
   dec $1;
 }
 --enable_query_log
 
-
-CREATE DATABASE events_test2;
-USE events_test2;
+CREATE DATABASE events_conn1_test2;
+USE events_conn1_test2;
 --disable_query_log
-let $1= 1050;
+let $1= 400;
 while ($1)
 {
-  eval CREATE EVENT ev_drop$1 ON SCHEDULE EVERY 1 SECOND DO SELECT $1;
+  eval CREATE EVENT ev_round4_drop$1 ON SCHEDULE EVERY 1 SECOND DO INSERT INTO events_test.fill_it VALUES("conn1_round4_ev$1", NOW());
   dec $1;
 }
 --enable_query_log
-SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_test2';
+SELECT COUNT(*) FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA='events_conn1_test2';
 --sleep 6
-DROP DATABASE events_test2;
+connection conn2;
+--send
+DROP DATABASE events_conn2_db;
+connection conn3;
+--send
+DROP DATABASE events_conn3_db;
+connection default;
+--send
+DROP DATABASE events_conn1_test2;
+DROP DATABASE events_conn1_test3;
 SET GLOBAL event_scheduler=0;
-DROP DATABASE events_test3;
-SET GLOBAL event_scheduler=1;
-DROP DATABASE events_test4;
+DROP DATABASE events_conn1_test4;
 SET GLOBAL event_scheduler=1;
+connection conn2;
+reap;
+disconnect conn2;
+connection conn3;
+reap;
+disconnect conn3;
+connection default;
 USE events_test;
+DROP TABLE fill_it;
 #
 # DROP DATABASE test end (bug #16406)
 #

--- 1.37/sql/event.cc	2006-02-28 21:32:30 +02:00
+++ 1.38/sql/event.cc	2006-04-20 18:15:59 +03:00
@@ -38,23 +38,8 @@
    ENABLED to DISABLED status change and this is safe for replicating. As well
    an event may be deleted which is also safe for RBR.
 
- - Maybe move all allocations during parsing to evex_mem_root thus saving
-    double parsing in evex_create_event!
-
- - If the server is killed (stopping) try to kill executing events?
- 
- - What happens if one renames an event in the DB while it is in memory?
-   Or even deleting it?
-  
- - Consider using conditional variable when doing shutdown instead of
-   waiting till all worker threads end.
- 
- - Make Event_timed::get_show_create_event() work
-
  - Add logging to file
 
- - Move comparison code to class Event_timed
-
 Warning:
  - For now parallel execution is not possible because the same sp_head cannot be
    executed few times!!! There is still no lock attached to particular event.
@@ -65,7 +50,7 @@ Warning:
 QUEUE EVEX_EQ_NAME;
 MEM_ROOT evex_mem_root;
 time_t mysql_event_last_create_time= 0L;
-
+ulong opt_event_executor;
 
 static TABLE_FIELD_W_TYPE event_table_fields[EVEX_FIELD_COUNT] = {
   {
@@ -186,30 +171,6 @@ LEX_STRING interval_type_to_name[] = {
 }; 
 
 
-
-/*
-  Inits the scheduler queue - prioritized queue from mysys/queue.c
-
-  Synopsis
-    evex_queue_init()
-
-      queue - pointer the the memory to be initialized as queue. has to be
-              allocated from the caller
-
-  Notes
-    During initialization the queue is sized for 30 events, and when is full
-    will auto extent with 30.
-*/
-
-void
-evex_queue_init(EVEX_QUEUE_TYPE *queue)
-{
-  if (init_queue_ex(queue, 30 /*num_el*/, 0 /*offset*/, 0 /*smallest_on_top*/,
-                    event_timed_compare_q, NULL, 30 /*auto_extent*/))
-    sql_print_error("Insufficient memory to initialize executing queue.");
-}
-
-
 /*
   Compares 2 LEX strings regarding case.
 
@@ -257,14 +218,8 @@ int sortcmp_lex_string(LEX_STRING s, LEX
 int
 my_time_compare(TIME *a, TIME *b)
 {
-
-#ifdef ENABLE_WHEN_WE_HAVE_MILLISECOND_IN_TIMESTAMPS
-  my_ulonglong a_t= TIME_to_ulonglong_datetime(a)*100L + a->second_part;
-  my_ulonglong b_t= TIME_to_ulonglong_datetime(b)*100L + b->second_part;
-#else
   my_ulonglong a_t= TIME_to_ulonglong_datetime(a);
   my_ulonglong b_t= TIME_to_ulonglong_datetime(b);
-#endif
 
   if (a_t > b_t)
     return 1;
@@ -276,31 +231,6 @@ my_time_compare(TIME *a, TIME *b)
 
 
 /*
-  Compares the execute_at members of 2 Event_timed instances
-
-  Synopsis
-    event_timed_compare()
-
-      a - first Event_timed object
-      b - second Event_timed object
-
-  RETURNS:
-   -1   - a->execute_at < b->execute_at
-    0   - a->execute_at == b->execute_at
-    1   - a->execute_at > b->execute_at
-
-  Notes
-    execute_at.second_part is not considered during comparison
-*/
-
-int
-event_timed_compare(Event_timed *a, Event_timed *b)
-{
-  return my_time_compare(&a->execute_at, &b->execute_at);
-}
-
-
-/*
   Compares the execute_at members of 2 Event_timed instances.
   Used as callback for the prioritized queue when shifting
   elements inside.
@@ -324,7 +254,8 @@ event_timed_compare(Event_timed *a, Even
 int 
 event_timed_compare_q(void *vptr, byte* a, byte *b)
 {
-  return event_timed_compare((Event_timed *)a, (Event_timed *)b);
+  return my_time_compare(&((Event_timed *)a)->execute_at,
+                         &((Event_timed *)b)->execute_at);
 }
 
 
@@ -462,6 +393,71 @@ common_1_lev_code:
 }
 
 
+static bool events_initted= false;
+
+/*
+  Inits the scheduler's structures.
+
+  Synopsis
+    events_init()
+
+  Returns
+    0  OK
+    1  Error
+*/
+
+int
+events_init()
+{
+  Event_scheduler *scheduler;
+  DBUG_ENTER("events_init");
+  if (!(scheduler= Event_scheduler::get_instance()))
+  {
+    sql_print_error("SCHEDULER: Error during initialization");
+    DBUG_RETURN(1);
+  }
+  events_initted= true;
+  
+  if (opt_event_executor && scheduler->start())
+  {
+    sql_print_error("SCHEDULER: Error while starting");
+    DBUG_RETURN(1);
+  }
+  DBUG_RETURN(0);
+}
+
+
+/*
+   Cleans up scheduler's resources. Called at sever shutdown.
+
+   SYNOPSIS
+     events_shutdown()
+*/
+
+void
+events_shutdown()
+{
+  Event_scheduler *scheduler;
+  DBUG_ENTER("events_shutdown");
+
+  if (events_initted)
+  {
+    scheduler= Event_scheduler::get_instance();
+    DBUG_ASSERT(scheduler);
+    scheduler->shutdown();
+
+    if (Event_scheduler::destroy())
+    {
+      DBUG_ASSERT(0);
+      sql_print_error("SCHEDULER: Trying to destroy while still running!");
+    }
+    events_initted= false;    
+  }
+
+  DBUG_VOID_RETURN;
+}
+
+
 /*
   Open mysql.event table for read
 
@@ -721,7 +717,7 @@ trunc_err:
      db_update_event. The name of the event is inside "et".
 */
 
-static int
+int
 db_create_event(THD *thd, Event_timed *et, my_bool create_if_not,
                 uint *rows_affected)
 {
@@ -954,7 +950,7 @@ err:
      2) tbl is not closed at exit
 */
 
-static int
+int
 db_find_event(THD *thd, sp_name *name, LEX_STRING *definer, Event_timed **ett,
               TABLE *tbl, MEM_ROOT *root)
 {
@@ -1011,147 +1007,6 @@ done:
 
 
 /*
-   Looks for a named event in mysql.event and then loads it from 
-   the table, compiles it and insert it into the cache.
-
-   SYNOPSIS
-     evex_load_and_compile_event()
-       thd       THD
-       spn       the name of the event to alter
-       definer   who is the owner
-       use_lock  whether to obtain a lock on LOCK_event_arrays or not
-
-   RETURN VALUE
-       0   - OK
-       < 0 - error (in this case underlying functions call my_error()).
-*/
-
-static int
-evex_load_and_compile_event(THD * thd, sp_name *spn, LEX_STRING definer,
-                            bool use_lock)
-{
-  int ret= 0;
-  MEM_ROOT *tmp_mem_root;
-  Event_timed *ett;
-  Open_tables_state backup;
-
-  DBUG_ENTER("db_load_and_compile_event");
-  DBUG_PRINT("enter", ("name: %*s", spn->m_name.length, spn->m_name.str));
-
-  tmp_mem_root= thd->mem_root;
-  thd->mem_root= &evex_mem_root;
-
-  thd->reset_n_backup_open_tables_state(&backup);
-  /* no need to use my_error() here because db_find_event() has done it */
-  ret= db_find_event(thd, spn, &definer, &ett, NULL, NULL);
-  thd->restore_backup_open_tables_state(&backup);
-  if (ret)
-    goto done;
-
-  /*
-    allocate on evex_mem_root. if you call without evex_mem_root
-    then sphead will not be cleared!
-  */
-  if ((ret= ett->compile(thd, &evex_mem_root)))
-    goto done;
-  
-  ett->compute_next_execution_time();
-  if (use_lock)
-    VOID(pthread_mutex_lock(&LOCK_event_arrays));
-
-  evex_queue_insert(&EVEX_EQ_NAME, (EVEX_PTOQEL) ett);
-
-  /*
-    There is a copy in the array which we don't need. sphead won't be
-    destroyed.
-  */
-
-  if (use_lock)
-    VOID(pthread_mutex_unlock(&LOCK_event_arrays));
-
-done:
-  if (thd->mem_root != tmp_mem_root)
-    thd->mem_root= tmp_mem_root;  
-
-  DBUG_RETURN(ret);
-}
-
-
-/*
-  Removes from queue in memory the event which is identified by the tupple
-  (db, name).
-
-   SYNOPSIS
-     evex_remove_from_cache()
-  
-       db       - db name
-       name     - event name
-       use_lock - whether to lock the mutex LOCK_event_arrays or not in case it
-                  has been already locked outside
-       is_drop  - if an event is currently being executed then we can also delete
-                  the Event_timed instance, so we alarm the event that it should
-                  drop itself if this parameter is set to TRUE. It's false on
-                  ALTER EVENT.
-
-   RETURNS
-     0  OK (always)
-*/
-
-static int
-evex_remove_from_cache(LEX_STRING *db, LEX_STRING *name, bool use_lock,
-                       bool is_drop)
-{
-  //ToDo : Add definer to the tuple (db, name) to become triple
-  uint i;
-  int ret= 0;
-
-  DBUG_ENTER("evex_remove_from_cache");
-  /*
-    It is possible that 2 (or 1) pass(es) won't find the event in memory.
-    The reason is that DISABLED events are not cached.
-  */
-
-  if (use_lock)
-    VOID(pthread_mutex_lock(&LOCK_event_arrays));
-
-  for (i= 0; i < evex_queue_num_elements(EVEX_EQ_NAME); ++i)
-  {
-    Event_timed *et= evex_queue_element(&EVEX_EQ_NAME, i, Event_timed*);
-    DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?",db->str,name->str, et->dbname.str,
-                        et->name.str));
-    if (!sortcmp_lex_string(*name, et->name, system_charset_info) &&
-        !sortcmp_lex_string(*db, et->dbname, system_charset_info))
-    {
-      if (et->can_spawn_now())
-      {
-        DBUG_PRINT("evex_remove_from_cache", ("not running - free and delete"));
-        et->free_sp();
-        delete et;
-      }
-      else
-      {
-        DBUG_PRINT("evex_remove_from_cache",
-               ("running.defer mem free. is_drop=%d", is_drop));
-        et->flags|= EVENT_EXEC_NO_MORE;
-        et->dropped= is_drop;
-      }
-      DBUG_PRINT("evex_remove_from_cache", ("delete from queue"));
-      evex_queue_delete_element(&EVEX_EQ_NAME, i);
-      /* ok, we have cleaned */
-      ret= 0;
-      goto done;
-    }
-  }
-
-done:
-  if (use_lock)
-    VOID(pthread_mutex_unlock(&LOCK_event_arrays));
-
-  DBUG_RETURN(ret);
-}
-
-
-/*
    The function exported to the world for creating of events.
 
    SYNOPSIS
@@ -1171,26 +1026,20 @@ int
 evex_create_event(THD *thd, Event_timed *et, uint create_options,
                   uint *rows_affected)
 {
-  int ret = 0;
+  int ret;
 
   DBUG_ENTER("evex_create_event");
   DBUG_PRINT("enter", ("name: %*s options:%d", et->name.length,
                 et->name.str, create_options));
 
-  if ((ret = db_create_event(thd, et,
+  if (!(ret = db_create_event(thd, et,
                              create_options & HA_LEX_CREATE_IF_NOT_EXISTS,
                              rows_affected)))
-    goto done;
-
-  VOID(pthread_mutex_lock(&LOCK_evex_running));
-  if (evex_is_running && et->status == MYSQL_EVENT_ENABLED)
   {
-    sp_name spn(et->dbname, et->name);
-    ret= evex_load_and_compile_event(thd, &spn, et->definer, true);
+    if (events_initted &&
+        (ret= Event_scheduler::get_instance()->add_event(thd, et, true)))
+      my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret);
   }
-  VOID(pthread_mutex_unlock(&LOCK_evex_running));
-
-done:
   /* No need to close the table, it will be closed in sql_parse::do_command */
 
   DBUG_RETURN(ret);
@@ -1217,41 +1066,20 @@ evex_update_event(THD *thd, Event_timed 
                   uint *rows_affected)
 {
   int ret;
-  bool need_second_pass= true;
 
   DBUG_ENTER("evex_update_event");
   DBUG_PRINT("enter", ("name: %*s", et->name.length, et->name.str));
-
   /*
     db_update_event() opens & closes the table to prevent
     crash later in the code when loading and compiling the new definition.
     Also on error conditions my_error() is called so no need to handle here
   */
-  if ((ret= db_update_event(thd, et, new_name)))
-    goto done;
-
-  VOID(pthread_mutex_lock(&LOCK_evex_running));
-  if (!evex_is_running)
-    UNLOCK_MUTEX_AND_BAIL_OUT(LOCK_evex_running, done);
-
-  VOID(pthread_mutex_lock(&LOCK_event_arrays));
-  evex_remove_from_cache(&et->dbname, &et->name, false, false);
-  if (et->status == MYSQL_EVENT_ENABLED)
-  {
-    if (new_name)
-      ret= evex_load_and_compile_event(thd, new_name, et->definer, false);
-    else
-    {
-      sp_name spn(et->dbname, et->name);
-      ret= evex_load_and_compile_event(thd, &spn, et->definer, false);
-    }
-    if (ret == EVEX_COMPILE_ERROR)
-      my_error(ER_EVENT_COMPILE_ERROR, MYF(0));
+  if (!(ret= db_update_event(thd, et, new_name)))
+  {
+    if (events_initted && 
+        (ret= Event_scheduler::get_instance()->replace_event(thd, et)))
+      my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret);
   }
-  VOID(pthread_mutex_unlock(&LOCK_event_arrays));
-  VOID(pthread_mutex_unlock(&LOCK_evex_running));
-
-done:
   DBUG_RETURN(ret);
 }
 
@@ -1333,22 +1161,15 @@ int
 evex_drop_event(THD *thd, Event_timed *et, bool drop_if_exists,
                 uint *rows_affected)
 {
-  int ret= 0;
+  int ret;
 
   DBUG_ENTER("evex_drop_event");
-
-
-  VOID(pthread_mutex_lock(&LOCK_evex_running));
-  if (evex_is_running)
-    ret= evex_remove_from_cache(&et->dbname, &et->name, true, true);
-  VOID(pthread_mutex_unlock(&LOCK_evex_running));
-
-  if (ret == 1)
-    ret= 0;
-  else if (ret == 0)   
-    ret= db_drop_event(thd, et, drop_if_exists, rows_affected);
-  else
-    my_error(ER_UNKNOWN_ERROR, MYF(0));
+  if (!(ret= db_drop_event(thd, et, drop_if_exists, rows_affected)))
+  {
+    if (events_initted &&
+        (ret= Event_scheduler::get_instance()->drop_event(thd, et)))
+      my_error(ER_EVENT_MODIFY_QUEUE_ERROR, MYF(0), ret);
+  }
 
   DBUG_RETURN(ret);
 }
@@ -1395,7 +1216,7 @@ evex_show_create_event(THD *thd, sp_name
     show_str.set_charset(system_charset_info);
 
     if (et->get_create_event(thd, &show_str))
-      DBUG_RETURN(1);
+      goto err;
 
     field_list.push_back(new Item_empty_string("Event", NAME_LEN));
 
@@ -1409,7 +1230,7 @@ evex_show_create_event(THD *thd, sp_name
                                                show_str.length()));
     if (protocol->send_fields(&field_list, Protocol::SEND_NUM_ROWS |
                                            Protocol::SEND_EOF))
-      DBUG_RETURN(1);
+      goto err;
 
     protocol->prepare_for_resend();
     protocol->store(et->name.str, et->name.length, system_charset_info);
@@ -1420,187 +1241,99 @@ evex_show_create_event(THD *thd, sp_name
     ret= protocol->write();
     send_eof(thd);
   }
-
+  delete et;
   DBUG_RETURN(ret);
+err:
+  delete et;
+  DBUG_RETURN(1);  
 }
 
 
 /*
-  evex_drop_db_events - Drops all events in the selected database
+  Drops all events from a schema
+
+  Synopsis
+    evex_drop_db_events()
+      thd  Thread
+      db   ASCIIZ schema name
 
-  thd  - Thread
-  db   - ASCIIZ the name of the database
-  
   Returns:
-    0  - OK
-    1  - Failed to delete a specific row
-    2  - Got NULL while reading db name from a row
-
-  Note:
-    The algo is the following
-    1. Go through the in-memory cache, if the scheduler is working
-       and for every event whose dbname matches the database we drop
-       check whether is currently in execution:
-       - Event_timed::can_spawn() returns true -> the event is not
-         being executed in a child thread. The reason not to use
-         Event_timed::is_running() is that the latter shows only if
-         it is being executed, which is 99% of the time in the thread
-         but there are some initiliazations before and after the
-         anonymous SP is being called. So if we delete in this moment
-         -=> *boom*, so we have to check whether the thread has been
-         spawned and can_spawn() is the right method.
-       - Event_timed::can_spawn() returns false -> being runned ATM
-         just set the flags so it should drop itself.
+    0   OK
+    !0  Error
 */
 
 int
-evex_drop_db_events(THD *thd, char *db)
+evex_drop_schema_events(THD *thd, char *db, uint *dropped)
 {
-  TABLE *table;
-  READ_RECORD read_record_info;
   int ret= 0;
-  uint i;
   LEX_STRING db_lex= {db, strlen(db)};
   
   DBUG_ENTER("evex_drop_db_events");  
-  DBUG_PRINT("info",("dropping events from %s", db));
+  DBUG_PRINT("enter", ("dropping events from %s", db));
 
-  VOID(pthread_mutex_lock(&LOCK_event_arrays));
+  *dropped= 0;
+  if (events_initted)
+    ret= Event_scheduler::get_instance()->
+                                drop_schema_events(thd, &db_lex, dropped);
+  else
+    ret= db_drop_events_from_table(thd, &db_lex, dropped);
 
-  if ((ret= evex_open_event_table(thd, TL_WRITE, &table)))
-  {
-    sql_print_error("Table mysql.event is damaged.");
-    VOID(pthread_mutex_unlock(&LOCK_event_arrays));
-    DBUG_RETURN(SP_OPEN_TABLE_FAILED);
-  }
+  DBUG_RETURN(ret);
+}
 
-  DBUG_PRINT("info",("%d elements in the queue",
-             evex_queue_num_elements(EVEX_EQ_NAME)));
-  VOID(pthread_mutex_lock(&LOCK_evex_running));
-  if (!evex_is_running)
-    goto skip_memory;
 
-  for (i= 0; i < evex_queue_num_elements(EVEX_EQ_NAME); ++i)
-  {
-    Event_timed *et= evex_queue_element(&EVEX_EQ_NAME, i, Event_timed*);
-    if (sortcmp_lex_string(et->dbname, db_lex, system_charset_info))
-      continue;
+/*
+  Drops all events in the selected database, from mysql.event.
 
-    if (et->can_spawn_now_n_lock(thd))
-    {
-      DBUG_PRINT("info",("event %s not running - direct delete", et->name.str));
-      if (!(ret= evex_db_find_event_aux(thd, et, table)))
-      {
-        DBUG_PRINT("info",("event %s found on disk", et->name.str));
-        if ((ret= table->file->ha_delete_row(table->record[0])))
-        {
-          sql_print_error("Error while deleting a row - dropping "
-                          "a database. Skipping the rest.");
-          my_error(ER_EVENT_DROP_FAILED, MYF(0), et->name.str);
-          goto end;
-        }
-        DBUG_PRINT("info",("deleted event [%s] num [%d]. Time to free mem",
-                   et->name.str, i));
-      }
-      else if (ret == EVEX_KEY_NOT_FOUND)
-      {
-        sql_print_error("Expected to find event %s.%s of %s on disk-not there.",
-                        et->dbname.str, et->name.str, et->definer.str);
-      }
-      et->free_sp();
-      delete et;
-      et= 0;
-      /* no need to call et->spawn_unlock because we already cleaned et */
-    }
-    else
-    {
-      DBUG_PRINT("info",("event %s is running. setting exec_no_more and dropped",
-                  et->name.str));
-      et->flags|= EVENT_EXEC_NO_MORE;
-      et->dropped= TRUE;
-    }
-    DBUG_PRINT("info",("%d elements in the queue",
-               evex_queue_num_elements(EVEX_EQ_NAME)));
-    evex_queue_delete_element(&EVEX_EQ_NAME, i);// 0 is top
-    DBUG_PRINT("info",("%d elements in the queue",
-               evex_queue_num_elements(EVEX_EQ_NAME)));
-    /*
-      decrease so we start at the same position, there will be
-      less elements in the queue, it will still be ordered so on
-      next iteration it will be again i the current element or if
-      no more we finish.
-    */
-    --i;
-  }
+  Synopsis
+    evex_drop_db_events_from_table()
+      thd  Thread
+      db   Schema name
+
+  Returns
+     0  OK
+    !0  Error from ha_delete_row
+*/
 
-skip_memory:
-  /*
-   The reasoning behind having two loops is the following:
-   If there was only one loop, the table-scan, then for every element which
-   matches, the queue in memory has to be searched to remove the element.
-   While if we go first over the queue and remove what's in there we have only
-   one pass over it and after finishing it, moving to table-scan for the disabled
-   events. This needs quite less time and means quite less locking on
-   LOCK_event_arrays.
-  */
-  DBUG_PRINT("info",("Mem-cache checked, now going to db for disabled events"));
+int
+db_drop_events_from_table(THD *thd, LEX_STRING *db, uint *dropped_num)
+{
+  int ret;
+  TABLE *table;
+  READ_RECORD read_record_info;
+  DBUG_ENTER("db_drop_events_from_table");  
+  DBUG_PRINT("info", ("dropping events from %s", db->str));
+
+  if ((ret= evex_open_event_table(thd, TL_WRITE, &table)))
+  {
+    sql_print_error("Table mysql.event is damaged.");
+    DBUG_RETURN(ret);
+  }
+  *dropped_num= 0;
   /* only enabled events are in memory, so we go now and delete the rest */
-  init_read_record(&read_record_info, thd, table ,NULL,1,0);
+  init_read_record(&read_record_info, thd, table, NULL, 1, 0);
   while (!(read_record_info.read_record(&read_record_info)) && !ret)
   {
-    char *et_db;
+    char *et_db= get_field(thd->mem_root, table->field[EVEX_FIELD_DB]);
 
-    if ((et_db= get_field(thd->mem_root, table->field[EVEX_FIELD_DB])) == NULL)
-    {
-      ret= 2;
-      break;
-    }
-    
     LEX_STRING et_db_lex= {et_db, strlen(et_db)};
-    if (!sortcmp_lex_string(et_db_lex, db_lex, system_charset_info))
+    DBUG_PRINT("info", ("Current event %s.%s", et_db,
+               get_field(thd->mem_root, table->field[EVEX_FIELD_NAME])));
+    if (!sortcmp_lex_string(et_db_lex, *db, system_charset_info))
     {
-      Event_timed ett;
-      char *ptr;
-      
-      if ((ptr= get_field(thd->mem_root, table->field[EVEX_FIELD_STATUS]))
-           == NullS)
-      {
-        sql_print_error("Error while loading from mysql.event. "
-                        "Table probably corrupted");
-        goto end;
-      }
-      /*
-        When not running nothing is in memory so we have to clean
-        everything.
-        We don't delete EVENT_ENABLED events when the scheduler is running
-        because maybe this is an event which we asked to drop itself when
-        it is finished and it hasn't finished yet, so we don't touch it.
-        It will drop itself. The not running ENABLED events has been already
-        deleted from ha_delete_row() above in the loop over the QUEUE
-        (in case the executor is running).
-        'D' stands for DISABLED, 'E' for ENABLED - it's an enum
-      */
-      if ((evex_is_running && ptr[0] == 'D') || !evex_is_running)
-      {
-        DBUG_PRINT("info", ("Dropping %s.%s", et_db, ett.name.str));
-        if ((ret= table->file->ha_delete_row(table->record[0])))
-        {
-          my_error(ER_EVENT_DROP_FAILED, MYF(0), ett.name.str);
-          goto end;
-        }
-      }
+      DBUG_PRINT("info", ("Dropping"));
+      if (!(ret= table->file->ha_delete_row(table->record[0])))
+        ++*dropped_num;
+      else
+        my_error(ER_EVENT_DROP_FAILED, MYF(0),
+                 get_field(thd->mem_root, table->field[EVEX_FIELD_NAME]));
     }
   }
-  DBUG_PRINT("info",("Disk checked for disabled events. Finishing."));
-
-end:
-  VOID(pthread_mutex_unlock(&LOCK_evex_running));
-  VOID(pthread_mutex_unlock(&LOCK_event_arrays));
   end_read_record(&read_record_info);
-
   thd->version--;   /* Force close to free memory */
 
   close_thread_tables(thd);
 
+  DBUG_PRINT("info", ("Dropped %lu events", *dropped_num));
   DBUG_RETURN(ret);
 }

--- 1.27/sql/event.h	2006-03-16 14:14:32 +02:00
+++ 1.28/sql/event.h	2006-04-20 18:15:59 +03:00
@@ -19,6 +19,7 @@
 
 #include "sp.h"
 #include "sp_head.h"
+#include "event_scheduler.h"
 
 #define EVEX_OK                 SP_OK
 #define EVEX_KEY_NOT_FOUND      SP_KEY_NOT_FOUND
@@ -36,9 +37,11 @@
 #define EVEX_BAD_PARAMS        -21
 #define EVEX_NOT_RUNNING       -22
 #define EVEX_MICROSECOND_UNSUP -23
+#define EVEX_CANT_KILL         -24
 
 #define EVENT_EXEC_NO_MORE      (1L << 0)
 #define EVENT_NOT_USED          (1L << 1)
+#define EVENT_FREE_WHEN_FINISHED (1L << 2)
 
 extern ulong opt_event_executor;
 
@@ -75,6 +78,8 @@ enum evex_table_field
   EVEX_FIELD_COUNT /* a cool trick to count the number of fields :) */
 } ;
 
+int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs);
+
 class Event_timed
 {
   Event_timed(const Event_timed &);	/* Prevent use of these */
@@ -82,7 +87,9 @@ class Event_timed
   my_bool in_spawned_thread;
   ulong locked_by_thread_id;
   my_bool running;
+  ulong thread_id;
   pthread_mutex_t LOCK_running;
+  pthread_cond_t COND_finished;
 
   bool status_changed;
   bool last_executed_changed;
@@ -155,7 +162,7 @@ public:
 
 
   Event_timed():in_spawned_thread(0),locked_by_thread_id(0),
-                running(0), status_changed(false),
+                running(0), thread_id(0), status_changed(false),
                 last_executed_changed(false), expression(0), created(0),
                 modified(0), on_completion(MYSQL_EVENT_ON_COMPLETION_DROP),
                 status(MYSQL_EVENT_ENABLED), sphead(0), sql_mode(0), 
@@ -164,6 +171,7 @@ public:
                 
   {
     pthread_mutex_init(&this->LOCK_running, MY_MUTEX_INIT_FAST);
+    pthread_cond_init(&this->COND_finished, NULL);
     init();
   }
 
@@ -182,6 +190,7 @@ public:
   deinit_mutexes()
   {
     pthread_mutex_destroy(&this->LOCK_running);
+    pthread_cond_destroy(&this->COND_finished);
   }
 
   int
@@ -232,10 +241,10 @@ public:
   int
   compile(THD *thd, MEM_ROOT *mem_root= NULL);
 
-  my_bool
+  bool
   is_running()
   {
-    my_bool ret;
+    bool ret;
 
     VOID(pthread_mutex_lock(&this->LOCK_running));
     ret= running;
@@ -275,7 +284,7 @@ public:
   spawn_unlock(THD *thd);
 
   int
-  spawn_now(void * (*thread_func)(void*));
+  spawn_now(void * (*thread_func)(void*), void *arg);
   
   void
   spawn_thread_finish(THD *thd);
@@ -286,6 +295,16 @@ public:
     delete sphead;
     sphead= 0;
   }
+
+  bool
+  has_equal_db(Event_timed *etn);
+
+  int
+  kill_thread(THD *thd);
+
+  void
+  set_thread_id(ulong tid) { thread_id= tid; }
+
 protected:
   bool
   change_security_context(THD *thd, Security_context *s_ctx,
@@ -314,55 +333,38 @@ evex_open_event_table(THD *thd, enum thr
 int
 evex_show_create_event(THD *thd, sp_name *spn, LEX_STRING definer);
 
-int sortcmp_lex_string(LEX_STRING s, LEX_STRING t, CHARSET_INFO *cs);
-
 int
 event_reconstruct_interval_expression(String *buf,
                                       interval_type interval,
                                       longlong expression);
 
 int
-evex_drop_db_events(THD *thd, char *db);
+evex_drop_schema_events(THD *thd, char *db, uint *dropped);
 
 
 int
-init_events();
+events_init();
 
 void
-shutdown_events();
-
-
-// auxiliary
-int
-event_timed_compare(Event_timed **a, Event_timed **b);
-
+events_shutdown();
 
+/* Compares only the name part of the identifier */
+bool
+event_timed_name_equal(Event_timed *et, LEX_STRING *name);
+
+/* Compares only the schema part of the identifier */
+bool
+event_timed_db_equal(Event_timed *et, LEX_STRING *db);
 
 /*
-CREATE TABLE event (
-  db char(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '',
-  name char(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '',
-  body longblob NOT NULL,
-  definer char(77) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '',
-  execute_at DATETIME default NULL,
-  interval_value int(11) default NULL,
-  interval_field ENUM('YEAR','QUARTER','MONTH','DAY','HOUR','MINUTE','WEEK',
-                       'SECOND','MICROSECOND', 'YEAR_MONTH','DAY_HOUR',
-                       'DAY_MINUTE','DAY_SECOND',
-                       'HOUR_MINUTE','HOUR_SECOND',
-                       'MINUTE_SECOND','DAY_MICROSECOND',
-                       'HOUR_MICROSECOND','MINUTE_MICROSECOND',
-                       'SECOND_MICROSECOND') default NULL,
-  created TIMESTAMP NOT NULL,
-  modified TIMESTAMP NOT NULL,
-  last_executed DATETIME default NULL,
-  starts DATETIME default NULL,
-  ends DATETIME default NULL,
-  status ENUM('ENABLED','DISABLED') NOT NULL default 'ENABLED',
-  on_completion ENUM('DROP','PRESERVE') NOT NULL default 'DROP',
-  comment varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL default '',
-  PRIMARY KEY  (definer,db,name)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT 'Events';
+  Compares only the definer part of the identifier. Use during DROP USER
+  to drop user's events. (Still not implemented)
 */
+bool
+event_timed_definer_equal(Event_timed *et, LEX_STRING *definer);
+
+/* Compares the whole identifier*/
+bool
+event_timed_identifier_equal(Event_timed *a, Event_timed *b);
 
 #endif /* _EVENT_H_ */

--- 1.20/sql/event_priv.h	2006-02-28 19:33:25 +02:00
+++ 1.21/sql/event_priv.h	2006-04-20 18:15:59 +03:00
@@ -23,11 +23,6 @@
 #define EVENT_EXEC_ALREADY_EXEC 1
 #define EVENT_EXEC_CANT_FORK    2
 
-#define EVEX_USE_QUEUE
-
-#define UNLOCK_MUTEX_AND_BAIL_OUT(__mutex, __label) \
-    { VOID(pthread_mutex_unlock(&__mutex)); goto __label; }
-
 #define EVEX_DB_FIELD_LEN 64
 #define EVEX_NAME_FIELD_LEN 64
 #define EVEX_MAX_INTERVAL_VALUE 2147483647L
@@ -44,39 +39,18 @@ evex_db_find_event_by_name(THD *thd, con
 int
 event_timed_compare_q(void *vptr, byte* a, byte *b);
 
-int db_drop_event(THD *thd, Event_timed *et, bool drop_if_exists,
-                uint *rows_affected);
-
-
-#define EXEC_QUEUE_QUEUE_NAME executing_queue
-#define EXEC_QUEUE_DARR_NAME evex_executing_queue
-
-
-#define EVEX_QUEUE_TYPE QUEUE
-#define EVEX_PTOQEL byte *
-
-#define EVEX_EQ_NAME executing_queue
-#define evex_queue_first_element(queue, __cast) ((__cast)queue_top(queue))
-#define evex_queue_element(queue, idx, __cast) ((__cast)queue_element(queue, idx))
-#define evex_queue_delete_element(queue, idx)  queue_remove(queue, idx)
-#define evex_queue_destroy(queue)              delete_queue(queue)
-#define evex_queue_first_updated(queue)        queue_replaced(queue)
-#define evex_queue_insert(queue, element)      queue_insert_safe(queue, element);
-
-
-
-void
-evex_queue_init(EVEX_QUEUE_TYPE *queue);
-
-#define evex_queue_num_elements(queue) queue.elements
+int
+db_drop_event(THD *thd, Event_timed *et, bool drop_if_exists,
+              uint *rows_affected);
+int
+db_find_event(THD *thd, sp_name *name, LEX_STRING *definer, Event_timed **ett,
+              TABLE *tbl, MEM_ROOT *root);
 
+int
+db_create_event(THD *thd, Event_timed *et, my_bool create_if_not,
+                uint *rows_affected);
 
-extern bool evex_is_running;
-extern MEM_ROOT evex_mem_root;
-extern pthread_mutex_t LOCK_event_arrays,
-                       LOCK_workers_count,
-                       LOCK_evex_running;
-extern ulonglong evex_main_thread_id;
-extern QUEUE EVEX_EQ_NAME;
+int
+db_drop_events_from_table(THD *thd, LEX_STRING *db, uint *dropped);
 
 #endif /* _EVENT_PRIV_H_ */
--- New file ---
+++ sql/event_scheduler.cc	06/04/20 18:16:00
/* Copyright (C) 2004-2005 MySQL AB

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

#include "event_priv.h"
#include "event.h"

/*
  ToDo:
  1. Use union { my_bool my_bool_value; long long_value; } sys_var_tmp;
     for manager_working.
  2. Talk to Alik to get a check for configure.in for my_time_t and time_t
  3. Refactor init_event_thread().
  4. Look at guardian.h|cc to see its life cycle, has similarities according
     to Kostja.
*/


class Worker_thread_param
{
public:
  Event_timed *et;
  pthread_mutex_t LOCK_started;
  pthread_cond_t COND_started;

  Worker_thread_param(Event_timed *etn):et(etn)
  {
    pthread_mutex_init(&LOCK_started, MY_MUTEX_INIT_FAST);
    pthread_cond_init(&COND_started, NULL);  
  }

  ~Worker_thread_param()
  {
    pthread_mutex_destroy(&LOCK_started);
    pthread_cond_destroy(&COND_started);
  }
};

Event_scheduler
*Event_scheduler::singleton= NULL;

my_bool
Event_scheduler::manager_working= FALSE;



extern LEX_STRING warning_level_names[];

typedef void (*sql_print_xxx_func)(const char *format, ...);
static sql_print_xxx_func sql_print_xxx_handlers[3] =
{
  sql_print_information,
  sql_print_warning,
  sql_print_error
};


/*
  Prints the stack of infos, warnings, errors from thd to
  the console so it can be fetched by the logs-into-tables and
  checked later.

  Synopsis
    evex_print_warnings
      thd    - thread used during the execution of the event
      et     - the event itself
*/

void
evex_print_warnings(THD *thd, Event_timed *et)
{
  MYSQL_ERROR *err;
  DBUG_ENTER("evex_print_warnings");
  char msg_buf[1024];
  char prefix_buf[512];
  String prefix(prefix_buf, sizeof(prefix_buf), system_charset_info);
  prefix.length(0);

  List_iterator_fast<MYSQL_ERROR> it(thd->warn_list);
  while ((err= it++))
  {
    String err_msg(msg_buf, sizeof(msg_buf), system_charset_info);
    /* set it to 0 or we start adding at the end. That's the trick ;) */
    err_msg.length(0);
    if (!prefix.length())
    {
      prefix.append("SCHEDULER: [");

      append_identifier(thd, &prefix, et->definer_user.str,
                        et->definer_user.length);
      prefix.append('@');
      append_identifier(thd, &prefix, et->definer_host.str,
                        et->definer_host.length);
      prefix.append("][", 2);
      append_identifier(thd,&prefix, et->dbname.str, et->dbname.length);
      prefix.append('.');
      append_identifier(thd,&prefix, et->name.str, et->name.length);
      prefix.append("] ", 2);
    }

    err_msg.append(prefix);
    err_msg.append(err->msg, strlen(err->msg), system_charset_info);
    err_msg.append("]");
    DBUG_ASSERT(err->level < 3);
    (sql_print_xxx_handlers[err->level])("%*s", err_msg.length(),
                                         err_msg.c_ptr());
  }
  DBUG_VOID_RETURN;
}

extern const char *my_localhost;

/*
  Inits an scheduler thread handler, both the main and a worker

  SYNOPSIS
    init_event_thread()
      thd - the THD of the thread. Has to be allocated by the caller.

  NOTES
    1. The host of the thead is my_localhost
    2. thd->net is initted with NULL - no communication.

  Returns
    0  OK
   -1  Error
*/

static int
init_event_thread(THD* thd)
{
  thd->thread_stack = (char*)thd;               // remember where our stack is
  DBUG_ENTER("init_event_thread");
  thd->client_capabilities= 0;
  thd->security_ctx->master_access= 0;
  thd->security_ctx->db_access= 0;
  thd->security_ctx->host_or_ip= (char*)my_localhost;
  my_net_init(&thd->net, 0);
  thd->net.read_timeout = slave_net_timeout;
  thd->slave_thread= 0;
  thd->options|= OPTION_AUTO_IS_NULL;
  thd->client_capabilities= CLIENT_LOCAL_FILES;
  thd->real_id=pthread_self();
  VOID(pthread_mutex_lock(&LOCK_thread_count));
  thd->thread_id= thread_id++;
  threads.append(thd);
  thread_count++;
  thread_running++;
  VOID(pthread_mutex_unlock(&LOCK_thread_count));

  if (init_thr_lock() || thd->store_globals())
  {
    thd->cleanup();
    delete thd;
    DBUG_RETURN(-1);
  }

#if !defined(__WIN__) && !defined(OS2) && !defined(__NETWARE__)
  sigset_t set;
  VOID(sigemptyset(&set));			// Get mask in use
  VOID(pthread_sigmask(SIG_UNBLOCK,&set,&thd->block_signals));
#endif

  thd->proc_info= "Initialized";
  thd->version= refresh_version;
  thd->set_time();

  /*
    Guarantees that we will see the thread in SHOW PROCESSLIST though its
    vio is NULL.
  */
  thd->system_thread= 1;

  DBUG_RETURN(0);
}


/*
  Inits the main manager thread and then calls Event_scheduler::run()
  of arg. 

  SYNOPSIS
    event_scheduler_run_proxy()
      arg  void* ptr to Event_scheduler

  NOTES
    1. The host of the thead is my_localhost
    2. thd->net is initted with NULL - no communication.
    3. The reason to have a proxy function is that it's not possible to
       use a method as function to be executed in a spawned thread:
       - our pthread_hander_t macro uses extern "C"
       - separating thread setup from the real execution loop is also to be
         considered good.

  Returns
    0  OK
   -1  Error
*/

pthread_handler_t
event_scheduler_run_proxy(void *arg)
{
  THD *thd= NULL;                               // needs to be first for thread_stack
  Event_scheduler *scheduler= (Event_scheduler *) arg;

  DBUG_ENTER("event_scheduler_run_proxy");

  my_thread_init();
  pthread_detach_this_thread();

  /* note that contructor of THD uses DBUG_ ! */
  if (!(thd = new THD) || init_event_thread(thd))
    sql_print_error("SCHEDULER: Cannot init manager event thread.");
  else
  {
    thd->security_ctx->user= my_strdup("event_scheduler", MYF(0));

    sql_print_information("SCHEDULER: Manager thread booting");

    THD_CHECK_SENTRY(thd);
    scheduler->run(thd);
  
    /*
      NOTE: Don't touch `esm` after this point because we have notified the
            thread which shuts us down that we have finished cleaning. In this
            very moment a new manager thread could be started and a crash is not
            welcome.
    */
    THD_CHECK_SENTRY(thd);
  }

  /*
    If we cannot create THD then don't decrease because we haven't touched
    thread_count and thread_running in init_event_thread() which was never
    called. In init_event_thread() thread_count and thread_running are
    always increased even in the case the method returns an error.
  */
  if (thd)
  {
    pthread_mutex_lock(&LOCK_thread_count);
    thread_count--;
    thread_running--;
    pthread_mutex_unlock(&LOCK_thread_count);

    thd->proc_info = "Clearing";
    DBUG_ASSERT(thd->net.buff != 0);
    net_end(&thd->net); 
    THD_CHECK_SENTRY(thd);
    delete thd;
  }
  my_thread_end();
  pthread_exit(0);
  DBUG_RETURN(0);                               // Can't return anything here
}


/*
   Function that executes an event in a child thread. Setups the 
   environment for the event execution and cleans after that.

   SYNOPSIS
     event_worker_thread()
       arg  The Event_timed object to be processed
*/

pthread_handler_t
event_worker_thread(void *arg)
{
  THD *thd; /* needs to be first for thread_stack */
  Worker_thread_param *param = (Worker_thread_param *) arg;
  Event_timed *event= param->et;
  MEM_ROOT worker_mem_root;
  int ret;
  bool startup_error= false;

  DBUG_ENTER("event_worker_thread");

  my_thread_init();
  pthread_detach_this_thread();

  if (!(thd = new THD) || init_event_thread(thd))
  {
    sql_print_error("SCHEDULER: Startup failure.");
    startup_error= true;
    event->spawn_thread_finish(thd);
  }
  else
    event->set_thread_id(thd->thread_id);
  
  /* Signal the manager thread that we have started successfully */
  pthread_mutex_lock(&param->LOCK_started);
  pthread_cond_signal(&param->COND_started);
  pthread_mutex_unlock(&param->LOCK_started);

  if (!startup_error)
  {
    thd->init_for_queries();
    thd->enable_slow_log= true;
    if (Event_scheduler::register_worker_thread(thd->thread_id))
    {
      DBUG_PRINT("info", ("Manager not running. Could be an error"));
    }
    else
    {
      uint flags;
      event->set_thread_id(thd->thread_id);
      sql_print_information("SCHEDULER: [%s.%s of %s] executing in thread %lu",
                            event->dbname.str, event->name.str,
                            event->definer.str, thd->thread_id);

      ret= event->execute(thd, thd->mem_root);
      evex_print_warnings(thd, event);
      sql_print_information("SCHEDULER: [%s.%s of %s] executed. RetCode=%d",
                            event->dbname.str, event->name.str,
                            event->definer.str, ret);
      if (ret == EVEX_COMPILE_ERROR)
        sql_print_information("SCHEDULER: COMPILE ERROR for event %s.%s of %s",
                              event->dbname.str, event->name.str,
                              event->definer.str);
      else if (ret == EVEX_MICROSECOND_UNSUP)
        sql_print_information("SCHEDULER: MICROSECOND is not supported");

      /*
        Copy the flags because after spawn_thread_finish() it's unsafe to
        touch `event`
      */
      flags= event->flags;

      /* This should be called always because it does some deinit work */
      event->spawn_thread_finish(thd);

      /*
        If this flag is set it's safe to touch event because it's removed from
        the in-memory queue and we hold the only reference
      */
      if (flags & EVENT_FREE_WHEN_FINISHED)
        delete event;
    }
  }
  pthread_mutex_lock(&LOCK_thread_count);
  thread_count--;
  thread_running--;
  pthread_mutex_unlock(&LOCK_thread_count);

  if (Event_scheduler::deregister_worker_thread(thd->thread_id))
    sql_print_information("SCHEDULER: Error during deregister_worker_thread()");

  if (thd)
  {
    thd->proc_info = "Clearing";
    DBUG_ASSERT(thd->net.buff != 0);
    /* QQ: Is this needed THD::~THD does it too */
    net_end(&thd->net);
    THD_CHECK_SENTRY(thd);
    DBUG_PRINT("info", ("Worker thread %lu exiting", thd->thread_id));
    VOID(pthread_mutex_lock(&LOCK_thread_count));
    delete thd;
    VOID(pthread_mutex_unlock(&LOCK_thread_count));
  }

  my_thread_end();
  pthread_exit(0);
  DBUG_RETURN(0);                               // Can't return anything here
}


/*
  Returns singleton instance of the class

  Synopsis
    Event_scheduler::get_instance()
      implicit_create  Whether to create a new instance if singleton is null
                       or just return null.
  Returns
    NULL     Error
    address  Success
*/

Event_scheduler*
Event_scheduler::get_instance()
{
  Event_scheduler *tmp;
  DBUG_ENTER("Event_scheduler::get_instance");

  if (sizeof(my_time_t) != sizeof(time_t))
  {
    sql_print_error("SCHEDULER: sizeof(my_time_t) != sizeof(time_t) ."
                    "The scheduler may not work correctly. Stopping.");
    DBUG_ASSERT(0);
    DBUG_RETURN(NULL);
  }

  if (!singleton && (tmp= new Event_scheduler))
  {
    if (tmp->init())
      delete tmp;
    else
      singleton= tmp;
  }

  DBUG_RETURN(singleton);
}


/*
  Initializes a scheduler object

  Synopsis
    Event_scheduler::init()
      scheduler  Event_scheduler object to initialize

  Returns
    false  OK
    true   Error
*/

bool
Event_scheduler::init()
{
  DBUG_ENTER("Event_scheduler::init");
  DBUG_PRINT("enter", ("this=%p", this));

  /* Check it rarely (when creating instance) but do check it */
  check_system_tables();

  if (pthread_mutex_init(&LOCK_scheduler_data, MY_MUTEX_INIT_FAST) ||
      pthread_cond_init(&COND_bulk_drop_finished, NULL) ||      
      pthread_cond_init(&COND_new_work, NULL) ||
      pthread_cond_init(&COND_started, NULL) ||
      pthread_cond_init(&COND_shutdown, NULL) ||
      pthread_cond_init(&COND_last_worker_exit, NULL) ||
      pthread_mutex_init(&LOCK_last_worker_exit, MY_MUTEX_INIT_FAST))
  {
    sql_print_error("SCHEDULER: Unable to initalize mutexes or conditions");
    DBUG_RETURN(true);
  }

#ifndef DBUG_OFF
  if (pthread_mutex_init(&LOCK_executed, MY_MUTEX_INIT_FAST) ||
      pthread_cond_init(&COND_executed, NULL))  
  {
    sql_print_error("SCHEDULER: Unable to initalize mutexes or conditions");
    DBUG_RETURN(true);
  }
#endif

  /* init memory root */
  init_alloc_root(&scheduler_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);

  if (init_queue_ex(&queue, 30 /*num_el*/, 0 /*offset*/, 0 /*smallest_on_top*/,
                    event_timed_compare_q, NULL, 30 /*auto_extent*/))
  {
    sql_print_error("SCHEDULER: Can't initialize queue executing queue");
    DBUG_RETURN(true);
  }

  my_init_dynamic_array(&workers, sizeof(ulong), 50, 100);

  state= INITIALIZED;

  DBUG_RETURN(false);
}


/*
  Cleanups memory

  Synopsis
    Event_scheduler::~Event_scheduler()

  LOCK PROCOTOL: Expects that this method is synchronized
*/

Event_scheduler::~Event_scheduler() 
{
  /* ToDo: Wait if there are elements on the queue */
  lock_mutex(&LOCK_scheduler_data);
  singleton->state= IN_SHUTDOWN;
  unlock_mutex(&LOCK_scheduler_data);

  delete_queue(&queue);
  delete_dynamic(&workers);
  free_root(&scheduler_root, MYF(0));
  pthread_cond_destroy(&COND_new_work);
  pthread_cond_destroy(&COND_started);
  pthread_cond_destroy(&COND_bulk_drop_finished);
#ifndef DBUG_OFF
  pthread_cond_destroy(&COND_executed);
  pthread_mutex_destroy(&LOCK_executed);
#endif
  state= STOPPED;
  pthread_mutex_destroy(&LOCK_scheduler_data);
}


/*
  Destroys the singleton if there is any. The destructor
  of Event_scheduler is private.

  Synopsis
    Event_scheduler::destroy()
*/

bool
Event_scheduler::destroy()
{
  DBUG_ENTER("Event_scheduler::destroy");
  if (singleton && singleton->state > RUNNING)
  {
    /* the master thread should not be running */
    DBUG_ASSERT(0);
    DBUG_RETURN(true);
  }
  delete singleton;
  singleton= NULL;
  DBUG_RETURN(false);
}


/*
  Adds an event to the scheduler queue

  Synopsis
    Event_scheduler::add_event()
      et   The event to add

  Returns
    OP_OK             OK or scheduler not working
    OP_LOAD_ERROR     Error during loading from disk
*/

enum Event_scheduler::enum_error_code
Event_scheduler::add_event(THD *thd, Event_timed *et, bool check_existance)
{
  enum enum_error_code res;
  Event_timed *et_new;
  DBUG_ENTER("Event_scheduler::add_event");
  DBUG_PRINT("enter", ("thd=%p et=%p lock=%p",thd,et,&LOCK_scheduler_data));

  lock_mutex(&LOCK_scheduler_data);
  if (state != RUNNING)
  {
    DBUG_PRINT("info", ("manager not running but %d. doing nothing", state));
    unlock_mutex(&LOCK_scheduler_data);
    DBUG_RETURN(OP_OK);
  }
  check_n_wait_for_bulk_drop_finished(thd);
  if (check_existance && find_event(et, false))
  {
    res= OP_ALREADY_EXISTS;
    goto end;
  }

  /* We need to load the event on scheduler_root */
  if (!(res= load_and_compile_event(thd, et, &et_new)))
  {
    queue_insert_safe(&queue, (byte *) et_new);
    DBUG_PRINT("info", ("Sending COND_new_work"));
    pthread_cond_signal(&COND_new_work);
  }
end:
  unlock_mutex(&LOCK_scheduler_data);
  DBUG_RETURN(res);
}


/*
  Drops an event from the scheduler queue

  Synopsis
    Event_scheduler::drop_event()
      etn   The event to drop
      state  Wait the event or kill&drop

  Returns
    FALSE OK (replaced or scheduler not working)
    TRUE  Failure
*/

bool
Event_scheduler::drop_event(THD *thd, Event_timed *et)
{
  Event_timed *et_old;
  DBUG_ENTER("Event_scheduler::drop_event");
  DBUG_PRINT("enter", ("thd=%p et=%p lock=%p",thd,et,&LOCK_scheduler_data));
  lock_mutex(&LOCK_scheduler_data);
  if (state != RUNNING)
  {
    DBUG_PRINT("info", ("manager not running but %d. doing nothing", state));
    unlock_mutex(&LOCK_scheduler_data);
    DBUG_RETURN(false);
  }
  check_n_wait_for_bulk_drop_finished(thd);

  if (!(et_old= find_event(et, true)))
    DBUG_PRINT("info", ("No such event found, probably DISABLED"));

  unlock_mutex(&LOCK_scheduler_data);

  /* See comments in ::replace_event() why this is split in two parts. */
  if (et_old)
  {
    switch (et_old->kill_thread(thd)) {
    case EVEX_CANT_KILL:
      /* Don't delete but continue */
      et_old->flags |= EVENT_FREE_WHEN_FINISHED;
      break;
    case 0:
      /* 
        kill_thread() waits till the spawned thread finishes after it's
        killed. Hence, we delete here memory which is no more referenced from
        a running thread.
      */
      delete et_old;
      /*
        We don't signal COND_new_work here because:
        1. Even if the dropped event is on top of the queue this will not
          move another one to be executed before the time the one on the
          top (but could be at the same second as the dropped one)
        2. If this was the last event on the queue, then pthread_cond_timedwait
          in ::run() will finish and then see that the queue is empty and
          call cond_wait(). Hence, no need to interrupt the blocked
          ::run() thread.
      */
      break;
    default:
      DBUG_ASSERT(0);
    }
  }

  DBUG_RETURN(false);
}


/*
  Replaces an event in the scheduler queue

  Synopsis
    Event_scheduler::replace_event()
      et    The event to replace(add) into the queue
      state  Async or sync stopping

  Returns
    OP_OK             OK or scheduler not working
    OP_LOAD_ERROR     Error during loading from disk
    OP_ALREADY_EXISTS Event already in the queue    
*/

enum Event_scheduler::enum_error_code
Event_scheduler::replace_event(THD *thd, Event_timed *et)
{
  enum enum_error_code res;
  Event_timed *et_old, *et_new= NULL;

  DBUG_ENTER("Event_scheduler::replace_event");
  DBUG_PRINT("enter", ("thd=%p et=%p lock=%p",thd,et,&LOCK_scheduler_data));

  lock_mutex(&LOCK_scheduler_data);
  if (state != RUNNING)
  {
    DBUG_PRINT("info", ("manager not running but %d. doing nothing", state));
    unlock_mutex(&LOCK_scheduler_data);
    DBUG_RETURN(OP_OK);
  }
  check_n_wait_for_bulk_drop_finished(thd);

  if (!(et_old= find_event(et, true)))
    DBUG_PRINT("info", ("%s.%s not found cached, probably was DISABLED",
                        et->dbname.str, et->name.str));

  /* 
    We need to load the event (it's strings but on the object itself)
    on scheduler_root. et_new could be NULL :
    1. Error occured
    2. If the replace is DISABLED, we don't load it into the queue.
  */
  if (!(res= load_and_compile_event(thd, et, &et_new)))
  {
    queue_insert_safe(&queue, (byte *) et_new);
    DBUG_PRINT("info", ("Sending COND_new_work"));
    pthread_cond_signal(&COND_new_work);
  }
  else if (res == OP_DISABLED_EVENT)
    res= OP_OK;

  unlock_mutex(&LOCK_scheduler_data);
  /* 
    We don't move this code above because a potential kill_thread will call
    THD::awake(). Which in turn will try to acqure mysys_var->current_mutex,
    which is LOCK_scheduler_data on which the COND_new_work in ::run() locks.
    Hence, we try to acquire a lock which we have already acquired and we run
    into an assert. Holding LOCK_scheduler_data however is not needed because
    we don't touch any invariant of the manager anymore. ::drop_event() does
    the same.
  */
  if (et_old)
  {
    switch (et_old->kill_thread(thd)) {
    case EVEX_CANT_KILL:
      /* Don't delete but continue */
      et_old->flags |= EVENT_FREE_WHEN_FINISHED;
      break;
    case 0:
      /* 
        kill_thread() waits till the spawned thread finishes after it's
        killed. Hence, we delete here memory which is no more referenced from
        a running thread.
      */
      delete et_old;
      /*
        We don't signal COND_new_work here because:
        1. Even if the dropped event is on top of the queue this will not
          move another one to be executed before the time the one on the
          top (but could be at the same second as the dropped one)
        2. If this was the last event on the queue, then pthread_cond_timedwait
          in ::run() will finish and then see that the queue is empty and
          call cond_wait(). Hence, no need to interrupt the blocked
          ::run() thread.
      */
      break;
    default:
      DBUG_ASSERT(0);
    }
  }

  DBUG_RETURN(res);
}


/*
  Searches for an event in the scheduler queue

  Synopsis
    Event_scheduler::find_event()
      etn            The event to find
      comparator     The function to use for comparing
      remove_from_q  If found whether to remove from the Q

  Returns
    NULL       Not found
    otherwise  Address
    
  Notes
    The caller should do the locking also the caller is responsible for
    actual signalling in case an event is removed from the queue 
    (signalling COND_new_work for instance).
*/

Event_timed *
Event_scheduler::find_event(Event_timed *etn, bool remove_from_q)
{
  uint i;
  DBUG_ENTER("Event_scheduler::find_event");

  for (i= 0; i < queue.elements; ++i)
  {
    Event_timed *et= (Event_timed *) queue_element(&queue, i);
    DBUG_PRINT("info", ("[%s.%s]==[%s.%s]?", etn->dbname.str, etn->name.str,
                        et->dbname.str, et->name.str));
    if (event_timed_identifier_equal(etn, et))
    {
      if (remove_from_q)
        queue_remove(&queue, i);
      DBUG_RETURN(et);
    }
  }

  DBUG_RETURN(NULL);
}


/*
  Drops all events from the in-memory queue and disk that match
  certain pattern evaluated by a comparator function

  Synopsis
    Event_scheduler::drop_matching_events()
      thd            THD
      pattern        A pattern string
      comparator     The function to use for comparing

  Returns
     -1  Scheduler not working
    >=0  Number of dropped events
    
  Note
    Expected is the caller to acquire lock on LOCK_scheduler_data
*/

void
Event_scheduler::drop_matching_events(THD *thd, LEX_STRING *pattern,
                           bool (*comparator)(Event_timed *,LEX_STRING *))
{
  DBUG_ENTER("Event_scheduler::drop_matching_events");
  DBUG_PRINT("enter", ("pattern=%*s state=%d", pattern->length, pattern->str,
             state));
  if (state == RUNNING)
  {
    uint i= 0, dropped= 0;   
    while (i < queue.elements)
    {
      Event_timed *et= (Event_timed *) queue_element(&queue, i);
      DBUG_PRINT("info", ("[%s.%s]?", et->dbname.str, et->name.str));
      if (comparator(et, pattern))
      {
        /*
          The queue is ordered. If we remove an element, then all elements after
          it will shift one position to the left, if we imagine it as an array
          from left to the right. In this case we should not increment the 
          counter and the (i < queue.elements) condition is ok.
        */
        queue_remove(&queue, i);

        /* See replace_event() */
        switch (et->kill_thread(thd)) {
        case EVEX_CANT_KILL:
          /* Don't delete but continue */
          et->flags |= EVENT_FREE_WHEN_FINISHED;
          ++dropped;
          break;
        case 0:
          delete et;
          ++dropped;
          break;
        default:
          DBUG_ASSERT(0);
        }
      }
      else
        i++;
    }
    DBUG_PRINT("info", ("Dropped %lu", dropped));
  }
  /*
    Don't send COND_new_work because no need to wake up the manager thread.
    When it wakes next time up it will recalculate how much more it should
    sleep if the top of the queue has been changed by this method.
  */

  DBUG_VOID_RETURN;
}


/*
  Drops all events from the in-memory queue and disk that are from
  certain schema.

  Synopsis
    Event_scheduler::drop_schema_events()
      thd        THD
      db         The schema name

  Returns
     -1  Scheduler not working
    >=0  Number of dropped events
  
  Note 
*/

int
Event_scheduler::drop_schema_events(THD *thd, LEX_STRING *schema,
                                    uint *dropped_num)
{
  int ret;
  DBUG_ENTER("Event_scheduler::drop_schema_events");
  lock_mutex(&LOCK_scheduler_data);
  bulk_drop= true;
  unlock_mutex(&LOCK_scheduler_data);

  drop_matching_events(thd, schema, event_timed_db_equal);
  ret= db_drop_events_from_table(thd, schema, dropped_num);

  lock_mutex(&LOCK_scheduler_data);
  bulk_drop= false;
  pthread_cond_broadcast(&COND_bulk_drop_finished);
  unlock_mutex(&LOCK_scheduler_data);

  DBUG_RETURN(ret);
}


extern pthread_attr_t connection_attrib;

/*
  Starts the event scheduler

  Synopsis
    Event_scheduler::start()

  Returns
    OP_OK               OK
    OP_CANTFORK         Cannot create a new thread
    OP_CANTSTART        ::run() had problem booting
    OP_ALREADY_RUNNING  Manager already running
*/

enum Event_scheduler::enum_error_code
Event_scheduler::start()
{
  enum enum_error_code ret;
  pthread_t th;
  DBUG_ENTER("Event_scheduler::start");

  lock_mutex(&LOCK_scheduler_data);
  /* If already working or starting don't make another attempt */
  if (state > INITIALIZED)
  {
    DBUG_PRINT("info", ("manager is already running or starting"));
    ret= OP_ALREADY_RUNNING;
    goto finish;
  }

  /*
    Now if another thread calls start it will bail-out because the branch
    above will be executed. Thus no two or more child threads will be forked.
    If the child thread cannot start for some reason then `state` is set
    to CANTSTART and COND_started is also signaled. In this case we
    set `state` back to INITIALIZED so another attempt to start the manager
    can be made.
  */
  state= COMMENCING;
  manager_working= TRUE;
  /* Fork */
  if (pthread_create(&th, &connection_attrib, event_scheduler_run_proxy,
                    (void*)this))
  {
    DBUG_PRINT("error", ("cannot create a new thread"));
    state= INITIALIZED;
    ret= OP_CANTFORK;
    goto finish;
  }

  /*  Wait till the child thread has booted (w/ or wo success) */
  while (state != RUNNING && state != CANTSTART)
    cond_wait(&COND_started, &LOCK_scheduler_data);

  /*
    If we cannot start for some reason then don't prohibit further attempts.
    Set back to INITIALIZED.
  */
  if (state == CANTSTART)
  {
    state= INITIALIZED;
    manager_working= FALSE;
    ret= OP_CANTSTART;
    goto finish;
  }
  ret= OP_OK;

finish:
  unlock_mutex(&LOCK_scheduler_data);
  DBUG_RETURN(ret);
}


/*
  The internal loop of the event scheduler

  Synopsis
    Event_scheduler::run()
      thd  Thread

  Returns
    false OK
    true  Failure
*/

bool
Event_scheduler::run(THD *thd)
{
  int ret;
  struct timespec abstime;
  DBUG_ENTER("Event_scheduler::run");
  DBUG_PRINT("enter",("thd=%p", thd));

  THD_CHECK_SENTRY(thd);
  /*
    Load events from disk. `state` is currently COMMENCING. Hence,
    there is no race between loading and add_event() because the latter
    won't touch the queue until the state is not RUNNING, which is
    set a bit later in this function.
  */
  ret= load_events_from_db(thd);

  lock_mutex(&LOCK_scheduler_data);
  if (!ret)
  {
    thread_id= thd->thread_id;
    state= RUNNING;
  }
  else 
    state= CANTSTART;

  THD_CHECK_SENTRY(thd);
  DBUG_PRINT("info", ("Sending back COND_started"));
  pthread_cond_signal(&COND_started);
  unlock_mutex(&LOCK_scheduler_data);
  if (ret)
    DBUG_RETURN(true);

  sql_print_information("SCHEDULER: Manager thread started with id %lu",
                        thd->thread_id);
  abstime.tv_nsec= 0;
  while (!thd->killed)
  {
    TIME time_now_utc;
    Event_timed *et;
    my_bool tmp;
    time_t now_utc;

    lock_mutex(&LOCK_scheduler_data);
    if (bulk_drop)
      check_n_wait_for_bulk_drop_finished(thd);

    /* Wait in a loop protecting against catching spurious signals */
    while (!queue.elements && (!thd->killed && state == RUNNING))
    {
      DBUG_PRINT("info", ("Entering condition because of empty queue"));
      thd->enter_cond(&COND_new_work, &LOCK_scheduler_data, "Empty queue, sleeping");
      cond_wait(&COND_new_work, &LOCK_scheduler_data);
      DBUG_PRINT("info", ("Manager woke up. Hope we have events now. state=%d",
                 state));
      /*
        exit_cond does implicit mutex_unlock, we needed it locked if
        1. we loop again
        2. end the current loop and start doing calculations
      */
      thd->exit_cond("");
      lock_mutex(&LOCK_scheduler_data);
    }
    /* Guaranteed locked here */
    if (state != RUNNING || thd->killed)
    {
      unlock_mutex(&LOCK_scheduler_data);
      break;
    }

    et= (Event_timed *)queue_top(&queue);
    
    /* Skip disabled events */
    if (et->status != MYSQL_EVENT_ENABLED)
    {
      DBUG_ASSERT(0);
      sql_print_error("SCHEDULER: Found a disabled event %*s.%*s in the queue",
                      et->dbname.length, et->dbname.str, et->name.length,
                      et->name.str);
      queue_remove(&queue, 0);
      /* ToDo check this again */
      delete et;
      unlock_mutex(&LOCK_scheduler_data);
      continue;
    }
    thd->proc_info= (char *)"Computing";
    DBUG_PRINT("evex manager",("computing time to sleep till next exec"));
    /* This timestamp is in UTC */
    abstime.tv_sec= sec_since_epoch_TIME(&et->execute_at);

    thd->end_time();
    if (abstime.tv_sec > thd->query_start())
    {
      /* Event trigger time is in the future */
      thd->proc_info= (char *)"Sleep";
      DBUG_PRINT("info", ("Going to sleep. Should wakeup after approx %d secs",
                         abstime.tv_sec - thd->query_start()));
      DBUG_PRINT("info", ("Entering condition because waiting for activation"));
      /*
        Use THD::enter_cond()/exit_cond() or we won't be able to kill a
        sleeping thread. Though ::shutdown() can do it by sending COND_new_work
        an user can't by just issuing 'KILL x'; . In the latter case
        pthread_cond_timedwait() will wait till `abstime`.
        "Sleeping until next time"
      */
      thd->enter_cond(&COND_new_work, &LOCK_scheduler_data, "Sleeping");

      pthread_cond_timedwait(&COND_new_work, &LOCK_scheduler_data, &abstime);

      DBUG_PRINT("info", ("Manager woke up. state is %d", state));
      /*
        If we get signal we should recalculate the whether it's the right time
        because there could be :
        1. Spurious wake-up
        2. The top of the queue was changed (new one becase of add/drop/replace)

      */
      /* This will do implicit unlock_mutex(&LOCK_scheduler_data) */
      thd->exit_cond("");
    }
    else
    {
      thd->proc_info= (char *)"Executing";
      /* 
        Execute the event. An error may occur if a thread cannot be forked.
        In this case stop  the manager.
        We should enter ::execute_top() with locked LOCK_scheduler_data.
      */
      int ret= execute_top(thd);
      unlock_mutex(&LOCK_scheduler_data);
      if (ret)
        break;
    }
  }
end_loop:
  thd->proc_info= (char *)"Cleaning";

  lock_mutex(&LOCK_scheduler_data);
  /*
    It's possible that a user has used (SQL)COM_KILL. Hence set the appropriate
    state because it is only set by ::shutdown().
  */
  if (state != IN_SHUTDOWN)
  {
    DBUG_PRINT("info", ("We got KILL but the but not from ::shutdown()"));
    state= IN_SHUTDOWN;
  }
  unlock_mutex(&LOCK_scheduler_data);

  sql_print_information("SCHEDULER: Shutting down");

  thd->proc_info= (char *)"Cleaning queue";
  clean_queue(thd);
  THD_CHECK_SENTRY(thd);

  /* free mamager_root memory but don't destroy the root */
  thd->proc_info= (char *)"Cleaning memory root";
  free_root(&scheduler_root, MYF(0));
  THD_CHECK_SENTRY(thd);

  /*
    We notify the waiting thread which shutdowns us that we have cleaned.
    There are few more instructions to be executed in this pthread but
    they don't affect manager structures thus it's safe to signal already
    at this point.
  */
  lock_mutex(&LOCK_scheduler_data);
  thd->proc_info= (char *)"Sending shutdown signal";
  DBUG_PRINT("info", ("Sending COND_shutdown"));
  if (state == IN_SHUTDOWN)
    pthread_cond_signal(&COND_shutdown);

  /*
    Don't set state to STOPPED because ::start() checks
    (state > INITIALIZED) to decide whether it can fork a new thread or return.
    STOPPED is used by the dtor of Event_scheduler, thus
    preventing a fork of a new thread while the manager object is being
    destroyed.
  */
  state= INITIALIZED;
  /*
    We set it here because ::run() can stop not only because of ::shutdown()
    call but also because of `KILL x`
  */
  manager_working= FALSE;
  thread_id= 0;
  sql_print_information("SCHEDULER: Stopped");
  unlock_mutex(&LOCK_scheduler_data);

  /* We have modified, we set back */
  thd->query= NULL;
  thd->query_length= 0;

  DBUG_RETURN(false);
}


/*
  Executes the top element of the queue. Auxiliary method for ::run().

  Synopsis
    Event_scheduler::execute_top()

  Notes
    NO locking is done. EXPECTED is that the caller should have locked
    the queue (w/ LOCK_scheduler_data).

  Returns
    FALSE OK
    TRUE  Failure
*/

bool
Event_scheduler::execute_top(THD *thd)
{
  int fork_ret_code;
  DBUG_ENTER("Event_scheduler::execute_top");
  DBUG_PRINT("enter", ("thd=%p", thd));

  Event_timed *et= (Event_timed *)queue_top(&queue);

  DBUG_PRINT("info", ("SCHEDULER: execute_at of %s is %lld", et->name.str,
                       TIME_to_ulonglong_datetime(&et->execute_at)));
  et->mark_last_executed(thd);
  if (et->compute_next_execution_time())
  {
    sql_print_error("SCHEDULER: Error while computing time of %s.%s . "
                    "Disabling after execution.",
                    et->dbname.str, et->name.str);
    et->status= MYSQL_EVENT_DISABLED;
  }
  DBUG_PRINT("evex manager", ("[%10s] next exec at [%llu]", et->name.str,
             TIME_to_ulonglong_datetime(&et->execute_at)));

  et->update_fields(thd);

  /*
    1. For one-time event : year is > 0 and expression is 0
    2. For recurring, expression is != -=> check execute_at_null in this case
  */
  if ((et->execute_at.year && !et->expression) || et->execute_at_null)
    et->flags |= EVENT_EXEC_NO_MORE;

  if ((et->flags & EVENT_EXEC_NO_MORE) || et->status == MYSQL_EVENT_DISABLED)
    queue_remove(&queue, 0);// 0 is top, internally 1
  else
   queue_replaced(&queue);

  /* Is it good idea to pass a stack address ?*/
  Worker_thread_param param(et);
  
  lock_mutex(&param.LOCK_started);
  /* 
    We don't lock LOCK_scheduler_data fpr workers_increment() because it's a
    pre-requisite for calling the current_method.
  */
  workers_increment();
  switch ((fork_ret_code= et->spawn_now(event_worker_thread, &param))) {
  case EVENT_EXEC_CANT_FORK:
    /* 
      We don't lock LOCK_scheduler_data here because it's a pre-requisite
      for calling the current_method.
      We pass 0 to workers_decrement() because there was no thread forked and
      only workers_count should be updated but not the `workers` dynamic array
    */
    workers_decrement();
    sql_print_error("SCHEDULER: Problem while trying to create a thread");
    DBUG_RETURN(true);
  case EVENT_EXEC_ALREADY_EXEC:
    /* 
      We don't lock LOCK_scheduler_data here because it's a pre-requisite
      for calling the current_method.
      We pass 0 to workers_decrement() because there was no thread forked and
      only workers_count should be updated but not the `workers` dynamic array
    */
    workers_decrement();
    sql_print_information("SCHEDULER: %s.%s in execution. Skip this time.",
                          et->dbname.str, et->name.str);
    break;
  default:
    DBUG_ASSERT(!fork_ret_code);
    /* 
      We don't lock LOCK_scheduler_data here because it's a pre-requisite
      for calling the current_method.
      We pass 0 to workers_decrement() because there was no thread forked and
      only workers_count should be updated but not the `workers` dynamic array
    */
    if (unlikely(fork_ret_code))
      workers_decrement();
    else
    {
      /* Wait the forked thread to start */
      cond_wait(&param.COND_started, &param.LOCK_started);
#ifndef DBUG_OFF
      lock_mutex(&LOCK_executed);
      pthread_cond_signal(&COND_executed);
      unlock_mutex(&LOCK_executed);
#endif
    }
    /*
      param was allocated on the stack so no explicit delete as well as
      in this moment it's no more used in the spawned thread so it's safe
      to be deleted.
    */
    break;
  }
  unlock_mutex(&param.LOCK_started);

   
  DBUG_RETURN(false);
}


/*
  Cleans the scheduler's queue. Auxiliary method for ::run().

  Synopsis
    Event_scheduler::clean_queue()
      thd  Thread
*/

void
Event_scheduler::clean_queue(THD *thd)
{
  uint i;
  int ret;
  DBUG_ENTER("Event_scheduler::clean_queue");
  DBUG_PRINT("enter", ("thd=%p", thd));

  lock_mutex(&LOCK_scheduler_data);
  DBUG_PRINT("info", ("workers_count=%d workers.elements=%lu", workers_count,
             workers.elements));
  if (workers_count)
  {
    bool had_super= false;
    /*
      ToDo: This is a bit suboptimal because we try to kill everything, even
      non-running events.
    */
    for (i= 0; i < workers.elements; ++i)
      DBUG_PRINT("info", ("working_thread#%d=%lu", i,
                 *dynamic_element(&workers, i, ulong*)));
    /* We need temporarily SUPER_ACL to be able to kill our offsprings */
    if (!(thd->security_ctx->master_access & SUPER_ACL))
      thd->security_ctx->master_access|= SUPER_ACL;
    else
      had_super= true;
    
    /*
      Should be initialized to 0 because if workers_count is > 0 and
      w.elements =0 then kill_one_thread won't be called. And the
      do-while loop a bit later need it.
    */
    ret= 0;
    for (i= 0; i < workers.elements; ++i)
    {
      sql_print_information("SCHEDULER: Killing worker thread %lu",
                            *dynamic_element(&workers, i, ulong*));
      if ((ret= kill_one_thread(thd, *dynamic_element(&workers, i, ulong*),
                                false)))
      {
        sql_print_error("SCHEDULER: Error while killing code=%d", ret);
        break;
      }
    }
    if (!had_super)
      thd->security_ctx->master_access &= ~SUPER_ACL;

#ifndef DBUG_OFF
    if ((uint)workers_count > workers.elements)
      DBUG_PRINT("info", ("A thread was started but had not chance "
                          "to register itself"));
#endif
    sql_print_information("SCHEDULER: Waiting for worker threads to finish");
    /* Do it in a loop against spurious wakeups */
    if (!ret)
    {
      do {
        DBUG_PRINT("info", ("workers_count=%d", workers_count));
        cond_wait(&COND_last_worker_exit, &LOCK_scheduler_data);
      } while (workers_count);
      DBUG_PRINT("info", ("Got COND_last_worker_exit from the last thread"));
    }
  }
  unlock_mutex(&LOCK_scheduler_data);

  delete_dynamic(&workers);

  sql_print_information("SCHEDULER: Emptying the queue");

  /* empty the queue */
  for (i= 0; i < queue.elements; ++i)
  {
    Event_timed *et= (Event_timed *) queue_element(&queue, i);
    et->free_sp();
    delete et;
  }
  resize_queue(&queue, 0);

  DBUG_VOID_RETURN;
}


/*
  Shutdowns the event scheduler

  Synopsis
    Event_scheduler::shutdown()

  Returns
    OP_OK           OK
    OP_CANT_KILL    Error during stopping of manager thread
    OP_NOT_RUNNING  Manager not working
*/

enum Event_scheduler::enum_error_code
Event_scheduler::shutdown()
{
  int ret;
  THD *thd= current_thd;
  DBUG_ENTER("Event_scheduler::shutdown");
  DBUG_PRINT("enter", ("thd=%p", current_thd));

  lock_mutex(&LOCK_scheduler_data);
  if (state != RUNNING)
  {
    DBUG_PRINT("info", ("manager not running but %d. doing nothing", state));
    unlock_mutex(&LOCK_scheduler_data);
    DBUG_RETURN(OP_NOT_RUNNING);
  }
  state= IN_SHUTDOWN;

  DBUG_PRINT("info", ("Manager thread has id %d", thread_id));
  sql_print_information("SCHEDULER: Killing manager thread %lu", thread_id);
  
  /* 
    Sending the COND_new_work to ::run() is a way to get this working without
    race conditions. If we use kill_one_thread() it will call THD::awake() and
    because in ::run() both THD::enter_cond()/::exit_cond() are used,
    THD::awake() will try to lock LOCK_scheduler_data. If we unlock it before
    then the pthread_cond_signal(COND_shutdown) could be signaled in ::run()
    and we can miss the signal before we relock. A way is to use another mutex
    for this shutdown procedure but better not.
  */
  pthread_cond_signal(&COND_new_work);

  /* Guarantee we don't catch spurious signals */
  sql_print_information("SCHEDULER: Waiting the manager thread to reply");
  while (state != INITIALIZED)
  {
    DBUG_PRINT("info", ("Waiting for COND_shutdown from the manager thread."
               " Current value of state is %d . workers_count=%d", state,
               workers_count));
    cond_wait(&COND_shutdown, &LOCK_scheduler_data);
  }
  DBUG_PRINT("info", ("Manager thread has cleaned up. Set state to INIT"));
  unlock_mutex(&LOCK_scheduler_data);

  DBUG_RETURN(OP_OK);
}


/*
  Handles updates of @@event_scheduler

  Synopsis
    Event_scheduler::event_scheduler_var_update()

  Returns
    FALSE  OK
    TRUE   Error
*/

enum Event_scheduler::enum_error_code
Event_scheduler::start_or_stop(my_bool new_value)
{
  Event_scheduler *scheduler;
  enum enum_error_code res;
  DBUG_ENTER("Event_scheduler::start_or_stop");

  scheduler= Event_scheduler::get_instance();
  DBUG_ASSERT(scheduler);

  if (new_value == Event_scheduler::manager_working)
  {
    DBUG_PRINT("info", ("The new value is the same as the old"));
    DBUG_RETURN(OP_OK);
  }

  /*
    ::start()/::shutdown() will serialize the access. Both methods don't
    allow parallel execution in them.
  */
  DBUG_PRINT("info", ("%s the manager", new_value? "starting":"stopping"));
  DBUG_RETURN(new_value? scheduler->start():scheduler->shutdown());
}


/*
  Increments the number of running worker threads

  Synopsis
    Event_scheduler::workers_increment()

  NOTE
    The caller should have locked LOCK_scheduler_data!
*/

inline void
Event_scheduler::workers_increment()
{
  DBUG_ENTER("Event_scheduler::workers_increment");
  DBUG_PRINT("info", ("state=%d", singleton->state));
  DBUG_ASSERT(singleton->state == RUNNING);
  ++workers_count;
  DBUG_PRINT("info", ("workers_count=%d", workers_count));
  DBUG_VOID_RETURN;
}


/*
  Decrements the number of running worker threads

  Synopsis
    Event_scheduler::workers_decrement()

  NOTE
    The caller should have locked LOCK_scheduler_data!
*/

inline void
Event_scheduler::workers_decrement()
{
  DBUG_ENTER("Event_scheduler::workers_decrement");

  if (state == RUNNING || state == IN_SHUTDOWN)
    --workers_count;
  DBUG_PRINT("info", ("workers_count=%d", workers_count));
  DBUG_VOID_RETURN;
}


/*
  Checks and waits if bulk drop is being performed

  Synopsis
    Event_scheduler::check_n_wait_for_bulk_drop_finished()
      thd  Thread

  NOTE
    The caller should have locked LOCK_scheduler_data!
*/

inline void
Event_scheduler::check_n_wait_for_bulk_drop_finished(THD *thd)
{
  DBUG_ENTER("Event_scheduler::check_n_wait_for_bulk_drop_finished");
  while (bulk_drop)
  {
    thd->proc_info= (char*) "Waiting for bulk_drop_finished";
    cond_wait(&COND_bulk_drop_finished, &LOCK_scheduler_data);
    thd->proc_info= (char*) "";
  }
  DBUG_VOID_RETURN;
}


/*
  Wrapper for pthread_mutex_lock

  Synopsis
    Event_scheduler::lock_mutex()
      mutex Mutex to lock
*/

inline int
Event_scheduler::lock_mutex(pthread_mutex_t *mutex)
{
  DBUG_ENTER("Event_scheduler::lock_mutex");
  DBUG_PRINT("enter", ("mutex_lock=%p", mutex));
  DBUG_RETURN(pthread_mutex_lock(mutex));
}


/*
  Wrapper for pthread_mutex_unlock

  Synopsis
    Event_scheduler::unlock_mutex()
      mutex Mutex to unlock
*/

inline int
Event_scheduler::unlock_mutex(pthread_mutex_t *mutex)
{
  DBUG_ENTER("Event_scheduler::unlock_mutex");
  DBUG_PRINT("enter", ("mutex_unlock=%p", mutex));
  DBUG_RETURN(pthread_mutex_unlock(mutex));
}


/*
  Wrapper for pthread_cond_wait

  Synopsis
    Event_scheduler::cond_wait()
      cond   Conditional to wait for
      mutex  Mutex of the conditional
*/

inline int
Event_scheduler::cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
{
  DBUG_ENTER("Event_scheduler::cond_wait");
  DBUG_PRINT("enter", ("cond_wait=%p mutex_wait=%p", cond, mutex));
  DBUG_RETURN(pthread_cond_wait(cond, mutex));
}


/*
  Increments the number of running worker threads

  Synopsis
    Event_scheduler::register_worker_thread()
      thd_id  The id of the thread that starts

  Returns
    OP_OK           Ok
    OP_NOT_RUNNING  Scheduler not working
*/

enum Event_scheduler::enum_error_code
Event_scheduler::register_worker_thread(ulong thd_id)
{
  enum Event_scheduler::enum_error_code ret= OP_OK;

  DBUG_ENTER("Event_scheduler::register_worker_thread");
  DBUG_PRINT("enter", ("thd=%p thd_id=%lu", current_thd, thd_id));
  Event_scheduler *scheduler= get_instance();
  DBUG_ASSERT(scheduler);
  
  lock_mutex(&scheduler->LOCK_scheduler_data);
  if (scheduler->state != RUNNING)
  {
    /*
      If the server is being shut down don't attempt to execute but
      return an error. Or if not running at all.
    */
    ret= OP_NOT_RUNNING;
  }
  else
  {
    /* 
      Don't call singleton->workers_increment() because the count was already
      incremented before pthread_create() was called.
    */
    DBUG_PRINT("info", ("Adding %lu to the list of running events. "
               "Current workers_count=%d", thd_id, scheduler->workers_count));
    push_dynamic(&scheduler->workers, (gptr) &thd_id);
  }
  unlock_mutex(&scheduler->LOCK_scheduler_data);

  DBUG_RETURN(ret);
}


/*
  Decrements the number of running worker threads

  Synopsis
    Event_scheduler::deregister_worker_thread()
      thd_id  The id of the thread that finishes

  Returns
    OP_OK           Ok
    OP_NOT_RUNNING  Scheduler not working

  Note
    If thd_id wasn't found no error is generated. Thus if
    register_worker_thread() has failed for some reason it's safe to call
    this method.
*/

enum Event_scheduler::enum_error_code
Event_scheduler::deregister_worker_thread(ulong thd_id)
{
  enum Event_scheduler::enum_error_code ret= OP_OK;
  Event_scheduler *scheduler= singleton;

  DBUG_ENTER("Event_scheduler::deregister_worker_thread");
  DBUG_PRINT("enter", ("thd=%p thd_id=%lu", current_thd, thd_id));

  lock_mutex(&scheduler->LOCK_scheduler_data);
  DBUG_ASSERT(scheduler->state >= RUNNING);
  if (likely(scheduler->state >= RUNNING))
  {
    register uint i= 0;
    register ulong tid= thd_id;

    scheduler->workers_decrement();
    for (; i < scheduler->workers.elements; ++i)
      if (*dynamic_element(&scheduler->workers, i, ulong*) == tid)
      {
        delete_dynamic_element(&scheduler->workers, i);
        break;
      }
    /*
      The numbers can be different if 2 threads are spawned but only 1
      was executed and the second one is waiting. So w_c=1 and w.elements=0
      for example is ok.
      We signal on scheduler->workers_count because this is
      the way to determine if there are started threads. workers.elements
      just keeps track of the thread ids. Registering happens after thread
      was started and there could be some delay.
    */
    DBUG_PRINT("info", ("workers_count=%d workers.elements=%lu",
               scheduler->workers_count, scheduler->workers.elements));
    if (scheduler->state == IN_SHUTDOWN && !scheduler->workers_count)
    {
      DBUG_PRINT("info", ("Sending out COND_last_worker_exit"));
      pthread_cond_signal(&scheduler->COND_last_worker_exit);
    }
  }
  else
    ret= OP_NOT_RUNNING;
  unlock_mutex(&scheduler->LOCK_scheduler_data);

  DBUG_RETURN(ret);
}


/*
  Returns the number of elements in the queue

  Synopsis
    Event_scheduler::events_count()

  Returns
    -1   Manager not working
    >=0  Number of Event_timed objects in the queue
*/

int
Event_scheduler::events_count()
{
  int n;
  DBUG_ENTER("Event_scheduler::events_count");
  lock_mutex(&LOCK_scheduler_data);
  if (state == RUNNING)
    n= queue.elements;
  else
    n= -1;
  unlock_mutex(&LOCK_scheduler_data);

  DBUG_RETURN(n);
}


/*
  Looks for a named event in mysql.event and then loads it from
  the table, compiles and inserts it into the cache.

  SYNOPSIS
    Event_scheduler::load_and_compile_event()
      thd      THD
      etn      The name of the event to load and compile on manager's root
      etn_new  The loaded event

  RETURN VALUE
    NULL       Error during compile or the event is non-enabled.
    otherwise  Address
*/

enum Event_scheduler::enum_error_code
Event_scheduler::load_and_compile_event(THD *thd, Event_timed *etn,
                                        Event_timed **etn_new)
{
  int ret= 0;
  MEM_ROOT *tmp_mem_root;
  Event_timed *et_loaded= NULL;
  Open_tables_state backup;

  DBUG_ENTER("Event_scheduler::load_and_compile_event");
  DBUG_PRINT("enter",("thd=%p name:%*s",thd, etn->name.length, etn->name.str));

  thd->reset_n_backup_open_tables_state(&backup);
  /* No need to use my_error() here because db_find_event() has done it */
  {
    sp_name spn(etn->dbname, etn->name);
    ret= db_find_event(thd, &spn, &etn->definer, &et_loaded, NULL,
                       &scheduler_root);
  }
  thd->restore_backup_open_tables_state(&backup);
  /* In this case no memory was allocated so we don't need to clean */
  if (ret)
    DBUG_RETURN(OP_LOAD_ERROR);

  if (et_loaded->status != MYSQL_EVENT_ENABLED)
  {
    /*
      We don't load non-enabled events.
      In db_find_event() `et_new` was allocated on the heap and not on
      scheduler_root therefore we delete it here.
    */
    delete et_loaded;
    DBUG_RETURN(OP_DISABLED_EVENT);
  }

  et_loaded->compute_next_execution_time();
  *etn_new= et_loaded;

  DBUG_RETURN(OP_OK);
}


/*
   Loads all ENABLED events from mysql.event into the prioritized
   queue. Called during scheduler main thread initialization. Compiles
   the events. Creates Event_timed instances for every ENABLED event
   from mysql.event.

   SYNOPSIS
     Event_scheduler::load_events_from_db()
       thd - Thread context. Used for memory allocation in some cases.
     
   RETURNS
     0  OK
    !0  Error (EVEX_OPEN_TABLE_FAILED, EVEX_MICROSECOND_UNSUP, 
               EVEX_COMPILE_ERROR) - in all these cases mysql.event was
               tampered.

   NOTES
     Reports the error to the console
*/

int
Event_scheduler::load_events_from_db(THD *thd)
{
  TABLE *table;
  READ_RECORD read_record_info;
  int ret= -1;
  uint count= 0;
  bool clean_the_queue= false;
  /* Compile the events on this root but only for syntax check, then discard */
  MEM_ROOT boot_root;

  DBUG_ENTER("Event_scheduler::load_events_from_db");
  DBUG_PRINT("enter", ("thd=%p", thd));

  lock_mutex(&LOCK_scheduler_data);
  if (state > COMMENCING)
  {
    DBUG_ASSERT(0);
    sql_print_error("SCHEDULER: Trying to load events while already running.");
    unlock_mutex(&LOCK_scheduler_data);
    DBUG_RETURN(EVEX_GENERAL_ERROR);
  }
  unlock_mutex(&LOCK_scheduler_data);

  if ((ret= evex_open_event_table(thd, TL_READ, &table)))
  {
    sql_print_error("SCHEDULER: Table mysql.event is damaged. Can not open.");
    DBUG_RETURN(EVEX_OPEN_TABLE_FAILED);
  }

  init_alloc_root(&boot_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
  init_read_record(&read_record_info, thd, table ,NULL,1,0);
  while (!(read_record_info.read_record(&read_record_info)))
  {
    Event_timed *et;
    if (!(et= new Event_timed))
    {
      DBUG_PRINT("info", ("Out of memory"));
      clean_the_queue= true;
      break;
    }
    DBUG_PRINT("info", ("Loading event from row."));

    if ((ret= et->load_from_row(&scheduler_root, table)))
    {
      clean_the_queue= true;
      sql_print_error("SCHEDULER: Error while loading from mysql.event. "
                      "Table probably corrupted");
      break;
    }
    if (et->status != MYSQL_EVENT_ENABLED)
    {
      DBUG_PRINT("info",("%s is disabled",et->name.str));
      delete et;
      continue;
    }

    DBUG_PRINT("info", ("Event %s loaded from row. ", et->name.str));

    /* We load only on manager root just to check whether the body compiles */
    switch (ret= et->compile(thd, &boot_root)) {
    case EVEX_MICROSECOND_UNSUP:
      et->free_sp();
      sql_print_error("SCHEDULER: mysql.event is tampered. MICROSECOND is not "
                      "supported but found in mysql.event");
      goto end;
    case EVEX_COMPILE_ERROR:
      sql_print_error("SCHEDULER: Error while compiling %s.%s. Aborting load.",
                      et->dbname.str, et->name.str);
      goto end;
    default:
      /* Free it, it will be compiled again on the worker thread */
      et->free_sp();
      break;
    }

    /* let's find when to be executed */
    if (et->compute_next_execution_time())
    {
      sql_print_error("SCHEDULER: Error while computing execution time of %s.%s."
                      " Skipping", et->dbname.str, et->name.str);
      continue;
    }

    DBUG_PRINT("load_events_from_db", ("Adding %p to the exec list."));
    queue_insert_safe(&queue,  (byte *) et);
    count++;
  }
end:
  end_read_record(&read_record_info);
  free_root(&boot_root, MYF(0));

  if (clean_the_queue)
  {
    for (count= 0; count < queue.elements; ++count)
      queue_remove(&queue, count);
    ret= -1;
  }
  else
  {
    ret= 0;
    sql_print_information("SCHEDULER: Loaded %d event%s", count, (count == 1)?"":"s");
  }

  /* Force close to free memory */
  thd->version--;  

  close_thread_tables(thd);

  DBUG_PRINT("info", ("Status code %d. Loaded %d event(s)", ret, count));
  DBUG_RETURN(ret);
}


/*
  Opens mysql.db and mysql.user and checks whether:
    1. mysql.db has column Event_priv at column 20 (0 based);
    2. mysql.user has column Event_priv at column 29 (0 based);

  Synopsis
    Event_scheduler::check_system_tables()
*/

void
Event_scheduler::check_system_tables()
{
  THD *thd= current_thd;
  TABLE_LIST tables;
  bool not_used;
  Open_tables_state backup;

  /* thd is 0x0 during boot of the server. Later it's !=0x0 */
  if (!thd)
    return;
  DBUG_ENTER("Event_scheduler::check_system_tables");
  DBUG_PRINT("enter", ("thd=%p", thd));

  thd->reset_n_backup_open_tables_state(&backup);

  bzero((char*) &tables, sizeof(tables));
  tables.db= (char*) "mysql";
  tables.table_name= tables.alias= (char*) "db";
  tables.lock_type= TL_READ;

  if (simple_open_n_lock_tables(thd, &tables))
    sql_print_error("Cannot open mysql.db");
  else
  {
    table_check_intact(tables.table, MYSQL_DB_FIELD_COUNT, mysql_db_table_fields,
                       &mysql_db_table_last_check,ER_CANNOT_LOAD_FROM_TABLE);
    close_thread_tables(thd);
  }

  bzero((char*) &tables, sizeof(tables));
  tables.db= (char*) "mysql";
  tables.table_name= tables.alias= (char*) "user";
  tables.lock_type= TL_READ;

  if (simple_open_n_lock_tables(thd, &tables))
    sql_print_error("Cannot open mysql.db");
  else
  {
    if (tables.table->s->fields < 29 ||
      strncmp(tables.table->field[29]->field_name,
              STRING_WITH_LEN("Event_priv")))
      sql_print_error("mysql.user has no `Event_priv` column at position 29");

    close_thread_tables(thd);
  }

  thd->restore_backup_open_tables_state(&backup);

  DBUG_VOID_RETURN;
}




#ifndef DBUG_OFF

#define ERR_W_MSG(cond, msg) \
{ \
  sql_print_information("Testing %s", msg); \
  DBUG_PRINT("info",("Testing %s", msg)); \
  protocol->prepare_for_resend(); \
  protocol->store(msg, strlen(msg), system_charset_info); \
  if (!(cond)){ \
    protocol->store((char*)STRING_WITH_LEN("ERROR"), system_charset_info); \
    ret= protocol->write(); \
    goto err; \
} else {\
    protocol->store((char*)STRING_WITH_LEN("OK"), system_charset_info); \
    ret= protocol->write(); \
    if (ret) \
      goto err; \
  } \
}

bool
Event_scheduler::tests_run(THD *thd)
{
  Protocol *protocol= thd->protocol;
  Event_scheduler *manager;
  List<Item> field_list;
  int ret;
  struct timespec abstime;
  int exec_count=0;

  DBUG_ENTER("evex_manager_tester");

  field_list.push_back(new Item_empty_string("Test", 100));
  field_list.push_back(new Item_empty_string("Result", 5));
  if (protocol->send_fields(&field_list, Protocol::SEND_NUM_ROWS |
                                           Protocol::SEND_EOF))
    DBUG_RETURN(true);

  Event_timed et;
  et.dbname.str= (char *)"test";
  et.dbname.length= strlen("test");
  et.name.str= (char *)"event_name";
  et.name.length= strlen("event_name");
  et.definer.str= (char *)"root@localhost";
  et.definer.length= strlen("root@localhost");
  et.body.str= (char *) "select 1 from dual";
  et.body.length= strlen("select 1 from dual");
  et.expression= 3;
  et.interval= INTERVAL_SECOND;
  et.ends_null= true;

  my_tz_UTC->gmt_sec_to_TIME(&et.starts, time(NULL) + 10);
  Event_timed et2;
  et2.dbname.str= (char *)"test";
  et2.dbname.length= strlen("test");
  et2.name.str= (char *)"event_name2";
  et2.name.length= strlen("event_name2");
  et2.definer.str= (char *)"root@localhost";
  et2.definer.length= strlen("root@localhost");
  et2.body.str= (char *) "select 2 from dual";
  et2.body.length= strlen("select 2 from dual");
  et2.expression= 5;
  et2.interval= INTERVAL_SECOND;
  et2.ends_null= true;
  my_tz_UTC->gmt_sec_to_TIME(&et2.starts, time(NULL) + 15);

  uint tmp;

  /* Force new manager instantiation */
  ERR_W_MSG(!Event_scheduler::destroy(), "Destroy old manager");

  ERR_W_MSG(!Event_scheduler::singleton, "Old manager");
  
  manager= Event_scheduler::get_instance();
  ERR_W_MSG(manager, "Create new manager");

  ERR_W_MSG((manager->state == Event_scheduler::INITIALIZED),
                  "Test manager state ");

  ERR_W_MSG(manager->events_count() == -1, "Test count");

  ERR_W_MSG(!db_create_event(thd, &et, false, &tmp),
                  "Create event on disk");

  ERR_W_MSG(!manager->start(), "Start manager");

  manager->add_event(thd, &et, false);
  ERR_W_MSG(manager->events_count() == 2, "Test count wo check");
  
  manager->add_event(thd, &et, true);
  ERR_W_MSG(manager->events_count() == 2, "Test count with check");

  manager->add_event(thd, &et, false);
  ERR_W_MSG(manager->events_count() == 3, "Test count wo check");


  manager->add_event(thd, &et2, true);
  ERR_W_MSG(manager->events_count() == 3, "Test count after add of et2");

  ERR_W_MSG(!db_create_event(thd, &et2, false, &tmp),
                  "Create event et2 on disk");

  manager->add_event(thd, &et2, true);
  ERR_W_MSG(manager->events_count() == 4,
                  "Test count after add of et2 (again)");

  manager->drop_event(thd, &et);
  ERR_W_MSG(manager->events_count() == 3, "Test delete 1");

  manager->drop_event(thd, &et);
  ERR_W_MSG(manager->events_count() == 2, "Test delete 2");

  manager->drop_event(thd, &et);
  ERR_W_MSG(manager->events_count() == 1, "Test delete 3");

  et2.body.str= (char *) "select 212 from dual";
  et2.body.length= strlen("select 212 from dual");

  manager->replace_event(thd, &et2);
  ERR_W_MSG(manager->events_count() == 1, "Test delete 4");

  /*
    Here we will get an empty line because kill_one_thread() which is
    used by ::shutdown() calls send_eof().
  */
  ERR_W_MSG(!manager->shutdown(), "Shutdown the manager");

  ERR_W_MSG(!et.drop(thd), "et.drop()");
  ERR_W_MSG(!et2.drop(thd), "et2.drop()");


  /* New test starting */


  ERR_W_MSG(!manager->start(), "Start manager again and sleep 2s");
  ERR_W_MSG(manager->start()==
              Event_scheduler::OP_ALREADY_RUNNING,
            "Try to start again");

  lock_mutex(&manager->LOCK_executed);

  ERR_W_MSG(!db_create_event(thd, &et, false, &tmp), "Disk Create event et1");
  ERR_W_MSG(!db_create_event(thd, &et2, false, &tmp),"Disk Create event et2");

  ERR_W_MSG(!manager->add_event(thd, &et, true),  "Add et to the queue, step 1");
  ERR_W_MSG(manager->events_count() == 1, "Add et to queue, step 2");

  ERR_W_MSG(!manager->add_event(thd, &et2, true),  "Add et2 to the queue, step 1");
  ERR_W_MSG(manager->events_count() == 2, "Add et2 to the queue, step 2");

  /* now there should be 2 events */
  
  /* 16 sec test */
  abstime.tv_nsec= 0;
  abstime.tv_sec= time(NULL) + 13;
  while (ETIMEDOUT != pthread_cond_timedwait(&manager->COND_executed,
                                             &manager->LOCK_executed,
                                             &abstime))
  {
    exec_count++;
    sql_print_information("Exec_count=%d", exec_count);
  }

  unlock_mutex(&manager->LOCK_executed);
  sql_print_information("Exec_count=%d", exec_count);
  ERR_W_MSG(exec_count == 8, "Execution count");
  /*
    Here we will get an empty line because kill_one_thread() which is
    used by ::shutdown() calls send_eof().
  */
  ERR_W_MSG(!manager->shutdown(), "Shutdown manager");
  ERR_W_MSG(manager->shutdown()==Event_scheduler::OP_NOT_RUNNING,
            "Shutdown manager again for checking");

  ERR_W_MSG(!Event_scheduler::destroy(), "Destroy new manager");
   
  ERR_W_MSG(!Event_scheduler::singleton, "Singleton");

  ERR_W_MSG(!et.drop(thd), "et.drop()");

  ERR_W_MSG(!et2.drop(thd), "et2.drop()");

  ERR_W_MSG(TRUE, "All Tests");

  send_eof(thd);
  DBUG_RETURN(false);
err:
  manager->shutdown();
  Event_scheduler::destroy();
  send_eof(thd);
  DBUG_RETURN(true);
}

#endif

--- New file ---
+++ sql/event_scheduler.h	06/04/20 18:16:01
/* Copyright (C) 2004-2005 MySQL AB

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

#ifndef _EVENT_SCHEDULER_H_
#define _EVENT_SCHEDULER_H_

class THD;
typedef bool * (*event_timed_identifier_comparator)(Event_timed*, Event_timed*);

/*
  Locking hierarchy:
  ->LOCK_executed (used for counting of forks together with COND_executed)

  ->LOCK_scheduler_data (protects `state`)
  -->LOCK_last_worker_exit (for COND_last_worker_exit)
*/


class Event_scheduler
{
public:
  /* Storage for @@event_scheduler */
  static my_bool manager_working;

  /* Return codes */
  enum enum_error_code
  {
    OP_OK= 0,
    OP_CANTFORK,
    OP_CANTSTART,
    OP_ALREADY_RUNNING,
    OP_NOT_RUNNING,
    OP_CANT_KILL,
    OP_CANT_INIT,
    OP_DISABLED_EVENT,
    OP_LOAD_ERROR,
    OP_ALREADY_EXISTS
  };

  /* Singleton access */
  static Event_scheduler*
  get_instance();

  enum enum_error_code
  add_event(THD *thd, Event_timed *et, bool check_existance);

  bool
  drop_event(THD *thd, Event_timed *et);

  enum enum_error_code
  replace_event(THD *thd, Event_timed *et);

  bool
  trigger_event(THD *thd, Event_timed *etn) { DBUG_ASSERT(0); return true;}

  int
  drop_schema_events(THD *thd, LEX_STRING *schema, uint *dropped_num);

  int
  drop_user_events(THD *thd, LEX_STRING *definer, uint *dropped_num) { DBUG_ASSERT(0); return 0;}

  int
  events_count();

  enum enum_error_code
  start();

  bool
  run(THD *thd);

  enum enum_error_code
  shutdown();

  /* Call this only at the end of the server. */
  static bool
  destroy();

  static enum enum_error_code
  start_or_stop(my_bool new_value);

  static enum enum_error_code
  register_worker_thread(ulong thd_id);

  static enum enum_error_code
  deregister_worker_thread(ulong thd_id);

#ifndef DBUG_OFF
  static bool
  tests_run(THD *thd);
  
  pthread_cond_t COND_executed;
  pthread_mutex_t LOCK_executed;
#endif

protected:
  bool 
  init();

  /*
    LOCKING PROTOCOL: Does not do any locking. The caller is responsible to
                      lock and signal.
  */
  Event_timed *
  find_event(Event_timed *etn, bool remove_from_q);

  void
  workers_increment();

  void
  workers_decrement();

  static int
  lock_mutex(pthread_mutex_t *mutex);
  
  static int
  unlock_mutex(pthread_mutex_t *mutex);
  
  int
  cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

  /* helper functions */
  bool
  execute_top(THD *thd);
  
  void
  clean_queue(THD *thd);
  
  static void
  check_system_tables();

  enum enum_error_code
  load_and_compile_event(THD *thd, Event_timed *etn, Event_timed **etn_new);

  int
  load_events_from_db(THD *thd);

  void
  check_n_wait_for_bulk_drop_finished(THD *thd);
  
  void
  drop_matching_events(THD *thd, LEX_STRING *pattern,
                       bool (*)(Event_timed *,LEX_STRING *));

private:
  /* Prevent use of these */
  Event_scheduler(const Event_scheduler &);
  void operator=(Event_scheduler &);

protected:
  /* Singleton is used */
  Event_scheduler():bulk_drop(false), thread_id(0), workers_count(0)
  {}

  /*
    LOCK PROCOTOL: Expects that this method is synchronized
  */
  ~Event_scheduler();

  /*
          |---> STOPPED <--|
          |                |
          |                |
    UNINITIALIZED -> INITIALIZED -> COMMENCING -> RUNNING -> IN_SHUTDOWN
                       ^ ^               |                    |
                       | |- CANTSTART <--|                    |
                       |______________________________________|

    The life cycle of a scheduler
    - UNINITIALIZED :The object is created and only the mutex is initialized
    - INITIALIZED   :All member variables are initialized
    - COMMENCING    :The scheduler is starting, no other attempt to start 
                     should succeed before the state is back to INITIALIZED.
    - CANTSTART     :Set by the ::run() method in case it can't start for some
                     reason. In this case the connection thread that tries to
                     start the scheduler sees that some error has occurred and
                     returns an error to the user. Finally, the connection
                     thread sets the state to INITIALIZED, so further attempts
                     to start the scheduler could be made.
    - RUNNING       :The scheduler is running. New events could be added,
                     dropped, altered. The scheduler could be stopped.
    - IN_SHUTDOWN   :The scheduler is shutting down, due to request by setting
                     the global event_scheduler to 0/false, or because of a
                     KILL command sent by a user to the master thread.
    - STOPPED       :The state just before the object is destructed.
  */
  enum enum_state
  {
    UNINITIALIZED= 0,
    INITIALIZED,
    COMMENCING,
    CANTSTART,
    RUNNING,
    IN_SHUTDOWN,
    STOPPED
  };

  /* Singleton instance */
  static Event_scheduler *singleton;
  
  /* This is the current status of the life-cycle of the manager. */
  enum enum_state state;

  /*
    Set to true if bulk drop is undergoing. In this case all other operations
    wait till the bulk drop (schema events, user events) has finished.
  */
  bool bulk_drop;

  /*
    LOCK_scheduler_data is the mutex which protects the access to the manager's queue
    as well as used when signalling COND_new_work, COND_started and
    COND_shutdown.
  */
  pthread_mutex_t LOCK_scheduler_data;

  /* Complementing mutex for COND_last_worker_exit */
  pthread_mutex_t LOCK_last_worker_exit;  

  /*
    Holds the thread id of the executor thread or 0 if the executor is not
    running. It is used by ::shutdown() to know which thread to kill with
    kill_one_thread(). The latter wake ups a thread if it is waiting on a
    conditional variable and sets thd->killed to non-zero.
  */
  ulong thread_id;

  /*
    COND_new_work is a conditional used to signal that there is a change
    of the queue that should inform the executor thread that new event should
    be executed sooner than previously expected, because of add/replace event.
  */
  pthread_cond_t COND_new_work;

  /*
    COND_started is a conditional used to synchronize the thread in which 
    ::start() was called and the spawned thread. ::start() spawns a new thread
    and then waits on COND_started but also checks when awaken that `state` is
    either RUNNING or CANTSTART. Then it returns back.
  */
  pthread_cond_t COND_started;

  /*
    COND_shutdown is a conditional very similar to COND_started in it usage but
    is used during ::shutdown() call. ::shutdown() kills the executor thread and
    then waits for COND_shutdown to be signalled by the latter. When this happens
    and `state` is set back by the executor thread to INITIALIZED, to guarantee
    protection against spurious wake-ups, ::shutdown() returns.
  */
  pthread_cond_t COND_shutdown;

  /*
    COND_last_worker_exit is a conditional signaled by a worker thread when the 
    workers_count reaches 0. This notifies the manager thread that it shoud
    continue with the clean up.
  */
  pthread_cond_t COND_last_worker_exit;

  /*
    COND_bulk_drop_finished is a conditional signaled by when bulk drop of
    events in drop_matching_events() is finished. The conditional is used by
    wait_for_bulk_drop_finished() in every method that modifies the queue.
    In this case drop_matching_events runs without being protected by
    LOCK_scheduler_data.
  */
  pthread_cond_t COND_bulk_drop_finished;

  /* The MEM_ROOT of the object */
  MEM_ROOT scheduler_root;
  
  /* The sorted queue with the Event_timed objects */
  QUEUE queue;
  
  /*
    Number of running threads. It can differ from workers.elements because
    workers_count is incremented before forking of a new thread to prevent
    the following race condition:
    1. Thread forked (and stops)
    2. Manager shutdown and does not see the forked thread because it hasn't
       called ::register_worker_thread() and frees the Event_timed object
    3. The forked thread tries to access the Event_timed object
    Intentionaly left int and not uint to catch errors with counting if any
    occur.
  */
  int workers_count;

  /*
    DYNAMIC_ARRAY which holds the id of the threads which are spawned, so we
    can kill them during shutdown. Hence, no need to go over the whole list of
    events in memory and ask everyone to kill itself, if running. Additional
    gain is that an event can be run, with this code, in more than one threads
    but in every thread the sp_head has to be compiled again, because it's not
    reentrant and is tightly coupled to class THD. 
  */
  DYNAMIC_ARRAY workers;
};

#endif /* _EVENT_SCHEDULER_H_ */



--- 1.47/sql/event_timed.cc	2006-03-17 10:36:32 +02:00
+++ 1.48/sql/event_timed.cc	2006-04-20 18:15:59 +03:00
@@ -20,7 +20,6 @@
 #include "sp.h"
 
 
-
 extern int MYSQLparse(void *thd);
 
 /*
@@ -927,7 +926,11 @@ Event_timed::compute_next_execution_time
     goto ret;
   }
 ret:
-
+  sql_print_information("SCHEDULER: Next execution of %*s.%*s at "
+                        "[%04d-%02d-%02d %02d:%02d:%02d UTC]",
+                        dbname.length, dbname.str, name.length, name.str,
+                        execute_at.year, execute_at.month, execute_at.day,
+                        execute_at.hour, execute_at.minute, execute_at.second);
   DBUG_RETURN(false);
 err:
   DBUG_RETURN(true);
@@ -1209,6 +1212,8 @@ Event_timed::execute(THD *thd, MEM_ROOT 
 
   VOID(pthread_mutex_lock(&this->LOCK_running));
   running= false;
+  /* Will compile every time a new sp_head on different root */
+  free_sp();
   VOID(pthread_mutex_unlock(&this->LOCK_running));
 
 done:
@@ -1457,10 +1462,9 @@ extern pthread_attr_t connection_attrib;
 */
 
 int
-Event_timed::spawn_now(void * (*thread_func)(void*))
+Event_timed::spawn_now(void * (*thread_func)(void*), void *arg)
 {  
   int ret= EVENT_EXEC_STARTED;
-  static uint exec_num= 0;
   DBUG_ENTER("Event_timed::spawn_now");
   DBUG_PRINT("info", ("[%s.%s]", dbname.str, name.str));
 
@@ -1469,19 +1473,12 @@ Event_timed::spawn_now(void * (*thread_f
   {
     pthread_t th;
     in_spawned_thread= true;
-    if (pthread_create(&th, &connection_attrib, thread_func, (void*)this))
+    if (pthread_create(&th, &connection_attrib, thread_func, arg))
     {
       DBUG_PRINT("info", ("problem while spawning thread"));
       ret= EVENT_EXEC_CANT_FORK;
       in_spawned_thread= false;
     }
-#ifndef DBUG_OFF
-    else
-    {
-      sql_print_information("SCHEDULER: Started thread %d", ++exec_num);
-      DBUG_PRINT("info", ("thread spawned"));
-    }
-#endif
   }
   else
   {
@@ -1498,18 +1495,12 @@ void
 Event_timed::spawn_thread_finish(THD *thd)
 {
   DBUG_ENTER("Event_timed::spawn_thread_finish");
-  VOID(pthread_mutex_lock(&this->LOCK_running));
+  VOID(pthread_mutex_lock(&LOCK_running));
   in_spawned_thread= false;
-  if ((flags & EVENT_EXEC_NO_MORE) || status == MYSQL_EVENT_DISABLED)
-  {
-    DBUG_PRINT("info", ("%s exec no more. to drop=%d", name.str, dropped));
-    if (dropped)
-      drop(thd);
-    VOID(pthread_mutex_unlock(&this->LOCK_running));
-    delete this;
-    DBUG_VOID_RETURN;
-  }
-  VOID(pthread_mutex_unlock(&this->LOCK_running));
+  DBUG_PRINT("info", ("Sending COND_finished for thread %d", thread_id));
+  thread_id= 0;
+  pthread_cond_signal(&COND_finished);
+  VOID(pthread_mutex_unlock(&LOCK_running));
   DBUG_VOID_RETURN;
 }
 
@@ -1527,7 +1518,7 @@ Event_timed::spawn_unlock(THD *thd)
 {
   int ret= 0;
   VOID(pthread_mutex_lock(&this->LOCK_running));
-  if (!in_spawned_thread)
+  if (in_spawned_thread)
   {
     if (locked_by_thread_id == thd->thread_id)
     {        
@@ -1545,4 +1536,133 @@ Event_timed::spawn_unlock(THD *thd)
   }
   VOID(pthread_mutex_unlock(&this->LOCK_running));
   return ret;
+}
+
+
+/*
+  Kills a running event
+  Synopsis
+    Event_timed::kill_thread()
+    
+  Returns 
+     0    OK
+    -1    EVEX_CANT_KILL
+    !0   Error 
+*/
+
+int
+Event_timed::kill_thread(THD *thd)
+{
+  int ret= 0;
+  DBUG_ENTER("Event_timed::kill_thread");
+  pthread_mutex_lock(&LOCK_running);
+  DBUG_PRINT("info", ("thread_id=%lu", thread_id));
+
+  if (thread_id == thd->thread_id)
+  {
+    /*
+      We don't kill ourselves in cases like :
+      alter event e_43 do alter event e_43 do set @a = 4 because
+      we will never receive COND_finished.
+    */
+    DBUG_PRINT("info", ("It's not safe to kill ourselves in self altering queries"));
+    ret= EVEX_CANT_KILL;
+  }
+  else if (thread_id && !(ret= kill_one_thread(thd, thread_id, false)))
+  {
+    thd->enter_cond(&COND_finished, &LOCK_running, "Waiting for finished");
+    DBUG_PRINT("info", ("Waiting for COND_finished from thread %d", thread_id));
+    while (thread_id)
+      pthread_cond_wait(&COND_finished, &LOCK_running);
+
+    DBUG_PRINT("info", ("Got COND_finished"));
+    /* This will implicitly unlock LOCK_running. Hence we return before that */
+    thd->exit_cond("");
+
+    DBUG_RETURN(0);
+  }
+  else if (!thread_id && in_spawned_thread)
+  {
+    /*
+      Because the manager thread waits for the forked thread to update thread_id
+      this situation is impossible.
+    */
+    DBUG_ASSERT(0);
+  }
+  pthread_mutex_unlock(&LOCK_running);
+  DBUG_RETURN(ret);
+}
+
+
+/*
+  Checks whether two events have the same name
+
+  Synopsis
+    event_timed_name_equal()
+
+  Returns
+    true  names are equal
+    false names are not equal
+*/
+
+bool
+event_timed_name_equal(Event_timed *et, LEX_STRING *name)
+{
+  return !sortcmp_lex_string(et->name, *name, system_charset_info);
+}
+
+
+/*
+  Checks whether two events are in the same schema
+
+  Synopsis
+    event_timed_db_equal()
+
+  Returns
+    true  schemas are equal
+    false schemas are not equal
+*/
+
+bool
+event_timed_db_equal(Event_timed *et, LEX_STRING *db)
+{
+  return !sortcmp_lex_string(et->dbname, *db, system_charset_info);
+}
+
+
+/*
+  Checks whether two events have the same definer
+
+  Synopsis
+    event_timed_definer_equal()
+
+  Returns
+    true  definers are equal
+    false definers are not equal
+*/
+
+bool
+event_timed_definer_equal(Event_timed *et, LEX_STRING *definer)
+{
+  return !sortcmp_lex_string(et->definer, *definer, system_charset_info);
+}
+
+
+/*
+  Checks whether two events are equal by identifiers
+
+  Synopsis
+    event_timed_identifier_equal()
+
+  Returns
+    true   equal
+    false  not equal
+*/
+
+bool
+event_timed_identifier_equal(Event_timed *a, Event_timed *b)
+{
+  return event_timed_name_equal(a, &b->name) &&
+         event_timed_db_equal(a, &b->dbname) &&
+         event_timed_definer_equal(a, &b->definer);
 }

--- 1.90/sql/share/errmsg.txt	2006-03-20 22:41:16 +02:00
+++ 1.91/sql/share/errmsg.txt	2006-04-20 18:15:59 +03:00
@@ -5826,3 +5826,7 @@ ER_NDB_CANT_SWITCH_BINLOG_FORMAT
 	eng "The NDB cluster engine does not support changing the binlog format on the fly yet"
 ER_PARTITION_NO_TEMPORARY
 	eng "Cannot create temporary table with partitions"
+ER_EVENT_MODIFY_QUEUE_ERROR
+        eng "Error %d occured during insert/replace/removal from/into memory queue"
+ER_EVENT_SET_VAR_ERROR
+        eng "Error during starting/stopping of the scheduler. Error code %u"

--- 1.112/mysql-test/t/disabled.def	2006-03-25 01:23:58 +02:00
+++ 1.113/mysql-test/t/disabled.def	2006-04-20 18:15:59 +03:00
@@ -9,9 +9,9 @@
 #  Do not use any TAB characters for whitespace.
 #
 ##############################################################################
-events_bugs             : test case unstable (race conditions). andrey will fix
-events_stress           : test case unstable. andrey will fix
-events                  : test case unstable. andrey will fix
+#events_bugs             : test case unstable (race conditions). andrey will fix
+#events_stress           : test case unstable. andrey will fix
+#events                  : test case unstable. andrey will fix
 #ndb_alter_table_row    : sometimes wrong error 1015!=1046
 ndb_autodiscover        : Needs to be fixed w.r.t binlog
 ndb_autodiscover2       : Needs to be fixed w.r.t binlog

--- 1.180/sql/set_var.cc	2006-03-21 11:54:21 +02:00
+++ 1.181/sql/set_var.cc	2006-04-20 18:15:59 +03:00
@@ -221,9 +221,11 @@ sys_var_long_ptr	sys_delayed_insert_time
 						   &delayed_insert_timeout);
 sys_var_long_ptr	sys_delayed_queue_size("delayed_queue_size",
 					       &delayed_queue_size);
+
+#include "event_scheduler.h"
 sys_var_event_executor  sys_event_executor("event_scheduler",
 					   (my_bool *)
-					   &event_executor_running_global_var);
+					   &Event_scheduler::manager_working);
 sys_var_long_ptr	sys_expire_logs_days("expire_logs_days",
 					     &expire_logs_days);
 sys_var_bool_ptr	sys_flush("flush", &myisam_flush);
@@ -3546,6 +3548,35 @@ byte *sys_var_thd_dbug::value_ptr(THD *t
     DBUG_EXPLAIN(buf, sizeof(buf));
   return (byte*) thd->strdup(buf);
 }
+
+
+/*
+   The update method of the global variable event_scheduler.
+   If event_scheduler is switched from 0 to 1 then the scheduler main
+   thread is started and if from 1 to 0 the scheduler thread is stopped
+
+   SYNOPSIS
+     sys_var_event_executor::update()
+       thd  Thread context (unused)
+       var  The new value
+
+   Returns
+     0  OK (always)
+*/
+
+bool
+sys_var_event_executor::update(THD *thd, set_var *var)
+{
+  enum Event_scheduler::enum_error_code res;
+  /* here start the thread if not running. */
+  DBUG_ENTER("sys_var_event_executor::update");
+
+  DBUG_PRINT("new_value", ("%lu", (bool)var->save_result.ulong_value));
+  if ((res= Event_scheduler::start_or_stop(var->save_result.ulong_value)))
+    my_error(ER_EVENT_SET_VAR_ERROR, MYF(0), (uint) res);
+  DBUG_RETURN((bool) res);
+}
+
 
 /****************************************************************************
   Used templates

--- 1.80/libmysqld/Makefile.am	2006-02-22 01:39:56 +02:00
+++ 1.81/libmysqld/Makefile.am	2006-04-20 18:15:58 +03:00
@@ -64,7 +64,7 @@ sqlsources = derror.cc field.cc field_co
 	spatial.cc gstream.cc sql_help.cc tztime.cc sql_cursor.cc \
 	sp_head.cc sp_pcontext.cc sp.cc sp_cache.cc sp_rcontext.cc \
 	parse_file.cc sql_view.cc sql_trigger.cc my_decimal.cc \
-        event_executor.cc event.cc event_timed.cc \
+        event_scheduler.cc event.cc event_timed.cc \
         rpl_filter.cc sql_partition.cc handlerton.cc sql_plugin.cc \
         sql_tablespace.cc \
         rpl_injector.cc my_user.c partition_info.cc

--- 1.11/sql/cmakelists.txt	2006-03-16 15:55:50 +02:00
+++ 1.12/sql/cmakelists.txt	2006-04-20 18:15:59 +03:00
@@ -39,7 +39,7 @@ ADD_EXECUTABLE(mysqld ../sql-common/clie
 					  sql_plugin.cc sql_prepare.cc sql_rename.cc sql_repl.cc sql_select.cc sql_show.cc
 					  sql_state.c sql_string.cc sql_table.cc sql_test.cc sql_trigger.cc sql_udf.cc sql_union.cc
 					  sql_update.cc sql_view.cc strfunc.cc table.cc thr_malloc.cc time.cc tztime.cc
-					  uniques.cc unireg.cc item_xmlfunc.cc rpl_tblmap.cc sql_binlog.cc event_executor.cc
+					  uniques.cc unireg.cc item_xmlfunc.cc rpl_tblmap.cc sql_binlog.cc event_scheduler.cc
 					  event_timed.cc sql_tablespace.cc event.cc ../sql-common/my_user.c partition_info.cc
 					  ${PROJECT_SOURCE_DIR}/sql/sql_yacc.cc
   					  ${PROJECT_SOURCE_DIR}/sql/sql_yacc.h

--- 1.233/BitKeeper/etc/ignore	2006-03-25 14:14:45 +02:00
+++ 1.234/BitKeeper/etc/ignore	2006-04-20 18:15:58 +03:00
@@ -1755,3 +1755,5 @@ mysql-test/r/events_logs_tests.log
 mysql-test/r/mysqltest.log
 mysql-test/r/symlink.log
 mysql-test/r/system_mysql_db.log
+libmysqld/event_scheduler.cc
+mysql-test/r/events_stress.log
Thread
bk commit into 5.1 tree (andrey:1.2235) BUG#17619ahristov20 Apr