| 1 | /* |
| 2 | Copyright (c) 2011, 2013 Monty Program Ab. |
| 3 | |
| 4 | This program is free software; you can redistribute it and/or modify |
| 5 | it under the terms of the GNU General Public License as published by |
| 6 | the Free Software Foundation; version 2 of the License. |
| 7 | |
| 8 | This program is distributed in the hope that it will be useful, |
| 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | GNU General Public License for more details. |
| 12 | |
| 13 | You should have received a copy of the GNU General Public License |
| 14 | along with this program; if not, write to the Free Software |
| 15 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ |
| 16 | |
| 17 | |
| 18 | #ifndef MY_APC_STANDALONE |
| 19 | |
| 20 | #include "mariadb.h" |
| 21 | #include "sql_class.h" |
| 22 | |
| 23 | #endif |
| 24 | |
| 25 | /* For standalone testing of APC system, see unittest/sql/my_apc-t.cc */ |
| 26 | |
| 27 | /* |
| 28 | Initialize the target. |
| 29 | |
| 30 | @note |
| 31 | Initialization must be done prior to enabling/disabling the target, or making |
| 32 | any call requests to it. |
| 33 | Initial state after initialization is 'disabled'. |
| 34 | */ |
| 35 | void Apc_target::init(mysql_mutex_t *target_mutex) |
| 36 | { |
| 37 | DBUG_ASSERT(!enabled); |
| 38 | LOCK_thd_kill_ptr= target_mutex; |
| 39 | #ifndef DBUG_OFF |
| 40 | n_calls_processed= 0; |
| 41 | #endif |
| 42 | } |
| 43 | |
| 44 | |
| 45 | /* [internal] Put request qe into the request list */ |
| 46 | |
| 47 | void Apc_target::enqueue_request(Call_request *qe) |
| 48 | { |
| 49 | mysql_mutex_assert_owner(LOCK_thd_kill_ptr); |
| 50 | if (apc_calls) |
| 51 | { |
| 52 | Call_request *after= apc_calls->prev; |
| 53 | qe->next= apc_calls; |
| 54 | apc_calls->prev= qe; |
| 55 | |
| 56 | qe->prev= after; |
| 57 | after->next= qe; |
| 58 | } |
| 59 | else |
| 60 | { |
| 61 | apc_calls= qe; |
| 62 | qe->next= qe->prev= qe; |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | |
| 67 | /* |
| 68 | [internal] Remove request qe from the request queue. |
| 69 | |
| 70 | The request is not necessarily first in the queue. |
| 71 | */ |
| 72 | |
| 73 | void Apc_target::dequeue_request(Call_request *qe) |
| 74 | { |
| 75 | mysql_mutex_assert_owner(LOCK_thd_kill_ptr); |
| 76 | if (apc_calls == qe) |
| 77 | { |
| 78 | if ((apc_calls= apc_calls->next) == qe) |
| 79 | { |
| 80 | apc_calls= NULL; |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | qe->prev->next= qe->next; |
| 85 | qe->next->prev= qe->prev; |
| 86 | } |
| 87 | |
| 88 | #ifdef HAVE_PSI_INTERFACE |
| 89 | |
| 90 | /* One key for all conds */ |
| 91 | PSI_cond_key key_show_explain_request_COND; |
| 92 | |
| 93 | static PSI_cond_info show_explain_psi_conds[]= |
| 94 | { |
| 95 | { &key_show_explain_request_COND, "show_explain" , 0 /* not using PSI_FLAG_GLOBAL*/ } |
| 96 | }; |
| 97 | |
| 98 | void init_show_explain_psi_keys(void) |
| 99 | { |
| 100 | if (PSI_server == NULL) |
| 101 | return; |
| 102 | |
| 103 | PSI_server->register_cond("sql" , show_explain_psi_conds, |
| 104 | array_elements(show_explain_psi_conds)); |
| 105 | } |
| 106 | #endif |
| 107 | |
| 108 | |
| 109 | /* |
| 110 | Make an APC (Async Procedure Call) to another thread. |
| 111 | |
| 112 | @detail |
| 113 | Make an APC call: schedule it for execution and wait until the target |
| 114 | thread has executed it. |
| 115 | |
| 116 | - The caller is responsible for making sure he's not posting request |
| 117 | to the thread he's calling this function from. |
| 118 | |
| 119 | - The caller must have locked target_mutex. The function will release it. |
| 120 | |
| 121 | @retval FALSE - Ok, the call has been made |
| 122 | @retval TRUE - Call wasnt made (either the target is in disabled state or |
| 123 | timeout occurred) |
| 124 | */ |
| 125 | |
| 126 | bool Apc_target::make_apc_call(THD *caller_thd, Apc_call *call, |
| 127 | int timeout_sec, bool *timed_out) |
| 128 | { |
| 129 | bool res= TRUE; |
| 130 | *timed_out= FALSE; |
| 131 | |
| 132 | if (enabled) |
| 133 | { |
| 134 | /* Create and post the request */ |
| 135 | Call_request apc_request; |
| 136 | apc_request.call= call; |
| 137 | apc_request.processed= FALSE; |
| 138 | mysql_cond_init(key_show_explain_request_COND, &apc_request.COND_request, |
| 139 | NULL); |
| 140 | enqueue_request(&apc_request); |
| 141 | apc_request.what="enqueued by make_apc_call" ; |
| 142 | |
| 143 | struct timespec abstime; |
| 144 | const int timeout= timeout_sec; |
| 145 | set_timespec(abstime, timeout); |
| 146 | |
| 147 | int wait_res= 0; |
| 148 | PSI_stage_info old_stage; |
| 149 | caller_thd->ENTER_COND(&apc_request.COND_request, LOCK_thd_kill_ptr, |
| 150 | &stage_show_explain, &old_stage); |
| 151 | /* todo: how about processing other errors here? */ |
| 152 | while (!apc_request.processed && (wait_res != ETIMEDOUT)) |
| 153 | { |
| 154 | /* We own LOCK_thd_kill_ptr */ |
| 155 | wait_res= mysql_cond_timedwait(&apc_request.COND_request, |
| 156 | LOCK_thd_kill_ptr, &abstime); |
| 157 | // &apc_request.LOCK_request, &abstime); |
| 158 | if (caller_thd->killed) |
| 159 | break; |
| 160 | } |
| 161 | |
| 162 | if (!apc_request.processed) |
| 163 | { |
| 164 | /* |
| 165 | The wait has timed out, or this thread was KILLed. |
| 166 | Remove the request from the queue (ok to do because we own |
| 167 | LOCK_thd_kill_ptr) |
| 168 | */ |
| 169 | apc_request.processed= TRUE; |
| 170 | dequeue_request(&apc_request); |
| 171 | *timed_out= TRUE; |
| 172 | res= TRUE; |
| 173 | } |
| 174 | else |
| 175 | { |
| 176 | /* Request was successfully executed and dequeued by the target thread */ |
| 177 | res= FALSE; |
| 178 | } |
| 179 | /* |
| 180 | exit_cond() will call mysql_mutex_unlock(LOCK_thd_kill_ptr) for us: |
| 181 | */ |
| 182 | caller_thd->EXIT_COND(&old_stage); |
| 183 | |
| 184 | /* Destroy all APC request data */ |
| 185 | mysql_cond_destroy(&apc_request.COND_request); |
| 186 | } |
| 187 | else |
| 188 | { |
| 189 | mysql_mutex_unlock(LOCK_thd_kill_ptr); |
| 190 | } |
| 191 | return res; |
| 192 | } |
| 193 | |
| 194 | |
| 195 | /* |
| 196 | Process all APC requests. |
| 197 | This should be called periodically by the APC target thread. |
| 198 | */ |
| 199 | |
| 200 | void Apc_target::process_apc_requests() |
| 201 | { |
| 202 | while (1) |
| 203 | { |
| 204 | Call_request *request; |
| 205 | |
| 206 | mysql_mutex_lock(LOCK_thd_kill_ptr); |
| 207 | if (!(request= get_first_in_queue())) |
| 208 | { |
| 209 | /* No requests in the queue */ |
| 210 | mysql_mutex_unlock(LOCK_thd_kill_ptr); |
| 211 | break; |
| 212 | } |
| 213 | |
| 214 | /* |
| 215 | Remove the request from the queue (we're holding queue lock so we can be |
| 216 | sure that request owner won't try to remove it) |
| 217 | */ |
| 218 | request->what="dequeued by process_apc_requests" ; |
| 219 | dequeue_request(request); |
| 220 | request->processed= TRUE; |
| 221 | |
| 222 | request->call->call_in_target_thread(); |
| 223 | request->what="func called by process_apc_requests" ; |
| 224 | |
| 225 | #ifndef DBUG_OFF |
| 226 | n_calls_processed++; |
| 227 | #endif |
| 228 | mysql_cond_signal(&request->COND_request); |
| 229 | mysql_mutex_unlock(LOCK_thd_kill_ptr); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | |