| 1 | /* Copyright (c) 2006, 2013, Oracle and/or its affiliates. |
| 2 | |
| 3 | This program is free software; you can redistribute it and/or modify |
| 4 | it under the terms of the GNU General Public License as published by |
| 5 | the Free Software Foundation; version 2 of the License. |
| 6 | |
| 7 | This program is distributed in the hope that it will be useful, |
| 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 10 | GNU General Public License for more details. |
| 11 | |
| 12 | You should have received a copy of the GNU General Public License |
| 13 | along with this program; if not, write to the Free Software |
| 14 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
| 15 | |
| 16 | #include "mariadb.h" |
| 17 | #include "sql_priv.h" |
| 18 | #include "unireg.h" |
| 19 | #include "event_scheduler.h" |
| 20 | #include "events.h" |
| 21 | #include "event_data_objects.h" |
| 22 | #include "event_queue.h" |
| 23 | #include "event_db_repository.h" |
| 24 | #include "sql_connect.h" // init_new_connection_handler_thread |
| 25 | #include "sql_acl.h" // SUPER_ACL |
| 26 | |
| 27 | /** |
| 28 | @addtogroup Event_Scheduler |
| 29 | @{ |
| 30 | */ |
| 31 | |
| 32 | #ifdef __GNUC__ |
| 33 | #if __GNUC__ >= 2 |
| 34 | #define SCHED_FUNC __FUNCTION__ |
| 35 | #endif |
| 36 | #else |
| 37 | #define SCHED_FUNC "<unknown>" |
| 38 | #endif |
| 39 | |
| 40 | #define LOCK_DATA() lock_data(SCHED_FUNC, __LINE__) |
| 41 | #define UNLOCK_DATA() unlock_data(SCHED_FUNC, __LINE__) |
| 42 | #define COND_STATE_WAIT(mythd, abstime, stage) \ |
| 43 | cond_wait(mythd, abstime, stage, SCHED_FUNC, __FILE__, __LINE__) |
| 44 | |
| 45 | extern pthread_attr_t connection_attrib; |
| 46 | extern ulong event_executed; |
| 47 | |
| 48 | Event_db_repository *Event_worker_thread::db_repository; |
| 49 | |
| 50 | |
| 51 | static |
| 52 | const LEX_CSTRING scheduler_states_names[] = |
| 53 | { |
| 54 | { STRING_WITH_LEN("INITIALIZED" ) }, |
| 55 | { STRING_WITH_LEN("RUNNING" ) }, |
| 56 | { STRING_WITH_LEN("STOPPING" ) } |
| 57 | }; |
| 58 | |
| 59 | struct scheduler_param { |
| 60 | THD *thd; |
| 61 | Event_scheduler *scheduler; |
| 62 | }; |
| 63 | |
| 64 | |
| 65 | /* |
| 66 | Prints the stack of infos, warnings, errors from thd to |
| 67 | the console so it can be fetched by the logs-into-tables and |
| 68 | checked later. |
| 69 | |
| 70 | SYNOPSIS |
| 71 | evex_print_warnings |
| 72 | thd Thread used during the execution of the event |
| 73 | et The event itself |
| 74 | */ |
| 75 | |
| 76 | void |
| 77 | Event_worker_thread::print_warnings(THD *thd, Event_job_data *et) |
| 78 | { |
| 79 | const Sql_condition *err; |
| 80 | DBUG_ENTER("evex_print_warnings" ); |
| 81 | if (thd->get_stmt_da()->is_warning_info_empty()) |
| 82 | DBUG_VOID_RETURN; |
| 83 | |
| 84 | char msg_buf[10 * STRING_BUFFER_USUAL_SIZE]; |
| 85 | char prefix_buf[5 * STRING_BUFFER_USUAL_SIZE]; |
| 86 | String prefix(prefix_buf, sizeof(prefix_buf), system_charset_info); |
| 87 | prefix.length(0); |
| 88 | prefix.append(STRING_WITH_LEN("Event Scheduler: [" )); |
| 89 | |
| 90 | prefix.append(et->definer.str, et->definer.length, system_charset_info); |
| 91 | prefix.append("][" , 2); |
| 92 | prefix.append(et->dbname.str, et->dbname.length, system_charset_info); |
| 93 | prefix.append('.'); |
| 94 | prefix.append(et->name.str, et->name.length, system_charset_info); |
| 95 | prefix.append("] " , 2); |
| 96 | |
| 97 | Diagnostics_area::Sql_condition_iterator it= |
| 98 | thd->get_stmt_da()->sql_conditions(); |
| 99 | while ((err= it++)) |
| 100 | { |
| 101 | String err_msg(msg_buf, sizeof(msg_buf), system_charset_info); |
| 102 | /* set it to 0 or we start adding at the end. That's the trick ;) */ |
| 103 | err_msg.length(0); |
| 104 | err_msg.append(prefix); |
| 105 | err_msg.append(err->get_message_text(), |
| 106 | err->get_message_octet_length(), system_charset_info); |
| 107 | DBUG_ASSERT(err->get_level() < 3); |
| 108 | (sql_print_message_handlers[err->get_level()])("%*s" , err_msg.length(), |
| 109 | err_msg.c_ptr_safe()); |
| 110 | } |
| 111 | DBUG_VOID_RETURN; |
| 112 | } |
| 113 | |
| 114 | |
| 115 | /* |
| 116 | Performs post initialization of structures in a new thread. |
| 117 | |
| 118 | SYNOPSIS |
| 119 | post_init_event_thread() |
| 120 | thd Thread |
| 121 | |
| 122 | NOTES |
| 123 | Before this is called, one should not do any DBUG_XXX() calls. |
| 124 | |
| 125 | */ |
| 126 | |
| 127 | bool |
| 128 | post_init_event_thread(THD *thd) |
| 129 | { |
| 130 | (void) init_new_connection_handler_thread(); |
| 131 | if (init_thr_lock() || thd->store_globals()) |
| 132 | { |
| 133 | thd->cleanup(); |
| 134 | return TRUE; |
| 135 | } |
| 136 | return FALSE; |
| 137 | } |
| 138 | |
| 139 | |
| 140 | /* |
| 141 | Cleans up the THD and the threaded environment of the thread. |
| 142 | |
| 143 | SYNOPSIS |
| 144 | deinit_event_thread() |
| 145 | thd Thread |
| 146 | */ |
| 147 | |
| 148 | void |
| 149 | deinit_event_thread(THD *thd) |
| 150 | { |
| 151 | thd->proc_info= "Clearing" ; |
| 152 | DBUG_PRINT("exit" , ("Event thread finishing" )); |
| 153 | unlink_not_visible_thd(thd); |
| 154 | delete thd; |
| 155 | } |
| 156 | |
| 157 | |
| 158 | /* |
| 159 | Performs pre- mysql_thread_create() initialisation of THD. Do this |
| 160 | in the thread that will pass THD to the child thread. In the |
| 161 | child thread call post_init_event_thread(). |
| 162 | |
| 163 | SYNOPSIS |
| 164 | pre_init_event_thread() |
| 165 | thd The THD of the thread. Has to be allocated by the caller. |
| 166 | |
| 167 | NOTES |
| 168 | 1. The host of the thead is my_localhost |
| 169 | 2. thd->net is initted with NULL - no communication. |
| 170 | */ |
| 171 | |
| 172 | void |
| 173 | pre_init_event_thread(THD* thd) |
| 174 | { |
| 175 | THD *orig_thd= current_thd; |
| 176 | DBUG_ENTER("pre_init_event_thread" ); |
| 177 | |
| 178 | set_current_thd(thd); |
| 179 | thd->client_capabilities= 0; |
| 180 | thd->security_ctx->master_access= 0; |
| 181 | thd->security_ctx->db_access= 0; |
| 182 | thd->security_ctx->host_or_ip= (char*)my_localhost; |
| 183 | my_net_init(&thd->net, NULL, thd, MYF(MY_THREAD_SPECIFIC)); |
| 184 | thd->security_ctx->set_user((char*)"event_scheduler" ); |
| 185 | thd->net.read_timeout= slave_net_timeout; |
| 186 | thd->variables.option_bits|= OPTION_AUTO_IS_NULL; |
| 187 | thd->client_capabilities|= CLIENT_MULTI_RESULTS; |
| 188 | add_to_active_threads(thd); |
| 189 | |
| 190 | /* |
| 191 | Guarantees that we will see the thread in SHOW PROCESSLIST though its |
| 192 | vio is NULL. |
| 193 | */ |
| 194 | |
| 195 | thd->proc_info= "Initialized" ; |
| 196 | thd->set_time(); |
| 197 | |
| 198 | /* Do not use user-supplied timeout value for system threads. */ |
| 199 | thd->variables.lock_wait_timeout= LONG_TIMEOUT; |
| 200 | |
| 201 | set_current_thd(orig_thd); |
| 202 | DBUG_VOID_RETURN; |
| 203 | } |
| 204 | |
| 205 | |
| 206 | /* |
| 207 | Function that executes the scheduler, |
| 208 | |
| 209 | SYNOPSIS |
| 210 | event_scheduler_thread() |
| 211 | arg Pointer to `struct scheduler_param` |
| 212 | |
| 213 | RETURN VALUE |
| 214 | 0 OK |
| 215 | */ |
| 216 | |
| 217 | pthread_handler_t |
| 218 | event_scheduler_thread(void *arg) |
| 219 | { |
| 220 | /* needs to be first for thread_stack */ |
| 221 | THD *thd= (THD *) ((struct scheduler_param *) arg)->thd; |
| 222 | Event_scheduler *scheduler= ((struct scheduler_param *) arg)->scheduler; |
| 223 | bool res; |
| 224 | |
| 225 | thd->thread_stack= (char *)&thd; // remember where our stack is |
| 226 | |
| 227 | mysql_thread_set_psi_id(thd->thread_id); |
| 228 | |
| 229 | res= post_init_event_thread(thd); |
| 230 | |
| 231 | DBUG_ENTER("event_scheduler_thread" ); |
| 232 | my_free(arg); |
| 233 | if (!res) |
| 234 | scheduler->run(thd); |
| 235 | |
| 236 | deinit_event_thread(thd); |
| 237 | DBUG_LEAVE; // Against gcc warnings |
| 238 | my_thread_end(); |
| 239 | return 0; |
| 240 | } |
| 241 | |
| 242 | |
| 243 | /** |
| 244 | Function that executes an event in a child thread. Setups the |
| 245 | environment for the event execution and cleans after that. |
| 246 | |
| 247 | SYNOPSIS |
| 248 | event_worker_thread() |
| 249 | arg The Event_job_data object to be processed |
| 250 | |
| 251 | RETURN VALUE |
| 252 | 0 OK |
| 253 | */ |
| 254 | |
| 255 | pthread_handler_t |
| 256 | event_worker_thread(void *arg) |
| 257 | { |
| 258 | THD *thd; |
| 259 | Event_queue_element_for_exec *event= (Event_queue_element_for_exec *)arg; |
| 260 | |
| 261 | thd= event->thd; |
| 262 | |
| 263 | mysql_thread_set_psi_id(thd->thread_id); |
| 264 | |
| 265 | Event_worker_thread worker_thread; |
| 266 | worker_thread.run(thd, event); |
| 267 | |
| 268 | my_thread_end(); |
| 269 | return 0; // Can't return anything here |
| 270 | } |
| 271 | |
| 272 | |
| 273 | /** |
| 274 | Function that executes an event in a child thread. Setups the |
| 275 | environment for the event execution and cleans after that. |
| 276 | |
| 277 | SYNOPSIS |
| 278 | Event_worker_thread::run() |
| 279 | thd Thread context |
| 280 | event The Event_queue_element_for_exec object to be processed |
| 281 | */ |
| 282 | |
| 283 | void |
| 284 | Event_worker_thread::run(THD *thd, Event_queue_element_for_exec *event) |
| 285 | { |
| 286 | /* needs to be first for thread_stack */ |
| 287 | char my_stack; |
| 288 | Event_job_data job_data; |
| 289 | bool res; |
| 290 | |
| 291 | DBUG_ASSERT(thd->m_digest == NULL); |
| 292 | DBUG_ASSERT(thd->m_statement_psi == NULL); |
| 293 | |
| 294 | thd->thread_stack= &my_stack; // remember where our stack is |
| 295 | res= post_init_event_thread(thd); |
| 296 | |
| 297 | DBUG_ENTER("Event_worker_thread::run" ); |
| 298 | DBUG_PRINT("info" , ("Time is %u, THD: %p" , (uint)my_time(0), thd)); |
| 299 | |
| 300 | if (res) |
| 301 | goto end; |
| 302 | |
| 303 | if ((res= db_repository->load_named_event(thd, &event->dbname, &event->name, |
| 304 | &job_data))) |
| 305 | { |
| 306 | DBUG_PRINT("error" , ("Got error from load_named_event" )); |
| 307 | goto end; |
| 308 | } |
| 309 | |
| 310 | thd->enable_slow_log= TRUE; |
| 311 | |
| 312 | res= job_data.execute(thd, event->dropped); |
| 313 | |
| 314 | print_warnings(thd, &job_data); |
| 315 | |
| 316 | if (res) |
| 317 | sql_print_information("Event Scheduler: " |
| 318 | "[%s].[%s.%s] event execution failed." , |
| 319 | job_data.definer.str, |
| 320 | job_data.dbname.str, job_data.name.str); |
| 321 | end: |
| 322 | DBUG_ASSERT(thd->m_statement_psi == NULL); |
| 323 | DBUG_ASSERT(thd->m_digest == NULL); |
| 324 | DBUG_PRINT("info" , ("Done with Event %s.%s" , event->dbname.str, |
| 325 | event->name.str)); |
| 326 | |
| 327 | delete event; |
| 328 | deinit_event_thread(thd); |
| 329 | |
| 330 | DBUG_VOID_RETURN; |
| 331 | } |
| 332 | |
| 333 | |
| 334 | Event_scheduler::Event_scheduler(Event_queue *queue_arg) |
| 335 | :state(INITIALIZED), |
| 336 | scheduler_thd(NULL), |
| 337 | queue(queue_arg), |
| 338 | mutex_last_locked_at_line(0), |
| 339 | mutex_last_unlocked_at_line(0), |
| 340 | mutex_last_locked_in_func("n/a" ), |
| 341 | mutex_last_unlocked_in_func("n/a" ), |
| 342 | mutex_scheduler_data_locked(FALSE), |
| 343 | waiting_on_cond(FALSE), |
| 344 | started_events(0) |
| 345 | { |
| 346 | mysql_mutex_init(key_event_scheduler_LOCK_scheduler_state, |
| 347 | &LOCK_scheduler_state, MY_MUTEX_INIT_FAST); |
| 348 | mysql_cond_init(key_event_scheduler_COND_state, &COND_state, NULL); |
| 349 | mysql_mutex_record_order(&LOCK_scheduler_state, &LOCK_global_system_variables); |
| 350 | } |
| 351 | |
| 352 | |
| 353 | Event_scheduler::~Event_scheduler() |
| 354 | { |
| 355 | stop(); /* does nothing if not running */ |
| 356 | mysql_mutex_destroy(&LOCK_scheduler_state); |
| 357 | mysql_cond_destroy(&COND_state); |
| 358 | } |
| 359 | |
| 360 | |
| 361 | /** |
| 362 | Starts the scheduler (again). Creates a new THD and passes it to |
| 363 | a forked thread. Does not wait for acknowledgement from the new |
| 364 | thread that it has started. Asynchronous starting. Most of the |
| 365 | needed initializations are done in the current thread to minimize |
| 366 | the chance of failure in the spawned thread. |
| 367 | |
| 368 | @param[out] err_no - errno indicating type of error which caused |
| 369 | failure to start scheduler thread. |
| 370 | |
| 371 | @return |
| 372 | @retval false Success. |
| 373 | @retval true Error. |
| 374 | */ |
| 375 | |
| 376 | bool |
| 377 | Event_scheduler::start(int *err_no) |
| 378 | { |
| 379 | THD *new_thd= NULL; |
| 380 | bool ret= false; |
| 381 | pthread_t th; |
| 382 | struct scheduler_param *scheduler_param_value; |
| 383 | DBUG_ENTER("Event_scheduler::start" ); |
| 384 | |
| 385 | LOCK_DATA(); |
| 386 | DBUG_PRINT("info" , ("state before action %s" , scheduler_states_names[state].str)); |
| 387 | if (state > INITIALIZED) |
| 388 | goto end; |
| 389 | |
| 390 | if (!(new_thd= new THD(next_thread_id()))) |
| 391 | { |
| 392 | sql_print_error("Event Scheduler: Cannot initialize the scheduler thread" ); |
| 393 | ret= true; |
| 394 | goto end; |
| 395 | } |
| 396 | |
| 397 | pre_init_event_thread(new_thd); |
| 398 | new_thd->system_thread= SYSTEM_THREAD_EVENT_SCHEDULER; |
| 399 | new_thd->set_command(COM_DAEMON); |
| 400 | |
| 401 | /* |
| 402 | We should run the event scheduler thread under the super-user privileges. |
| 403 | In particular, this is needed to be able to lock the mysql.event table |
| 404 | for writing when the server is running in the read-only mode. |
| 405 | |
| 406 | Same goes for transaction access mode. Set it to read-write for this thd. |
| 407 | */ |
| 408 | new_thd->security_ctx->master_access |= SUPER_ACL; |
| 409 | new_thd->variables.tx_read_only= false; |
| 410 | new_thd->tx_read_only= false; |
| 411 | |
| 412 | /* This should not be marked with MY_THREAD_SPECIFIC */ |
| 413 | scheduler_param_value= |
| 414 | (struct scheduler_param *)my_malloc(sizeof(struct scheduler_param), MYF(0)); |
| 415 | scheduler_param_value->thd= new_thd; |
| 416 | scheduler_param_value->scheduler= this; |
| 417 | |
| 418 | scheduler_thd= new_thd; |
| 419 | DBUG_PRINT("info" , ("Setting state go RUNNING" )); |
| 420 | state= RUNNING; |
| 421 | DBUG_PRINT("info" , ("Forking new thread for scheduler. THD: %p" , new_thd)); |
| 422 | if ((*err_no= mysql_thread_create(key_thread_event_scheduler, |
| 423 | &th, &connection_attrib, |
| 424 | event_scheduler_thread, |
| 425 | (void*)scheduler_param_value))) |
| 426 | { |
| 427 | DBUG_PRINT("error" , ("cannot create a new thread" )); |
| 428 | sql_print_error("Event scheduler: Failed to start scheduler," |
| 429 | " Can not create thread for event scheduler (errno=%d)" , |
| 430 | *err_no); |
| 431 | |
| 432 | state= INITIALIZED; |
| 433 | scheduler_thd= NULL; |
| 434 | deinit_event_thread(new_thd); |
| 435 | |
| 436 | delete scheduler_param_value; |
| 437 | ret= true; |
| 438 | } |
| 439 | |
| 440 | end: |
| 441 | UNLOCK_DATA(); |
| 442 | DBUG_RETURN(ret); |
| 443 | } |
| 444 | |
| 445 | |
| 446 | /* |
| 447 | The main loop of the scheduler. |
| 448 | |
| 449 | SYNOPSIS |
| 450 | Event_scheduler::run() |
| 451 | thd Thread |
| 452 | |
| 453 | RETURN VALUE |
| 454 | FALSE OK |
| 455 | TRUE Error (Serious error) |
| 456 | */ |
| 457 | |
| 458 | bool |
| 459 | Event_scheduler::run(THD *thd) |
| 460 | { |
| 461 | int res= FALSE; |
| 462 | DBUG_ENTER("Event_scheduler::run" ); |
| 463 | |
| 464 | sql_print_information("Event Scheduler: scheduler thread started with id %lu" , |
| 465 | (ulong) thd->thread_id); |
| 466 | /* |
| 467 | Recalculate the values in the queue because there could have been stops |
| 468 | in executions of the scheduler and some times could have passed by. |
| 469 | */ |
| 470 | queue->recalculate_activation_times(thd); |
| 471 | |
| 472 | while (is_running()) |
| 473 | { |
| 474 | Event_queue_element_for_exec *event_name; |
| 475 | |
| 476 | /* Gets a minimized version */ |
| 477 | if (queue->get_top_for_execution_if_time(thd, &event_name)) |
| 478 | { |
| 479 | sql_print_information("Event Scheduler: " |
| 480 | "Serious error during getting next " |
| 481 | "event to execute. Stopping" ); |
| 482 | break; |
| 483 | } |
| 484 | |
| 485 | DBUG_PRINT("info" , ("get_top_for_execution_if_time returned " |
| 486 | "event_name=%p" , event_name)); |
| 487 | if (event_name) |
| 488 | { |
| 489 | if ((res= execute_top(event_name))) |
| 490 | break; |
| 491 | } |
| 492 | else |
| 493 | { |
| 494 | DBUG_ASSERT(thd->killed); |
| 495 | DBUG_PRINT("info" , ("job_data is NULL, the thread was killed" )); |
| 496 | } |
| 497 | DBUG_PRINT("info" , ("state=%s" , scheduler_states_names[state].str)); |
| 498 | } |
| 499 | |
| 500 | LOCK_DATA(); |
| 501 | scheduler_thd= NULL; |
| 502 | state= INITIALIZED; |
| 503 | DBUG_PRINT("info" , ("Broadcasting COND_state back to the stoppers" )); |
| 504 | mysql_cond_broadcast(&COND_state); |
| 505 | UNLOCK_DATA(); |
| 506 | |
| 507 | DBUG_RETURN(res); |
| 508 | } |
| 509 | |
| 510 | |
| 511 | /* |
| 512 | Creates a new THD instance and then forks a new thread, while passing |
| 513 | the THD pointer and job_data to it. |
| 514 | |
| 515 | SYNOPSIS |
| 516 | Event_scheduler::execute_top() |
| 517 | |
| 518 | RETURN VALUE |
| 519 | FALSE OK |
| 520 | TRUE Error (Serious error) |
| 521 | */ |
| 522 | |
| 523 | bool |
| 524 | Event_scheduler::execute_top(Event_queue_element_for_exec *event_name) |
| 525 | { |
| 526 | THD *new_thd; |
| 527 | pthread_t th; |
| 528 | int res= 0; |
| 529 | DBUG_ENTER("Event_scheduler::execute_top" ); |
| 530 | |
| 531 | if (!(new_thd= new THD(next_thread_id()))) |
| 532 | goto error; |
| 533 | |
| 534 | pre_init_event_thread(new_thd); |
| 535 | new_thd->system_thread= SYSTEM_THREAD_EVENT_WORKER; |
| 536 | event_name->thd= new_thd; |
| 537 | DBUG_PRINT("info" , ("Event %s@%s ready for start" , |
| 538 | event_name->dbname.str, event_name->name.str)); |
| 539 | |
| 540 | /* |
| 541 | TODO: should use thread pool here, preferably with an upper limit |
| 542 | on number of threads: if too many events are scheduled for the |
| 543 | same time, starting all of them at once won't help them run truly |
| 544 | in parallel (because of the great amount of synchronization), so |
| 545 | we may as well execute them in sequence, keeping concurrency at a |
| 546 | reasonable level. |
| 547 | */ |
| 548 | /* Major failure */ |
| 549 | if ((res= mysql_thread_create(key_thread_event_worker, |
| 550 | &th, &connection_attrib, event_worker_thread, |
| 551 | event_name))) |
| 552 | { |
| 553 | mysql_mutex_lock(&LOCK_global_system_variables); |
| 554 | Events::opt_event_scheduler= Events::EVENTS_OFF; |
| 555 | mysql_mutex_unlock(&LOCK_global_system_variables); |
| 556 | |
| 557 | sql_print_error("Event_scheduler::execute_top: Can not create event worker" |
| 558 | " thread (errno=%d). Stopping event scheduler" , res); |
| 559 | |
| 560 | deinit_event_thread(new_thd); |
| 561 | goto error; |
| 562 | } |
| 563 | |
| 564 | started_events++; |
| 565 | executed_events++; // For SHOW STATUS |
| 566 | |
| 567 | DBUG_PRINT("info" , ("Event is in THD: %p" , new_thd)); |
| 568 | DBUG_RETURN(FALSE); |
| 569 | |
| 570 | error: |
| 571 | DBUG_PRINT("error" , ("Event_scheduler::execute_top() res: %d" , res)); |
| 572 | delete event_name; |
| 573 | DBUG_RETURN(TRUE); |
| 574 | } |
| 575 | |
| 576 | |
| 577 | /* |
| 578 | Checks whether the state of the scheduler is RUNNING |
| 579 | |
| 580 | SYNOPSIS |
| 581 | Event_scheduler::is_running() |
| 582 | |
| 583 | RETURN VALUE |
| 584 | TRUE RUNNING |
| 585 | FALSE Not RUNNING |
| 586 | */ |
| 587 | |
| 588 | bool |
| 589 | Event_scheduler::is_running() |
| 590 | { |
| 591 | LOCK_DATA(); |
| 592 | bool ret= (state == RUNNING); |
| 593 | UNLOCK_DATA(); |
| 594 | return ret; |
| 595 | } |
| 596 | |
| 597 | |
| 598 | /** |
| 599 | Stops the scheduler (again). Waits for acknowledgement from the |
| 600 | scheduler that it has stopped - synchronous stopping. |
| 601 | |
| 602 | Already running events will not be stopped. If the user needs |
| 603 | them stopped manual intervention is needed. |
| 604 | |
| 605 | SYNOPSIS |
| 606 | Event_scheduler::stop() |
| 607 | |
| 608 | RETURN VALUE |
| 609 | FALSE OK |
| 610 | TRUE Error (not reported) |
| 611 | */ |
| 612 | |
| 613 | bool |
| 614 | Event_scheduler::stop() |
| 615 | { |
| 616 | THD *thd= current_thd; |
| 617 | DBUG_ENTER("Event_scheduler::stop" ); |
| 618 | DBUG_PRINT("enter" , ("thd: %p" , thd)); |
| 619 | |
| 620 | LOCK_DATA(); |
| 621 | DBUG_PRINT("info" , ("state before action %s" , scheduler_states_names[state].str)); |
| 622 | if (state != RUNNING) |
| 623 | { |
| 624 | /* Synchronously wait until the scheduler stops. */ |
| 625 | while (state != INITIALIZED) |
| 626 | COND_STATE_WAIT(thd, NULL, &stage_waiting_for_scheduler_to_stop); |
| 627 | goto end; |
| 628 | } |
| 629 | |
| 630 | /* Guarantee we don't catch spurious signals */ |
| 631 | do { |
| 632 | DBUG_PRINT("info" , ("Waiting for COND_started_or_stopped from " |
| 633 | "the scheduler thread. Current value of state is %s . " |
| 634 | "workers count=%d" , scheduler_states_names[state].str, |
| 635 | workers_count())); |
| 636 | /* |
| 637 | NOTE: We don't use kill_one_thread() because it can't kill COM_DEAMON |
| 638 | threads. In addition, kill_one_thread() requires THD but during shutdown |
| 639 | current_thd is NULL. Hence, if kill_one_thread should be used it has to |
| 640 | be modified to kill also daemons, by adding a flag, and also we have to |
| 641 | create artificial THD here. To save all this work, we just do what |
| 642 | kill_one_thread() does to kill a thread. See also sql_repl.cc for similar |
| 643 | usage. |
| 644 | */ |
| 645 | |
| 646 | state= STOPPING; |
| 647 | DBUG_PRINT("info" , ("Scheduler thread has id %lu" , |
| 648 | (ulong) scheduler_thd->thread_id)); |
| 649 | /* This will wake up the thread if it waits on Queue's conditional */ |
| 650 | sql_print_information("Event Scheduler: Killing the scheduler thread, " |
| 651 | "thread id %lu" , |
| 652 | (ulong) scheduler_thd->thread_id); |
| 653 | scheduler_thd->awake(KILL_CONNECTION); |
| 654 | |
| 655 | /* thd could be 0x0, when shutting down */ |
| 656 | sql_print_information("Event Scheduler: " |
| 657 | "Waiting for the scheduler thread to reply" ); |
| 658 | |
| 659 | /* |
| 660 | Wait only 2 seconds, as there is a small chance the thread missed the |
| 661 | above awake() call and we may have to do it again |
| 662 | */ |
| 663 | struct timespec top_time; |
| 664 | set_timespec(top_time, 2); |
| 665 | COND_STATE_WAIT(thd, &top_time, &stage_waiting_for_scheduler_to_stop); |
| 666 | } while (state == STOPPING); |
| 667 | DBUG_PRINT("info" , ("Scheduler thread has cleaned up. Set state to INIT" )); |
| 668 | sql_print_information("Event Scheduler: Stopped" ); |
| 669 | end: |
| 670 | UNLOCK_DATA(); |
| 671 | DBUG_RETURN(FALSE); |
| 672 | } |
| 673 | |
| 674 | |
| 675 | /* |
| 676 | Returns the number of living event worker threads. |
| 677 | |
| 678 | SYNOPSIS |
| 679 | Event_scheduler::workers_count() |
| 680 | */ |
| 681 | |
| 682 | uint |
| 683 | Event_scheduler::workers_count() |
| 684 | { |
| 685 | THD *tmp; |
| 686 | uint count= 0; |
| 687 | |
| 688 | DBUG_ENTER("Event_scheduler::workers_count" ); |
| 689 | mysql_mutex_lock(&LOCK_thread_count); // For unlink from list |
| 690 | I_List_iterator<THD> it(threads); |
| 691 | while ((tmp=it++)) |
| 692 | if (tmp->system_thread == SYSTEM_THREAD_EVENT_WORKER) |
| 693 | ++count; |
| 694 | mysql_mutex_unlock(&LOCK_thread_count); |
| 695 | DBUG_PRINT("exit" , ("%d" , count)); |
| 696 | DBUG_RETURN(count); |
| 697 | } |
| 698 | |
| 699 | |
| 700 | /* |
| 701 | Auxiliary function for locking LOCK_scheduler_state. Used |
| 702 | by the LOCK_DATA macro. |
| 703 | |
| 704 | SYNOPSIS |
| 705 | Event_scheduler::lock_data() |
| 706 | func Which function is requesting mutex lock |
| 707 | line On which line mutex lock is requested |
| 708 | */ |
| 709 | |
| 710 | void |
| 711 | Event_scheduler::lock_data(const char *func, uint line) |
| 712 | { |
| 713 | DBUG_ENTER("Event_scheduler::lock_data" ); |
| 714 | DBUG_PRINT("enter" , ("func=%s line=%u" , func, line)); |
| 715 | mysql_mutex_lock(&LOCK_scheduler_state); |
| 716 | mutex_last_locked_in_func= func; |
| 717 | mutex_last_locked_at_line= line; |
| 718 | mutex_scheduler_data_locked= TRUE; |
| 719 | DBUG_VOID_RETURN; |
| 720 | } |
| 721 | |
| 722 | |
| 723 | /* |
| 724 | Auxiliary function for unlocking LOCK_scheduler_state. Used |
| 725 | by the UNLOCK_DATA macro. |
| 726 | |
| 727 | SYNOPSIS |
| 728 | Event_scheduler::unlock_data() |
| 729 | func Which function is requesting mutex unlock |
| 730 | line On which line mutex unlock is requested |
| 731 | */ |
| 732 | |
| 733 | void |
| 734 | Event_scheduler::unlock_data(const char *func, uint line) |
| 735 | { |
| 736 | DBUG_ENTER("Event_scheduler::unlock_data" ); |
| 737 | DBUG_PRINT("enter" , ("func=%s line=%u" , func, line)); |
| 738 | mutex_last_unlocked_at_line= line; |
| 739 | mutex_scheduler_data_locked= FALSE; |
| 740 | mutex_last_unlocked_in_func= func; |
| 741 | mysql_mutex_unlock(&LOCK_scheduler_state); |
| 742 | DBUG_VOID_RETURN; |
| 743 | } |
| 744 | |
| 745 | |
| 746 | /* |
| 747 | Wrapper for mysql_cond_wait/timedwait |
| 748 | |
| 749 | SYNOPSIS |
| 750 | Event_scheduler::cond_wait() |
| 751 | thd Thread (Could be NULL during shutdown procedure) |
| 752 | abstime If not null then call mysql_cond_timedwait() |
| 753 | msg Message for thd->proc_info |
| 754 | func Which function is requesting cond_wait |
| 755 | line On which line cond_wait is requested |
| 756 | */ |
| 757 | |
| 758 | void |
| 759 | Event_scheduler::cond_wait(THD *thd, struct timespec *abstime, const PSI_stage_info *stage, |
| 760 | const char *src_func, const char *src_file, uint src_line) |
| 761 | { |
| 762 | DBUG_ENTER("Event_scheduler::cond_wait" ); |
| 763 | waiting_on_cond= TRUE; |
| 764 | mutex_last_unlocked_at_line= src_line; |
| 765 | mutex_scheduler_data_locked= FALSE; |
| 766 | mutex_last_unlocked_in_func= src_func; |
| 767 | if (thd) |
| 768 | thd->enter_cond(&COND_state, &LOCK_scheduler_state, stage, |
| 769 | NULL, src_func, src_file, src_line); |
| 770 | |
| 771 | DBUG_PRINT("info" , ("mysql_cond_%swait" , abstime? "timed" :"" )); |
| 772 | if (!abstime) |
| 773 | mysql_cond_wait(&COND_state, &LOCK_scheduler_state); |
| 774 | else |
| 775 | mysql_cond_timedwait(&COND_state, &LOCK_scheduler_state, abstime); |
| 776 | if (thd) |
| 777 | { |
| 778 | /* |
| 779 | This will free the lock so we need to relock. Not the best thing to |
| 780 | do but we need to obey cond_wait() |
| 781 | */ |
| 782 | thd->exit_cond(NULL, src_func, src_file, src_line); |
| 783 | LOCK_DATA(); |
| 784 | } |
| 785 | mutex_last_locked_in_func= src_func; |
| 786 | mutex_last_locked_at_line= src_line; |
| 787 | mutex_scheduler_data_locked= TRUE; |
| 788 | waiting_on_cond= FALSE; |
| 789 | DBUG_VOID_RETURN; |
| 790 | } |
| 791 | |
| 792 | |
| 793 | /* |
| 794 | Dumps the internal status of the scheduler |
| 795 | |
| 796 | SYNOPSIS |
| 797 | Event_scheduler::dump_internal_status() |
| 798 | */ |
| 799 | |
| 800 | void |
| 801 | Event_scheduler::dump_internal_status() |
| 802 | { |
| 803 | DBUG_ENTER("Event_scheduler::dump_internal_status" ); |
| 804 | |
| 805 | puts("" ); |
| 806 | puts("Event scheduler status:" ); |
| 807 | printf("State : %s\n" , scheduler_states_names[state].str); |
| 808 | printf("Thread id : %lu\n" , scheduler_thd ? |
| 809 | (ulong) scheduler_thd->thread_id : (ulong) 0); |
| 810 | printf("LLA : %s:%u\n" , mutex_last_locked_in_func, |
| 811 | mutex_last_locked_at_line); |
| 812 | printf("LUA : %s:%u\n" , mutex_last_unlocked_in_func, |
| 813 | mutex_last_unlocked_at_line); |
| 814 | printf("WOC : %s\n" , waiting_on_cond? "YES" :"NO" ); |
| 815 | printf("Workers : %u\n" , workers_count()); |
| 816 | printf("Executed : %lu\n" , (ulong) started_events); |
| 817 | printf("Data locked: %s\n" , mutex_scheduler_data_locked ? "YES" :"NO" ); |
| 818 | |
| 819 | DBUG_VOID_RETURN; |
| 820 | } |
| 821 | |
| 822 | /** |
| 823 | @} (End of group Event_Scheduler) |
| 824 | */ |
| 825 | |