List:Commits« Previous MessageNext Message »
From:ahristov Date:April 4 2006 8:42pm
Subject:bk commit into 5.1 tree (andrey:1.2234) 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.2234 06/04/04 22:41:58 andrey@lmy004. +14 -0
  Fix for bug#17619 (Scheduler race conditions)
  This is preliminary patch intended to have early review in the fixing process. The issue is big
  so this needs few revies during development.

  sql/event_scheduler.h
    1.1 06/04/04 22:41:48 andrey@lmy004. +262 -0

  sql/event_scheduler.cc
    1.1 06/04/04 22:41:48 andrey@lmy004. +1695 -0

  sql/event_scheduler.h
    1.0 06/04/04 22:41:48 andrey@lmy004. +0 -0
    BitKeeper file /work/mysql-5.1-bug17619-new/sql/event_scheduler.h

  sql/event_scheduler.cc
    1.0 06/04/04 22:41:48 andrey@lmy004. +0 -0
    BitKeeper file /work/mysql-5.1-bug17619-new/sql/event_scheduler.cc

  sql/sql_yacc.yy
    1.485 06/04/04 22:41:47 andrey@lmy004. +10 -0
    - parse SHOW EVENTS TEST

  sql/sql_parse.cc
    1.535 06/04/04 22:41:47 andrey@lmy004. +18 -7
    - run scheduler tests on SQLCOM_SHOW_EVENTS_TEST
    - let kill_one_thread() return whether there was an error or not
      and be silent if asked to be, makes it reusable in code that will send send_ok() on
      its own.

  sql/sql_lex.h
    1.225 06/04/04 22:41:47 andrey@lmy004. +1 -1
    add new command SQLCOM_SHOW_EVENTS_TEST, temporarily, for inside testing of the scheduler

  sql/set_var.cc
    1.181 06/04/04 22:41:47 andrey@lmy004. +3 -1
    - use the static member manager_working of Event_scheduler class,
    capsulation is better

  sql/mysql_priv.h
    1.388 06/04/04 22:41:46 andrey@lmy004. +1 -1
    let kill_one_thread() return whether there was an error or not

  sql/lex.h
    1.157 06/04/04 22:41:46 andrey@lmy004. +1 -0
    - add TEST as TEST_SYM. Needed for SHOW EVENTS TEST which is temporary code for testing
      the scheduler from inside the server with 1 command.

  sql/event_timed.cc
    1.48 06/04/04 22:41:46 andrey@lmy004. +20 -0
    Event_timed::kill_thread() stub, to implemented later, used by the
    current Event_scheduler_manager code

  sql/event_priv.h
    1.21 06/04/04 22:41:46 andrey@lmy004. +6 -0
    export db_find_event() and db_create_event()

  sql/event_executor.cc
    1.42 06/04/04 22:41:46 andrey@lmy004. +17 -15
    - init and destroy the LOCK_manager mutex of Event_schedule_manager

  sql/event.h
    1.28 06/04/04 22:41:46 andrey@lmy004. +17 -4
    move sortcmp_lex_string() so it is visible in the newly implemented
    Event_timed::same_name()

  sql/event.cc
    1.38 06/04/04 22:41:46 andrey@lmy004. +2 -2
    export db_create_event() and db_find_event()

  sql/Makefile.am
    1.134 06/04/04 22:41:46 andrey@lmy004. +3 -3
    add a new file event_scheduler.cc to the build

# 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 20:16:49 +01:00
+++ 1.134/sql/Makefile.am	2006-04-04 22:41:46 +02: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_executor.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 20:36:13 +01:00
+++ 1.157/sql/lex.h	2006-04-04 22:41:46 +02:00
@@ -521,6 +521,7 @@ static SYMBOL symbols[] = {
   { "TEMPORARY",	SYM(TEMPORARY)},
   { "TEMPTABLE",	SYM(TEMPTABLE_SYM)},
   { "TERMINATED",	SYM(TERMINATED)},
+  { "TEST",		SYM(TEST_SYM)},
   { "TEXT",		SYM(TEXT_SYM)},
   { "THAN",             SYM(THAN_SYM)},
   { "THEN",		SYM(THEN_SYM)},

--- 1.387/sql/mysql_priv.h	2006-03-22 07:57:50 +01:00
+++ 1.388/sql/mysql_priv.h	2006-04-04 22:41:46 +02:00
@@ -83,7 +83,7 @@ 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, bool no_error);
 bool net_request_file(NET* net, const char* fname);
 char* query_table_status(THD *thd,const char *db,const char *table_name);
 

--- 1.224/sql/sql_lex.h	2006-03-20 20:39:47 +01:00
+++ 1.225/sql/sql_lex.h	2006-04-04 22:41:47 +02: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 13:10:09 +01:00
+++ 1.535/sql/sql_parse.cc	2006-04-04 22:41:47 +02: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);
+    kill_one_thread(thd,id,false,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::run_tests(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);
+    kill_one_thread(thd, (ulong)it->val_int(), lex->type & ONLY_KILL_QUERY,false);
     break;
   }
 #ifndef NO_EMBEDDED_ACCESS_CHECKS
@@ -6872,7 +6879,7 @@ bool reload_acl_and_cache(THD *thd, ulon
     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, bool no_error)
 {
   THD *tmp;
   uint error=ER_NO_SUCH_THREAD;
@@ -6902,10 +6909,14 @@ void kill_one_thread(THD *thd, ulong id,
     pthread_mutex_unlock(&tmp->LOCK_delete);
   }
 
-  if (!error)
-    send_ok(thd);
-  else
-    my_error(error, MYF(0), id);
+  if (!no_error)
+  {
+    if (!error)
+      send_ok(thd);
+    else
+      my_error(error, MYF(0), id);
+  }
+  return error;
 }
 
 

--- 1.484/sql/sql_yacc.yy	2006-03-21 13:10:10 +01:00
+++ 1.485/sql/sql_yacc.yy	2006-04-04 22:41:47 +02:00
@@ -630,6 +630,7 @@ bool my_yyoverflow(short **a, YYSTYPE **
 %token  TEMPORARY
 %token  TEMPTABLE_SYM
 %token  TERMINATED
+%token  TEST_SYM
 %token  TEXT_STRING
 %token  TEXT_SYM
 %token  TIMESTAMP
@@ -8149,6 +8150,15 @@ show_param:
              lex->select_lex.db= $3;
              if (prepare_schema_table(YYTHD, lex, 0, SCH_EVENTS))
                YYABORT;
+           }
+         | EVENTS_SYM TEST_SYM
+           {
+#ifdef DBUG_OFF
+             yyerror(ER(ER_SYNTAX_ERROR));
+             YYABORT;
+#else
+             Lex->sql_command= SQLCOM_SHOW_EVENTS_TEST;
+#endif
            }
          | TABLE_SYM STATUS_SYM opt_db wild_and_where
            {

--- 1.37/sql/event.cc	2006-02-28 20:32:30 +01:00
+++ 1.38/sql/event.cc	2006-04-04 22:41:46 +02:00
@@ -721,7 +721,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 +954,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)
 {

--- 1.27/sql/event.h	2006-03-16 13:14:32 +01:00
+++ 1.28/sql/event.h	2006-04-04 22:41:46 +02: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
@@ -75,6 +76,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 */
@@ -286,6 +289,20 @@ public:
     delete sphead;
     sphead= 0;
   }
+
+  bool
+  same_name(Event_timed *etn)
+  {
+    return (!sortcmp_lex_string(etn->name, name, system_charset_info) &&
+        !sortcmp_lex_string(etn->dbname, dbname, system_charset_info) &&
+        !sortcmp_lex_string(etn->definer, definer, system_charset_info)
+        );      
+  }
+  
+  int
+  kill_thread();
+
+
 protected:
   bool
   change_security_context(THD *thd, Security_context *s_ctx,
@@ -314,8 +331,6 @@ 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,
@@ -335,8 +350,6 @@ shutdown_events();
 // auxiliary
 int
 event_timed_compare(Event_timed **a, Event_timed **b);
-
-
 
 /*
 CREATE TABLE event (

--- 1.41/sql/event_executor.cc	2006-03-01 04:21:57 +01:00
+++ 1.42/sql/event_executor.cc	2006-04-04 22:41:46 +02:00
@@ -17,6 +17,7 @@
 #include "event_priv.h"
 #include "event.h"
 #include "sp.h"
+#include "event_scheduler.h"
 
 #define WAIT_STATUS_READY         0
 #define WAIT_STATUS_EMPTY_QUEUE   1
@@ -108,6 +109,7 @@ evex_init_mutexes()
     return;
 
   evex_mutexes_initted= TRUE;
+  pthread_mutex_init(&Event_scheduler::LOCK_manager, MY_MUTEX_INIT_FAST);
   pthread_mutex_init(&LOCK_event_arrays, MY_MUTEX_INIT_FAST);
   pthread_mutex_init(&LOCK_workers_count, MY_MUTEX_INIT_FAST);
   pthread_mutex_init(&LOCK_evex_running, MY_MUTEX_INIT_FAST);
@@ -237,10 +239,17 @@ shutdown_events()
     evex_mutexes_initted= FALSE;
     VOID(pthread_mutex_lock(&LOCK_evex_running));
     VOID(pthread_mutex_unlock(&LOCK_evex_running));
-
+    
+    if (Event_scheduler::destroy())
+    {
+      DBUG_ASSERT(0);
+      sql_print_error("SCHEDULER: Trying to destroy while still running!");
+    }
     pthread_mutex_destroy(&LOCK_event_arrays);
     pthread_mutex_destroy(&LOCK_workers_count);
     pthread_mutex_destroy(&LOCK_evex_running);
+    pthread_mutex_destroy(&Event_scheduler::LOCK_manager);
+    
   }
   DBUG_VOID_RETURN;
 }
@@ -891,9 +900,9 @@ end:
    thread is started.
 
    SYNOPSIS
-     event_executor_worker()
-       thd - Thread context (unused)
-       car - the new value
+     sys_var_event_executor::update()
+       thd  Thread context (unused)
+       var  The new value
 
    Returns
      0  OK (always)
@@ -902,20 +911,13 @@ end:
 bool
 sys_var_event_executor::update(THD *thd, set_var *var)
 {
+  enum Event_scheduler::event_manager_op_code res;
   /* here start the thread if not running. */
   DBUG_ENTER("sys_var_event_executor::update");
-  VOID(pthread_mutex_lock(&LOCK_evex_running));
-  *value= var->save_result.ulong_value;
-
-  DBUG_PRINT("new_value", ("%d", *value));
-  if ((my_bool) *value && !evex_is_running)
-  {
-    VOID(pthread_mutex_unlock(&LOCK_evex_running));
-    init_events();
-  } else 
-    VOID(pthread_mutex_unlock(&LOCK_evex_running));
 
-  DBUG_RETURN(0);
+  DBUG_PRINT("new_value", ("%d", (bool)var->save_result.ulong_value));
+  DBUG_RETURN(Event_scheduler::
+                event_scheduler_var_update(var->save_result.ulong_value));
 }
 
 

--- 1.20/sql/event_priv.h	2006-02-28 18:33:25 +01:00
+++ 1.21/sql/event_priv.h	2006-04-04 22:41:46 +02:00
@@ -46,7 +46,13 @@ event_timed_compare_q(void *vptr, byte* 
 
 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);
 
 #define EXEC_QUEUE_QUEUE_NAME executing_queue
 #define EXEC_QUEUE_DARR_NAME evex_executing_queue
--- New file ---
+++ sql/event_scheduler.cc	06/04/04 22:41:48
/* 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"

/*
  1. Integrate the code with the already existing one. Call 
    - add_event
    - drop_event
    - replace_event
    where appropriate
  2. Fix kill_thread() in drop_event()/add_event().

*/

#define CHECK_RUNNING(__ret_code) \
if (mode != MANAGER_RUNNING) \
{ \
  DBUG_PRINT("info", ("manager not running but %d. doing nothing", mode)); \
  pthread_mutex_unlock(&LOCK_mutex); \
  DBUG_RETURN(__ret_code); \
}

#define SET_THD_INFO(__info) thd->proc_info= (char *)__info;


Event_scheduler
*Event_scheduler::ston= NULL;

pthread_mutex_t
Event_scheduler::LOCK_manager;

my_bool
Event_scheduler::manager_working= FALSE;


extern bool
evex_print_warnings(THD *thd, Event_timed *et);

/*
  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)
{
  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 *esm= (Event_scheduler *) arg;

  DBUG_ENTER("event_scheduler_run_proxy");

  my_thread_init();

  /* note that contructor of THD uses DBUG_ ! */
  if (!(thd = new THD))                         
  {
    sql_print_error("SCHEDULER: Cannot create THD for the main thread.");
    goto finish;
  }
  thd->thread_stack = (char*)&thd;              // remember where our stack is
  pthread_detach_this_thread();

  if (init_event_thread(thd))
  {
    sql_print_error("SCHEDULER: Cannot init main event thread.");
    goto finish;
  }

  thd->security_ctx->user= my_strdup("event_scheduler", MYF(0));

  sql_print_information("SCHEDULER: Main thread started");

  esm->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);

finish:
  /*
    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 *event_void)
{
  THD *thd; /* needs to be first for thread_stack */
  Event_timed *event = (Event_timed *) event_void;
  MEM_ROOT worker_mem_root;
  int ret;

  DBUG_ENTER("event_worker_thread");

  my_thread_init();

  if (!(thd = new THD)) /* note that contructor of THD uses DBUG_ ! */
  {
    sql_print_error("SCHEDULER: Cannot create a THD structure in an worker.");
    goto finish;
  }
  thd->thread_stack = (char*)&thd;              // remember where our stack is

  pthread_detach_this_thread();

  if (init_event_thread(thd))
  {
    sql_print_error("SCHEDULER: Cannot init worker thread.");
    goto finish;
  }

  /*
    This is specific to the worker thread which we don't do in the manager
    thread. Everything else in the init part is the same.
  */
  thd->init_for_queries();
  init_alloc_root(&worker_mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
  thd->mem_root= &worker_mem_root;

  thd->enable_slow_log= TRUE;
  if (Event_scheduler::register_worker_thread(thd->thread_id))
    sql_print_information("SCHEDULER: Error during register_worker_thread()");
  else
  {

    sql_print_information("SCHEDULER: Executing event %s.%s of %s [EXPR:%d]",
                          event->dbname.str, event->name.str,
                          event->definer.str, (int) event->expression);

    ret= event->execute(thd, &worker_mem_root);
    evex_print_warnings(thd, event);
    sql_print_information("SCHEDULER: Executed event %s.%s of %s  [EXPR:%d]. "
                         "RetCode=%d",  event->dbname.str, event->name.str,
                         event->definer.str, (int) event->expression, 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");

    event->spawn_thread_finish(thd);
  }
  pthread_mutex_lock(&LOCK_thread_count);
  thread_count--;
  thread_running--;
  pthread_mutex_unlock(&LOCK_thread_count);
  free_root(&worker_mem_root, MYF(0));
  
finish:
  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);

    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()

  Returns
    NULL     Error
    address  Success
*/

Event_scheduler*
Event_scheduler::get_instance()
{
  Event_scheduler *tmp= NULL;
  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 will not work correctly. Stopping.");
    DBUG_ASSERT(0);
    DBUG_RETURN(NULL);
  }

  pthread_mutex_lock(&LOCK_manager);
  if (ston)
    goto skip_init;

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

  if (!(tmp= new Event_scheduler))
    goto err;

  if (init_queue_ex(&tmp->queue, 30 /*num_el*/, 0 /*offset*/,
                    0 /*smallest_on_top*/, event_timed_compare_q,
                    NULL, 30 /*auto_extent*/))
  {
    sql_print_error("SCHEDULER: Insufficient memory to initialize "
                    "executing queue.");
    goto err;
  }
  if (pthread_mutex_init(&tmp->LOCK_mutex, MY_MUTEX_INIT_FAST))
  {
    sql_print_error("SCHEDULER: Unable to initalize LOCK_mutex");
    goto err;
  }

  if (pthread_cond_init(&tmp->COND_new_work, NULL))
  {
    sql_print_error("SCHEDULER: Unable to initialize COND_new_work");
    goto err;
  }

  if (pthread_cond_init(&tmp->COND_started, NULL))
  {
    sql_print_error("SCHEDULER: Unable to initialize COND_started");
    goto err;
  }

  if (pthread_cond_init(&tmp->COND_shutdown, NULL))
  {
    sql_print_error("SCHEDULER: Unable to initialize COND_shutdown");
    goto err;
  }

  if (pthread_mutex_init(&tmp->LOCK_last_worker_exit, MY_MUTEX_INIT_FAST))
  {
    sql_print_error("SCHEDULER: Unable to initalize LOCK_last_worker_exit");
    goto err;
  }
  if (pthread_cond_init(&tmp->COND_last_worker_exit, NULL))
  {
    sql_print_error("SCHEDULER: Unable to initialize COND_last_worker_exit");
    goto err;
  }

#ifndef DBUG_OFF
  if (pthread_mutex_init(&tmp->LOCK_executed, MY_MUTEX_INIT_FAST))
  {
    sql_print_error("SCHEDULER: Unable to initalize LOCK_executed");
    goto err;
  }
  if (pthread_cond_init(&tmp->COND_executed, NULL))
  {
    sql_print_error("SCHEDULER: Unable to initialize COND_executed");
    goto err;
  }
#endif

  /* init memory root */
  init_alloc_root(&tmp->manager_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);


  my_init_dynamic_array(&tmp->workers, sizeof(ulong), 50, 100);

  tmp->mode= MANAGER_INIT;

  ston= tmp;

skip_init:
  pthread_mutex_unlock(&LOCK_manager);
  DBUG_RETURN(ston);

err:
  pthread_mutex_unlock(&LOCK_manager);
  delete tmp;
  DBUG_RETURN(NULL);
}


/*
  Adds an event to the scheduler queue

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

  Returns
    FALSE OK
    TRUE  Failure
*/

bool
Event_scheduler::add_event(THD *thd, Event_timed *et,
                                   bool check_existance)
{
  Event_timed *et_new;
  DBUG_ENTER("Event_scheduler::add_event");
  pthread_mutex_lock(&LOCK_mutex);
  CHECK_RUNNING(false);
  if (check_existance)
    if (find_event(et, false))
    {
      pthread_mutex_unlock(&LOCK_mutex);
      DBUG_RETURN(true);
    }

  /* We need to load the event on manager_root */
  if ((et_new= load_and_compile_event(thd, et)))
  {
    queue_insert_safe(&queue, (byte *) et_new);
    pthread_cond_signal(&COND_new_work);
  }
  pthread_mutex_unlock(&LOCK_mutex);
  DBUG_RETURN(!et_new);
}


/*
  Drops an event from the scheduler queue

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

  Returns
    FALSE OK
    TRUE  Failure
*/

bool
Event_scheduler::drop_event(THD *thd, Event_timed *etn,
                                    enum event_drop_mode drop_mode)
{
  Event_timed *et;
  DBUG_ENTER("Event_scheduler::drop_event");
  pthread_mutex_lock(&LOCK_mutex);
  CHECK_RUNNING(false);

  if ((et= find_event(etn, true)))
  {
    /* ToDo : Stop the event ! */
    et->kill_thread();
    /*
      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 pthread_cond_wait(). Hence, no need to interrupt the blocked
         ::run() thread.
    */
  }
  else
    DBUG_PRINT("info", ("no such event found"));

  pthread_mutex_unlock(&LOCK_mutex);
  DBUG_RETURN(false);
}


/*
  Replaces an event in the scheduler queue

  Synopsis
    Event_scheduler::replace_event()
      et    The event to replace(add) into the queue
      mode  Wait the event or kill&drop

  Returns
    FALSE OK
    TRUE  Failure
*/

bool
Event_scheduler::replace_event(THD *thd, Event_timed *et,
                                       enum event_drop_mode replace_mode)
{
  Event_timed *et_old, *et_new= NULL;
  DBUG_ENTER("Event_scheduler::replace_event");
  pthread_mutex_lock(&LOCK_mutex);
  CHECK_RUNNING(false);
  /* ToDo : Stop the event ! */
  if ((et_old= find_event(et, true)))
    et_old->kill_thread();
  else
    DBUG_PRINT("info", ("%s.%s not found cached, probably was DISABLED",
                        et->dbname.str, et->name.str));

  /* We need to load the event on manager_root */
  if ((et_new= load_and_compile_event(thd, et)))
  {
    queue_insert_safe(&queue, (byte *) et_new);
    pthread_cond_signal(&COND_new_work);
  }
  pthread_mutex_unlock(&LOCK_mutex);
  DBUG_RETURN(!et_new);
}


/*
  Searches for an event in the scheduler queue

  Synopsis
    Event_scheduler::find_event()
      etn            The event to find
      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 (etn->same_name(et))
    {
      if (remove_from_q)
        queue_remove(&queue, i);
      DBUG_RETURN(et);
    }
  }

  DBUG_RETURN(NULL);
}


extern pthread_attr_t connection_attrib;

/*
  Starts the event scheduler

  Synopsis
    Event_scheduler::start()

  Returns
    EVENT_OP_OK               OK
    EVENT_OP_CANTFORK         Cannot create a new thread
    EVENT_OP_CANTSTART        ::run() had problem booting
    EVENT_OP_ALREADY_RUNNING  Manager already running
*/

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

  pthread_mutex_lock(&LOCK_mutex);
  /* If already working or starting don't make another attempt */
  if (mode > MANAGER_INIT)
  {
    DBUG_PRINT("info", ("manager is already running or starting"));
    ret= EVENT_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 `mode` is set
    to MANAGER_CANTSTART and COND_started is also signaled. In this case we
    set `mode` back to MANAGER_INIT so another attempt to start the manager
    can be made.
  */
  mode= MANAGER_STARTING;
  manager_working= TRUE;
  /* Fork */
  if (pthread_create(&th, &connection_attrib, event_scheduler_run_proxy,
                    (void*)this))
  {
    DBUG_PRINT("error", ("cannot create a new thread"));
    mode= MANAGER_INIT;
    ret= EVENT_OP_CANTFORK;
    goto finish;
  }

  /*  Wait till the child thread has booted (w/ or wo success) */
  while (mode != MANAGER_RUNNING && mode != MANAGER_CANTSTART)
    pthread_cond_wait(&COND_started, &LOCK_mutex);

  /*
    If we cannot start for some reason then don't prohibit further attempts.
    Set back to MANAGER_INIT.
  */
  if (mode == MANAGER_CANTSTART)
  {
    mode= MANAGER_INIT;
    manager_working= FALSE;
    ret= EVENT_OP_CANTSTART;
    goto finish;
  }
  ret= EVENT_OP_OK;

finish:
  pthread_mutex_unlock(&LOCK_mutex);
  DBUG_RETURN(ret);
}


/*
  The internal loop of the event scheduler

  Synopsis
    Event_scheduler::run()

  Returns
    FALSE OK
    TRUE  Failure
*/

bool
Event_scheduler::run(THD *thd)
{
  int ret;
  struct timespec abstime;
  DBUG_ENTER("Event_scheduler::run");

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

  pthread_mutex_lock(&LOCK_mutex);
  if (!ret)
  {
    thread_id= thd->thread_id;
    mode= MANAGER_RUNNING;
  }
  else 
    mode= MANAGER_CANTSTART;

  DBUG_PRINT("info", ("Sending back COND_started"));
  pthread_cond_signal(&COND_started);
  pthread_mutex_unlock(&LOCK_mutex);
  if (ret)
    DBUG_RETURN(true);

  abstime.tv_nsec= 0;
  while (!thd->killed)
  {
    TIME time_now_utc;
    Event_timed *et;
    my_bool tmp;
    time_t now_utc;

    pthread_mutex_lock(&LOCK_mutex);
    if (mode != MANAGER_RUNNING)
    {
      pthread_mutex_unlock(&LOCK_mutex);
      break;
    }
    while (!queue.elements)
    {
      bool should_break;
      thd->enter_cond(&COND_new_work, &LOCK_mutex, "Empty queue, sleeping");
      pthread_cond_wait(&COND_new_work, &LOCK_mutex);
      DBUG_PRINT("info", ("Woke up. mode is %d", mode));
      should_break= (mode != MANAGER_RUNNING);
      thd->exit_cond("");
      if (should_break)
        goto end_loop;
    }

    SET_THD_INFO("Computing");
    et= (Event_timed *)queue_top(&queue);
    DBUG_PRINT("evex main thread",("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 */
      DBUG_PRINT("info", ("Going to sleep. Should wakeup after approx %d secs",
                         abstime.tv_sec - thd->query_start()));

      SET_THD_INFO("Sleep");
      sql_print_information("SCHEDULER: Going to sleep for %d secs",
                            abstime.tv_sec - thd->query_start());
      /*
        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`.
      */
      thd->enter_cond(&COND_new_work, &LOCK_mutex, "Sleeping until next time");

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

      DBUG_PRINT("info", ("Woke up. mode is %d", mode));
      /*
        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 pthread_mutex_unlock(&LOCK_mutex) */
      thd->exit_cond("");
    }
    else
    {
      SET_THD_INFO("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_mutex.
      */
      int ret= execute_top(thd);
      pthread_mutex_unlock(&LOCK_mutex);
      if (ret)
        break;
    }
  }
end_loop:
  SET_THD_INFO("Cleaning");

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

  sql_print_information("SCHEDULER: Stopping");

  SET_THD_INFO("Cleaning queue");
  clean_queue(thd);

  /* free mamager_root memory but don't destroy the root */
  SET_THD_INFO("Cleaning memory root");
  free_root(&manager_root, MYF(0));

  /*
    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.
  */
  pthread_mutex_lock(&LOCK_mutex);
  SET_THD_INFO("Sending shutdown signal");
  DBUG_PRINT("info", ("Sending COND_shutdown"));
  if (mode == MANAGER_SHUTDOWN)
    pthread_cond_signal(&COND_shutdown);

  /*
    Don't set mode to MANAGER_STOPPED because ::start() checks
    (mode > MANAGER_INIT) to decide whether it can fork a new thread or return.
    MANAGER_STOPPED is used by the dtor of Event_scheduler, thus
    preventing a fork of a new thread while the manager object is being
    destroyed.
  */
  mode= MANAGER_INIT;
  /*
    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.");
  pthread_mutex_unlock(&LOCK_mutex);

  /* We have modified the following with the SET_THD_INFO macro - 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_mutex).

  Returns
    FALSE OK
    TRUE  Failure
*/

bool
Event_scheduler::execute_top(THD *thd)
{
  int fork_ret_code;
  DBUG_ENTER("Event_scheduler::execute_top");
  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)));
  sql_print_information("SCHEDULER: Executed %s", 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 main thread", ("[%10s] next exec at [%llu]", et->name.str,
             TIME_to_ulonglong_datetime(&et->execute_at)));

  et->update_fields(thd);

  /* 
    We don't lock LOCK_mutex here because it's a pre-requisite for calling the
    current_method.
  */
  workers_increment(thd->thread_id);
  switch ((fork_ret_code= et->spawn_now(event_worker_thread))) {
  case EVENT_EXEC_CANT_FORK:
    /* 
      We don't lock LOCK_mutex 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(0);
    sql_print_error("SCHEDULER: Problem while trying to create a thread");
    DBUG_RETURN(true);
  case EVENT_EXEC_ALREADY_EXEC:
    /* 
      We don't lock LOCK_mutex 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(0);
    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_mutex 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(0);
#ifndef DBUG_OFF
    else
    {
      pthread_mutex_lock(&LOCK_executed);
      pthread_cond_signal(&COND_executed);
      pthread_mutex_unlock(&LOCK_executed);
    }
#endif
    break;
  }

  /*
    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);
   
  DBUG_RETURN(false);
}


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

  Synopsis
    Event_scheduler::clean_queue()
*/

void
Event_scheduler::clean_queue(THD *thd)
{
  uint i;
  int ret;
  DBUG_ENTER("Event_scheduler::clean_queue");

  pthread_mutex_lock(&LOCK_mutex);
  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;

    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, true)))
      {
        sql_print_error("SCHEDULER: Error while killing code=%d", ret);
        break;
      }
    }
    if (!had_super)
      thd->security_ctx->master_access &= ~SUPER_ACL;

    DBUG_PRINT("info", ("thd->security_ctx & SUPER_ACL=%ld User=%s",
               thd->security_ctx->master_access & SUPER_ACL,
               thd->security_ctx->user));

    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));
        pthread_cond_wait(&COND_last_worker_exit, &LOCK_mutex);
      } while (workers_count);
  }
  pthread_mutex_unlock(&LOCK_mutex);

  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()
      mode  Wait the events or kill them immediately

  Returns
    EVENT_OP_OK           OK
    EVENT_OP_CANT_KILL    Error during stopping of manager thread
    EVENT_OP_NOT_RUNNING  Manager not working
*/

enum Event_scheduler::event_manager_op_code
Event_scheduler::shutdown(enum event_drop_mode stop_mode)
{
  int ret;
  THD *thd= current_thd;
  DBUG_ENTER("Event_scheduler::shutdown");

  pthread_mutex_lock(&LOCK_mutex);
  CHECK_RUNNING(EVENT_OP_NOT_RUNNING);
  mode= MANAGER_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_mutex. 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 (mode != MANAGER_INIT)
  {
    DBUG_PRINT("info", ("Waiting for COND_shutdown from the manager thread."
               " Current value of mode is %d . workers_count=%d", mode,
               workers_count));
    pthread_cond_wait(&COND_shutdown, &LOCK_mutex);
  }
  sql_print_information("SCHEDULER: Manager thread replied");
  DBUG_PRINT("info", ("Manager thread has cleaned up. Set mode to INIT"));
  pthread_mutex_unlock(&LOCK_mutex);

  DBUG_RETURN(EVENT_OP_OK);
}


/*
  Handles updates of @@event_scheduler

  Synopsis
    Event_scheduler::event_scheduler_var_update()

  Returns
    FALSE  OK
    TRUE   Error
*/

enum Event_scheduler::event_manager_op_code
Event_scheduler::event_scheduler_var_update(my_bool new_value)
{
  Event_scheduler *mgr;
  enum event_manager_op_code res;
  DBUG_ENTER("Event_scheduler::event_scheduler_var_update");
  pthread_mutex_lock(&LOCK_manager);
  if (new_value == Event_scheduler::manager_working)
  {
    DBUG_PRINT("info", ("The new value is the same as the old"));
    pthread_mutex_unlock(&LOCK_manager);
    DBUG_RETURN(EVENT_OP_OK);
  }
  /* 
    Don't use the traditional way like other static functions do,
    namely lock LOCK_manager. If we get lock on LOCK_manager and
    ston is NULL and if the new value for the variable is `true`,
    meaning we have to start the scheduler there is a deadlock because
    ::get_instance() locks also on LOCK_manager. Instead, just call
    ::get_instance() and it will create an object if it is not needed.
    If setting the value to 
  */
  pthread_mutex_unlock(&LOCK_manager);
  if (!(mgr= Event_scheduler::get_instance()))
    DBUG_RETURN(EVENT_OP_CANT_INIT);

  /*
    ::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? mgr->start():mgr->shutdown(EVENT_STOP_SYNC));
}


/*
  Increments the number of running worker threads

  Synopsis
    Event_scheduler::workers_increment()
      thd_id  Newly started thread's id

  NOTE
    The caller should have locked LOCK_mutex!
*/

void
Event_scheduler::workers_increment(ulong thd_id)
{
  DBUG_ENTER("Event_scheduler::workers_increment");
  DBUG_PRINT("info", ("mode=%d", ston->mode));
  DBUG_ASSERT(ston->mode == MANAGER_RUNNING);
  ++workers_count;
  DBUG_VOID_RETURN;
}


/*
  Decrements the number of running worker threads

  Synopsis
    Event_scheduler::workers_decrement()
      thd_id  The id of the thread that finishes. Pass 0
              if the thread id is not known because the thread refused to
              start (workers_decrement() failed after successful
              workers_increment() call).
  NOTE
    The caller should have locked LOCK_mutex!
*/

void
Event_scheduler::workers_decrement(ulong thd_id)
{
  DBUG_ENTER("Event_scheduler::workers_decrement");

  if (mode == MANAGER_RUNNING || mode == MANAGER_SHUTDOWN)
    --workers_count;
  DBUG_PRINT("info", ("workers_count=%d", workers_count));
  DBUG_VOID_RETURN;
}


/*
  Increments the number of running worker threads

  Synopsis
    Event_scheduler::register_worker_thread()
      thd_id  The id of the thread that starts
  
  Returns
    EVENT_OP_OK           Ok
    EVENT_OP_NOT_RUNNING  Scheduler not working
*/

enum Event_scheduler::event_manager_op_code
Event_scheduler::register_worker_thread(ulong thd_id)
{
  enum Event_scheduler::event_manager_op_code ret= EVENT_OP_OK;

  DBUG_ENTER("Event_scheduler::register_worker_thread");
  pthread_mutex_lock(&LOCK_manager);
  DBUG_ASSERT(ston);
  if (ston)
  {
    pthread_mutex_lock(&ston->LOCK_mutex);
    /* 
      Don't call ston->workers_increment() because the count was already
      incremented before pthread_create() was called.
    */
    DBUG_PRINT("info", ("Adding %lu to the list of running events", thd_id));
    push_dynamic(&ston->workers, (gptr) &thd_id);
    pthread_mutex_unlock(&ston->LOCK_mutex);
  }
  else
    ret= EVENT_OP_NOT_RUNNING;
  pthread_mutex_unlock(&LOCK_manager);

  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
    EVENT_OP_OK           Ok
    EVENT_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::event_manager_op_code
Event_scheduler::deregister_worker_thread(ulong thd_id)
{
  enum Event_scheduler::event_manager_op_code ret= EVENT_OP_OK;

  DBUG_ENTER("Event_scheduler::deregister_worker_thread");
  pthread_mutex_lock(&LOCK_manager);
  DBUG_ASSERT(ston);
  if (ston)
  {
    pthread_mutex_lock(&ston->LOCK_mutex);
    ston->workers_decrement(thd_id);
    if (ston->mode == MANAGER_RUNNING || ston->mode == MANAGER_SHUTDOWN)
    {
      register uint i= 0;
      register ulong tid= thd_id;
      for (; i < ston->workers.elements; ++i)
        if (*dynamic_element(&ston->workers, i, ulong*) == tid)
        {
          delete_dynamic_element(&ston->workers, i);
          break;
        }
    }
    DBUG_PRINT("info", ("workers_count=%d workers.elements=%lu",
               ston->workers_count, ston->workers.elements));
    if (ston->mode == MANAGER_SHUTDOWN && !ston->workers_count)
    {
      DBUG_PRINT("info", ("Sending out COND_last_worker_exit"));
      pthread_cond_signal(&ston->COND_last_worker_exit);
    }
    pthread_mutex_unlock(&ston->LOCK_mutex);
  }
  else
    ret= EVENT_OP_NOT_RUNNING;
  pthread_mutex_unlock(&LOCK_manager);

  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");
  pthread_mutex_lock(&LOCK_mutex);
  if (mode == MANAGER_RUNNING)
    n= queue.elements;
  else
    n= -1;
  pthread_mutex_unlock(&LOCK_mutex);

  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

  RETURN VALUE
    NULL       Error during compile
    otherwise  Address
*/

Event_timed *
Event_scheduler::load_and_compile_event(THD *thd, Event_timed *etn)
{
  int ret= 0;
  MEM_ROOT *tmp_mem_root;
  Event_timed *et_new;
  Open_tables_state backup;

  DBUG_ENTER("Event_scheduler::load_and_compile_event");
  DBUG_PRINT("enter", ("name: %s", 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_new, NULL, &manager_root);
  }
  thd->restore_backup_open_tables_state(&backup);
  if (ret)
    DBUG_RETURN(NULL);

  et_new->compute_next_execution_time();

  DBUG_RETURN(et_new);
}


/*
   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

   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;

  DBUG_ENTER("evex_load_events_from_db");

  pthread_mutex_lock(&LOCK_mutex);
  if (mode > MANAGER_STARTING)
  {
    DBUG_ASSERT(0);
    sql_print_error("SCHEDULER: Trying to load events while already running.");
    pthread_mutex_unlock(&LOCK_mutex);
    DBUG_RETURN(EVEX_GENERAL_ERROR);
  }
  pthread_mutex_unlock(&LOCK_mutex);

  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_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("load_events_from_db", ("Out of memory"));
      ret= -1;
      goto end;
    }
    DBUG_PRINT("load_events_from_db", ("Loading event from row."));

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

    DBUG_PRINT("load_events_from_db",
               ("Event %s loaded from row. Time to compile", et->name.str));
    
    switch (ret= et->compile(thd, &manager_root)) {
    case EVEX_MICROSECOND_UNSUP:
      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:
      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 to the exec list."));

    queue_insert_safe(&queue,  (byte *) et);
    DBUG_PRINT("load_events_from_db", ("%p %*s",
               et, et->name.length,et->name.str));
    count++;
  }

  ret= 0;

end:
  end_read_record(&read_record_info);

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

  close_thread_tables(thd);
  if (!ret)
    sql_print_information("SCHEDULER: Loaded %d event%s", count, (count == 1)?"":"s");
  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;

  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);
}




#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::run_tests(THD *thd)
{
  Protocol *protocol= thd->protocol;
  Event_scheduler *mgr;
  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::ston, "Old manager");
  
  mgr= Event_scheduler::get_instance();
  ERR_W_MSG(mgr, "Create new manager");

  ERR_W_MSG((mgr->mode == Event_scheduler::MANAGER_INIT),
                  "Test manager mode ");

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

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

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

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

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


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

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

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

  mgr->drop_event(thd, &et, Event_scheduler::EVENT_STOP_SYNC);
  ERR_W_MSG(mgr->events_count() == 3, "Test delete 1");

  mgr->drop_event(thd, &et, Event_scheduler::EVENT_STOP_SYNC);
  ERR_W_MSG(mgr->events_count() == 2, "Test delete 2");

  mgr->drop_event(thd, &et, Event_scheduler::EVENT_STOP_SYNC);
  ERR_W_MSG(mgr->events_count() == 1, "Test delete 3");

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

  mgr->replace_event(thd, &et2, Event_scheduler::EVENT_STOP_SYNC);
  ERR_W_MSG(mgr->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(!mgr->shutdown(EVENT_STOP_SYNC), "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(!mgr->start(), "Start manager again and sleep 2s");
  ERR_W_MSG(mgr->start()==
              Event_scheduler::EVENT_OP_ALREADY_RUNNING,
            "Try to start again");

  pthread_mutex_lock(&mgr->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(!mgr->add_event(thd, &et, true),  "Add et to the queue, step 1");
  ERR_W_MSG(mgr->events_count() == 1, "Add et to queue, step 2");

  ERR_W_MSG(!mgr->add_event(thd, &et2, true),  "Add et2 to the queue, step 1");
  ERR_W_MSG(mgr->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(&mgr->COND_executed,
                                             &mgr->LOCK_executed,
                                             &abstime))
  {
    exec_count++;
    sql_print_information("Exec_count=%d", exec_count);
  }

  pthread_mutex_unlock(&mgr->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(!mgr->shutdown(EVENT_STOP_SYNC), "Shutdown manager");
  ERR_W_MSG(mgr->shutdown(EVENT_STOP_SYNC)==
             Event_scheduler::EVENT_OP_NOT_RUNNING,
            "Shutdown manager again for checking");

  ERR_W_MSG(!Event_scheduler::destroy(), "Destroy new manager");
   
  ERR_W_MSG(!Event_scheduler::ston, "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:
  mgr->shutdown(EVENT_STOP_SYNC);
  Event_scheduler::destroy();
  send_eof(thd);
  DBUG_RETURN(true);
}

#endif

--- New file ---
+++ sql/event_scheduler.h	06/04/04 22:41:48
#ifndef _EVENT_MANAGER_H_
#define _EVENT_MANAGER_H_

class THD;

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

  ->LOCK_manager 
  --->LOCK_mutex (protects `mode`)
  ----->LOCK_workers_count (protects `workers_count`)
  ----->LOCK_last_worker_exit (for COND_last_worker_exit)
*/


class Event_scheduler
{
public:
  /*
    This mutex is public so we can initialize it during server startup.
    The reason to initialize it there is that we use singleton and this
    mutex guards creation and destruction of the singleton object.
  */
  static pthread_mutex_t LOCK_manager;

  /* Storage for @@event_scheduler */
  static my_bool manager_working;

  /* SYNC or ASYNC stop of running events */
  enum event_drop_mode
  {
    EVENT_STOP_ASYNC = 1,
    EVENT_STOP_SYNC =2
  };

  /* The life cycle of a manager */
  enum event_manager_mode
  {
    MANAGER_INIT= 0,
    MANAGER_STARTING,
    MANAGER_CANTSTART,
    MANAGER_RUNNING,
    MANAGER_SHUTDOWN,
    MANAGER_STOPPED,
  };

  /* Return codes */
  enum event_manager_op_code
  {
    EVENT_OP_OK= 0,
    EVENT_OP_CANTFORK,
    EVENT_OP_CANTSTART,
    EVENT_OP_ALREADY_RUNNING,
    EVENT_OP_NOT_RUNNING,
    EVENT_OP_CANT_KILL,
    EVENT_OP_CANT_INIT
  };

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

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

  bool
  drop_event(THD *thd, Event_timed *etn, enum event_drop_mode drop_mode);

  bool
  replace_event(THD *thd, Event_timed *et, enum event_drop_mode replace_mode);

  /*
    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);

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

  int
  events_count();

  enum event_manager_op_code
  start();

  bool
  run(THD *thd);

  enum event_manager_op_code
  shutdown(enum event_drop_mode stop_mode);

  /* Call this only at the end of the server. */
  static bool
  destroy()
  {
    pthread_mutex_lock(&LOCK_manager);
    if (ston && ston->mode == MANAGER_RUNNING)
    {
      /* the master thread should not be running */
      DBUG_ASSERT(0);
      return true;
    }
    delete ston;
    ston= NULL;
    pthread_mutex_unlock(&LOCK_manager);
    return false;
  }

  static enum event_manager_op_code
  event_scheduler_var_update(my_bool new_value);

  static enum event_manager_op_code
  register_worker_thread(ulong thd_id);

  static enum event_manager_op_code
  deregister_worker_thread(ulong thd_id);

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

protected:
  void
  workers_increment(ulong thd_id);

  void
  workers_decrement(ulong thd_id);

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

  Event_timed *
  load_and_compile_event(THD *thd, Event_timed *etn);

  int
  load_events_from_db(THD *thd);

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

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

  /*
    LOCK PROCOTOL: Expects that this method is synchronized
  */
  ~Event_scheduler() 
  {
    /* ToDo: Wait if there are elements on the queue */
    pthread_mutex_lock(&LOCK_mutex);
    ston->mode= MANAGER_SHUTDOWN;
    pthread_mutex_unlock(&LOCK_mutex);

    delete_queue(&queue);
    pthread_cond_destroy(&COND_new_work);
    pthread_cond_destroy(&COND_started);
#ifndef DBUG_OFF
    pthread_cond_destroy(&COND_executed);
    pthread_mutex_destroy(&LOCK_executed);
#endif
    mode= MANAGER_STOPPED;
    pthread_mutex_destroy(&LOCK_mutex);
  }

  /* Singleton instance */
  static Event_scheduler *ston;
  
  /* This is the current status of the life-cycle of the manager. */
  enum event_manager_mode mode;

  /*
    LOCK_mutex 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_mutex;

  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 `mode` is
    either MODE_RUNNING or MODE_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 `mode` is set back by the executor thread to MODE_INIT, 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;
  
  MEM_ROOT       manager_root;
  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
  
  */
  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_MANAGER_H_ */


--- 1.47/sql/event_timed.cc	2006-03-17 09:36:32 +01:00
+++ 1.48/sql/event_timed.cc	2006-04-04 22:41:46 +02:00
@@ -927,6 +927,9 @@ Event_timed::compute_next_execution_time
     goto ret;
   }
 ret:
+  sql_print_information("SCHEDULER: Next execution at [%04d-%02d-%02d %02d:%02d:%02d UTC]",
+                         execute_at.year, execute_at.month, execute_at.day,
+                         execute_at.hour, execute_at.minute, execute_at.second);
 
   DBUG_RETURN(false);
 err:
@@ -1545,4 +1548,21 @@ 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
+    !0   Error 
+*/
+
+int
+Event_timed::kill_thread()
+{
+  return false;
 }

--- 1.180/sql/set_var.cc	2006-03-21 10:54:21 +01:00
+++ 1.181/sql/set_var.cc	2006-04-04 22:41:47 +02: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);
Thread
bk commit into 5.1 tree (andrey:1.2234) BUG#17619ahristov4 Apr