1 /* Copyright (c) 2000, 2021, 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, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 Without limiting anything contained in the foregoing, this file,
15 which is part of C Driver for MySQL (Connector/C), is also subject to the
16 Universal FOSS Exception, version 1.0, a copy of which can be found at
17 http://oss.oracle.com/licenses/universal-foss-exception.
18
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License, version 2.0, for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with this program; if not, write to the Free Software
26 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
27
28 /*
29 Read and write locks for Posix threads. All tread must acquire
30 all locks it needs through thr_multi_lock() to avoid dead-locks.
31 A lock consists of a master lock (THR_LOCK), and lock instances
32 (THR_LOCK_DATA).
33 Any thread can have any number of lock instances (read and write:s) on
34 any lock. All lock instances must be freed.
35 Locks are prioritized according to:
36
37 The current lock types are:
38
39 TL_READ # Low priority read
40 TL_READ_WITH_SHARED_LOCKS
41 TL_READ_HIGH_PRIORITY # High priority read
42 TL_READ_NO_INSERT # Read without concurrent inserts
43 TL_WRITE_ALLOW_WRITE # Write lock that allows other writers
44 TL_WRITE_CONCURRENT_INSERT
45 # Insert that can be mixed when selects
46 # Allows lower locks to take over
47 TL_WRITE_LOW_PRIORITY # Low priority write
48 TL_WRITE # High priority write
49 TL_WRITE_ONLY # High priority write
50 # Abort all new lock request with an error
51
52 Locks are prioritized according to:
53
54 WRITE_ALLOW_WRITE, WRITE_CONCURRENT_INSERT, WRITE_LOW_PRIORITY, READ,
55 WRITE, READ_HIGH_PRIORITY and WRITE_ONLY
56
57 Locks in the same privilege level are scheduled in first-in-first-out order.
58
59 To allow concurrent read/writes locks, with 'WRITE_CONCURRENT_INSERT' one
60 should put a pointer to the following functions in the lock structure:
61 (If the pointer is zero (default), the function is not called)
62
63 check_status:
64 Before giving a lock of type TL_WRITE_CONCURRENT_INSERT,
65 we check if this function exists and returns 0.
66 If not, then the lock is upgraded to TL_WRITE_LOCK
67 In MyISAM this is a simple check if the insert can be done
68 at the end of the datafile.
69 update_status:
70 Before a write lock is released, this function is called.
71 In MyISAM this functions updates the count and length of the datafile
72 get_status:
73 When one gets a lock this functions is called.
74 In MyISAM this stores the number of rows and size of the datafile
75 for concurrent reads.
76
77 The lock algorithm allows one to have one TL_WRITE_CONCURRENT_INSERT
78 lock at the same time as multiple read locks.
79
80 */
81
82 #include "mysys_priv.h"
83 #include "my_sys.h"
84 #include "thr_lock.h"
85 #include "mysql/psi/mysql_table.h"
86 #include <m_string.h>
87 #include <errno.h>
88
89 ulong locks_immediate = 0L, locks_waited = 0L;
90 enum thr_lock_type thr_upgraded_concurrent_insert_lock = TL_WRITE;
91
92 /* The following constants are only for debug output */
93 #define MAX_THREADS 100
94 #define MAX_LOCKS 100
95
96
97 LIST *thr_lock_thread_list; /* List of threads in use */
98 ulong max_write_lock_count= ~(ulong) 0L;
99
100 static void (*before_lock_wait)(void)= 0;
101 static void (*after_lock_wait)(void)= 0;
102
thr_set_lock_wait_callback(void (* before_wait)(void),void (* after_wait)(void))103 void thr_set_lock_wait_callback(void (*before_wait)(void),
104 void (*after_wait)(void))
105 {
106 before_lock_wait= before_wait;
107 after_lock_wait= after_wait;
108 }
109
110
111 static inline my_bool
thr_lock_owner_equal(THR_LOCK_INFO * rhs,THR_LOCK_INFO * lhs)112 thr_lock_owner_equal(THR_LOCK_INFO *rhs, THR_LOCK_INFO *lhs)
113 {
114 return rhs == lhs;
115 }
116
117
118 #ifdef EXTRA_DEBUG
119 #define MAX_FOUND_ERRORS 10 /* Report 10 first errors */
120 static uint found_errors=0;
121
check_lock(struct st_lock_list * list,const char * lock_type,const char * where,my_bool same_owner,my_bool no_cond)122 static int check_lock(struct st_lock_list *list, const char* lock_type,
123 const char *where, my_bool same_owner, my_bool no_cond)
124 {
125 THR_LOCK_DATA *data,**prev;
126 uint count=0;
127 THR_LOCK_INFO *first_owner= NULL;
128
129 prev= &list->data;
130 if (list->data)
131 {
132 enum thr_lock_type last_lock_type=list->data->type;
133
134 if (same_owner && list->data)
135 first_owner= list->data->owner;
136 for (data=list->data; data && count++ < MAX_LOCKS ; data=data->next)
137 {
138 if (data->type != last_lock_type)
139 last_lock_type=TL_IGNORE;
140 if (data->prev != prev)
141 {
142 my_message_stderr(0, "prev link %d didn't point at "
143 "previous lock at %s: %s", count, lock_type, where);
144 return 1;
145 }
146 if (same_owner &&
147 !thr_lock_owner_equal(data->owner, first_owner) &&
148 last_lock_type != TL_WRITE_ALLOW_WRITE)
149 {
150 my_message_stderr(0, "Found locks from different threads "
151 "in %s: %s", lock_type, where);
152 return 1;
153 }
154 if (no_cond && data->cond)
155 {
156 my_message_stderr(0, "Found active lock with not reset "
157 "cond %s: %s", lock_type, where);
158 return 1;
159 }
160 prev= &data->next;
161 }
162 if (data)
163 {
164 my_message_stderr(0, "found too many locks at %s: %s",
165 lock_type, where);
166 return 1;
167 }
168 }
169 if (prev != list->last)
170 {
171 my_message_stderr(0, "last didn't point at last lock at %s: %s",
172 lock_type, where);
173 return 1;
174 }
175 return 0;
176 }
177
178
check_locks(THR_LOCK * lock,const char * where,my_bool allow_no_locks)179 static void check_locks(THR_LOCK *lock, const char *where,
180 my_bool allow_no_locks)
181 {
182 uint old_found_errors=found_errors;
183 DBUG_ENTER("check_locks");
184
185 if (found_errors < MAX_FOUND_ERRORS)
186 {
187 if (check_lock(&lock->write,"write",where,1,1) |
188 check_lock(&lock->write_wait,"write_wait",where,0,0) |
189 check_lock(&lock->read,"read",where,0,1) |
190 check_lock(&lock->read_wait,"read_wait",where,0,0))
191 found_errors++;
192
193 if (found_errors < MAX_FOUND_ERRORS)
194 {
195 uint count=0;
196 THR_LOCK_DATA *data;
197 for (data=lock->read.data ; data ; data=data->next)
198 {
199 if ((int) data->type == (int) TL_READ_NO_INSERT)
200 count++;
201 /* Protect against infinite loop. */
202 assert(count <= lock->read_no_write_count);
203 }
204 if (count != lock->read_no_write_count)
205 {
206 found_errors++;
207 my_message_stderr(0, "at '%s': Locks read_no_write_count "
208 "was %u when it should have been %u",
209 where, lock->read_no_write_count,count);
210 }
211
212 if (!lock->write.data)
213 {
214 if (!allow_no_locks && !lock->read.data &&
215 (lock->write_wait.data || lock->read_wait.data))
216 {
217 found_errors++;
218 my_message_stderr(0, "at '%s': No locks in use but locks "
219 "are in wait queue", where);
220 }
221 if (!lock->write_wait.data)
222 {
223 if (!allow_no_locks && lock->read_wait.data)
224 {
225 found_errors++;
226 my_message_stderr(0, "at '%s': No write locks and "
227 "waiting read locks", where);
228 }
229 }
230 else
231 {
232 if (!allow_no_locks &&
233 (((lock->write_wait.data->type == TL_WRITE_CONCURRENT_INSERT ||
234 lock->write_wait.data->type == TL_WRITE_ALLOW_WRITE) &&
235 !lock->read_no_write_count)))
236 {
237 found_errors++;
238 my_message_stderr(0, "at '%s': Write lock %d waiting "
239 "while no exclusive read locks",
240 where, (int) lock->write_wait.data->type);
241 }
242 }
243 }
244 else
245 { /* Have write lock */
246 if (lock->write_wait.data)
247 {
248 if (!allow_no_locks &&
249 lock->write.data->type == TL_WRITE_ALLOW_WRITE &&
250 lock->write_wait.data->type == TL_WRITE_ALLOW_WRITE)
251 {
252 found_errors++;
253 my_message_stderr(0, "at '%s': Found WRITE_ALLOW_WRITE "
254 "lock waiting for WRITE_ALLOW_WRITE lock", where);
255 }
256 }
257 if (lock->read.data)
258 {
259 if (!thr_lock_owner_equal(lock->write.data->owner,
260 lock->read.data->owner) &&
261 ((lock->write.data->type > TL_WRITE_CONCURRENT_INSERT &&
262 lock->write.data->type != TL_WRITE_ONLY) ||
263 ((lock->write.data->type == TL_WRITE_CONCURRENT_INSERT ||
264 lock->write.data->type == TL_WRITE_ALLOW_WRITE) &&
265 lock->read_no_write_count)))
266 {
267 found_errors++;
268 my_message_stderr(0, "at '%s': Found lock of type %d "
269 "that is write and read locked",
270 where, lock->write.data->type);
271 DBUG_PRINT("warning",("At '%s': Found lock of type %d that is write and read locked\n",
272 where, lock->write.data->type));
273
274 }
275 }
276 if (lock->read_wait.data)
277 {
278 if (!allow_no_locks && lock->write.data->type <= TL_WRITE_CONCURRENT_INSERT &&
279 lock->read_wait.data->type <= TL_READ_HIGH_PRIORITY)
280 {
281 found_errors++;
282 my_message_stderr(0, "at '%s': Found read lock of "
283 "type %d waiting for write lock of type %d",
284 where, (int) lock->read_wait.data->type,
285 (int) lock->write.data->type);
286 }
287 }
288 }
289 }
290 if (found_errors != old_found_errors)
291 {
292 DBUG_PRINT("error",("Found wrong lock"));
293 }
294 }
295 DBUG_VOID_RETURN;
296 }
297
298 #else /* EXTRA_DEBUG */
299 #define check_locks(A,B,C)
300 #endif
301
302
303 /* Initialize a lock */
304
thr_lock_init(THR_LOCK * lock)305 void thr_lock_init(THR_LOCK *lock)
306 {
307 DBUG_ENTER("thr_lock_init");
308 memset(lock, 0, sizeof(*lock));
309
310 mysql_mutex_init(key_THR_LOCK_mutex, &lock->mutex, MY_MUTEX_INIT_FAST);
311 lock->read.last= &lock->read.data;
312 lock->read_wait.last= &lock->read_wait.data;
313 lock->write_wait.last= &lock->write_wait.data;
314 lock->write.last= &lock->write.data;
315
316 mysql_mutex_lock(&THR_LOCK_lock); /* Add to locks in use */
317 lock->list.data=(void*) lock;
318 thr_lock_thread_list=list_add(thr_lock_thread_list,&lock->list);
319 mysql_mutex_unlock(&THR_LOCK_lock);
320 DBUG_VOID_RETURN;
321 }
322
323
thr_lock_delete(THR_LOCK * lock)324 void thr_lock_delete(THR_LOCK *lock)
325 {
326 DBUG_ENTER("thr_lock_delete");
327 mysql_mutex_lock(&THR_LOCK_lock);
328 thr_lock_thread_list=list_delete(thr_lock_thread_list,&lock->list);
329 mysql_mutex_unlock(&THR_LOCK_lock);
330 mysql_mutex_destroy(&lock->mutex);
331 DBUG_VOID_RETURN;
332 }
333
334
thr_lock_info_init(THR_LOCK_INFO * info,my_thread_id thread_id,mysql_cond_t * suspend)335 void thr_lock_info_init(THR_LOCK_INFO *info, my_thread_id thread_id,
336 mysql_cond_t *suspend)
337 {
338 info->thread_id= thread_id;
339 info->suspend= suspend;
340 }
341
342 /* Initialize a lock instance */
343
thr_lock_data_init(THR_LOCK * lock,THR_LOCK_DATA * data,void * param)344 void thr_lock_data_init(THR_LOCK *lock,THR_LOCK_DATA *data, void *param)
345 {
346 data->lock=lock;
347 data->type=TL_UNLOCK;
348 data->owner= 0; /* no owner yet */
349 data->status_param=param;
350 data->cond=0;
351 }
352
353
354 static inline my_bool
has_old_lock(THR_LOCK_DATA * data,THR_LOCK_INFO * owner)355 has_old_lock(THR_LOCK_DATA *data, THR_LOCK_INFO *owner)
356 {
357 for ( ; data ; data=data->next)
358 {
359 if (thr_lock_owner_equal(data->owner, owner))
360 return 1; /* Already locked by thread */
361 }
362 return 0;
363 }
364
365 static void wake_up_waiters(THR_LOCK *lock);
366
367
368 static enum enum_thr_lock_result
wait_for_lock(struct st_lock_list * wait,THR_LOCK_DATA * data,THR_LOCK_INFO * owner,my_bool in_wait_list,ulong lock_wait_timeout)369 wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data,
370 THR_LOCK_INFO *owner,
371 my_bool in_wait_list, ulong lock_wait_timeout)
372 {
373 struct timespec wait_timeout;
374 enum enum_thr_lock_result result= THR_LOCK_ABORTED;
375 PSI_stage_info old_stage;
376 DBUG_ENTER("wait_for_lock");
377
378 /*
379 One can use this to signal when a thread is going to wait for a lock.
380 See debug_sync.cc.
381
382 Beware of waiting for a signal here. The lock has aquired its mutex.
383 While waiting on a signal here, the locking thread could not aquire
384 the mutex to release the lock. One could lock up the table
385 completely.
386
387 In detail it works so: When thr_lock() tries to acquire a table
388 lock, it locks the lock->mutex, checks if it can have the lock, and
389 if not, it calls wait_for_lock(). Here it unlocks the table lock
390 while waiting on a condition. The sync point is located before this
391 wait for condition. If we have a waiting action here, we hold the
392 the table locks mutex all the time. Any attempt to look at the table
393 lock by another thread blocks it immediately on lock->mutex. This
394 can easily become an unexpected and unobvious blockage. So be
395 warned: Do not request a WAIT_FOR action for the 'wait_for_lock'
396 sync point unless you really know what you do.
397 */
398 DEBUG_SYNC_C("wait_for_lock");
399
400 if (!in_wait_list)
401 {
402 (*wait->last)=data; /* Wait for lock */
403 data->prev= wait->last;
404 wait->last= &data->next;
405 }
406
407 locks_waited++;
408
409 /* Set up control struct to allow others to abort locks */
410 data->cond= owner->suspend;
411
412 enter_cond_hook(NULL, data->cond, &data->lock->mutex,
413 &stage_waiting_for_table_level_lock, &old_stage,
414 __func__, __FILE__, __LINE__);
415
416 /*
417 Since before_lock_wait potentially can create more threads to
418 scheduler work for, we don't want to call the before_lock_wait
419 callback unless it will really start to wait.
420
421 For similar reasons, we do not want to call before_lock_wait and
422 after_lock_wait for each lap around the loop, so we restrict
423 ourselves to call it before_lock_wait once before starting to wait
424 and once after the thread has exited the wait loop.
425 */
426 if ((!is_killed_hook(NULL) || in_wait_list) && before_lock_wait)
427 (*before_lock_wait)();
428
429 set_timespec(&wait_timeout, lock_wait_timeout);
430 while (!is_killed_hook(NULL) || in_wait_list)
431 {
432 int rc= mysql_cond_timedwait(data->cond, &data->lock->mutex, &wait_timeout);
433 /*
434 We must break the wait if one of the following occurs:
435 - the connection has been aborted (!is_killed_hook()),
436 - the lock has been granted (data->cond is set to NULL by the granter),
437 or the waiting has been aborted (additionally data->type is set to
438 TL_UNLOCK).
439 - the wait has timed out (rc == ETIMEDOUT)
440 Order of checks below is important to not report about timeout
441 if the predicate is true.
442 */
443 if (data->cond == 0)
444 {
445 DBUG_PRINT("thr_lock", ("lock granted/aborted"));
446 break;
447 }
448 if (rc == ETIMEDOUT || rc == ETIME)
449 {
450 /* purecov: begin inspected */
451 DBUG_PRINT("thr_lock", ("lock timed out"));
452 result= THR_LOCK_WAIT_TIMEOUT;
453 break;
454 /* purecov: end */
455 }
456 }
457
458 /*
459 We call the after_lock_wait callback once the wait loop has
460 finished.
461 */
462 if (after_lock_wait)
463 (*after_lock_wait)();
464
465 if (data->cond || data->type == TL_UNLOCK)
466 {
467 if (data->cond) /* aborted or timed out */
468 {
469 if (((*data->prev)=data->next)) /* remove from wait-list */
470 data->next->prev= data->prev;
471 else
472 wait->last=data->prev;
473 data->type= TL_UNLOCK; /* No lock */
474 check_locks(data->lock, "killed or timed out wait_for_lock", 1);
475 wake_up_waiters(data->lock);
476 }
477 else
478 {
479 DBUG_PRINT("thr_lock", ("lock aborted"));
480 check_locks(data->lock, "aborted wait_for_lock", 0);
481 }
482 }
483 else
484 {
485 result= THR_LOCK_SUCCESS;
486 if (data->lock->get_status)
487 (*data->lock->get_status)(data->status_param, 0);
488 check_locks(data->lock,"got wait_for_lock",0);
489 }
490 mysql_mutex_unlock(&data->lock->mutex);
491
492 exit_cond_hook(NULL, &old_stage, __func__, __FILE__, __LINE__);
493
494 DBUG_RETURN(result);
495 }
496
497
498 enum enum_thr_lock_result
thr_lock(THR_LOCK_DATA * data,THR_LOCK_INFO * owner,enum thr_lock_type lock_type,ulong lock_wait_timeout)499 thr_lock(THR_LOCK_DATA *data, THR_LOCK_INFO *owner,
500 enum thr_lock_type lock_type, ulong lock_wait_timeout)
501 {
502 THR_LOCK *lock=data->lock;
503 enum enum_thr_lock_result result= THR_LOCK_SUCCESS;
504 struct st_lock_list *wait_queue;
505 MYSQL_TABLE_WAIT_VARIABLES(locker, state) /* no ';' */
506 DBUG_ENTER("thr_lock");
507
508 data->next=0;
509 data->cond=0; /* safety */
510 data->type=lock_type;
511 data->owner= owner; /* Must be reset ! */
512
513 MYSQL_START_TABLE_LOCK_WAIT(locker, &state, data->m_psi,
514 PSI_TABLE_LOCK, lock_type);
515
516 mysql_mutex_lock(&lock->mutex);
517 DBUG_PRINT("lock",("data: 0x%lx thread: 0x%x lock: 0x%lx type: %d",
518 (long) data, data->owner->thread_id,
519 (long) lock, (int) lock_type));
520 check_locks(lock,(uint) lock_type <= (uint) TL_READ_NO_INSERT ?
521 "enter read_lock" : "enter write_lock",0);
522 if ((int) lock_type <= (int) TL_READ_NO_INSERT)
523 {
524 /* Request for READ lock */
525 if (lock->write.data)
526 {
527 /*
528 We can allow a read lock even if there is already a
529 write lock on the table if they are owned by the same
530 thread or if they satisfy the following lock
531 compatibility matrix:
532
533 Request
534 /-------
535 H|++++ WRITE_ALLOW_WRITE
536 e|+++- WRITE_CONCURRENT_INSERT
537 l ||||
538 d ||||
539 |||\= READ_NO_INSERT
540 ||\ = READ_HIGH_PRIORITY
541 |\ = READ_WITH_SHARED_LOCKS
542 \ = READ
543
544 + = Request can be satisified.
545 - = Request cannot be satisified.
546
547 READ_NO_INSERT and WRITE_ALLOW_WRITE should in principle
548 be incompatible. Before this could have caused starvation of
549 LOCK TABLE READ in InnoDB under high write load. However
550 now READ_NO_INSERT is only used for LOCK TABLES READ and this
551 statement is handled by the MDL subsystem.
552 See Bug#42147 for more information.
553 */
554
555 DBUG_PRINT("lock",("write locked 1 by thread: 0x%x",
556 lock->write.data->owner->thread_id));
557 if (thr_lock_owner_equal(data->owner, lock->write.data->owner) ||
558 (lock->write.data->type < TL_WRITE_CONCURRENT_INSERT) ||
559 ((lock->write.data->type == TL_WRITE_CONCURRENT_INSERT) &&
560 ((int) lock_type <= (int) TL_READ_HIGH_PRIORITY)))
561 { /* Already got a write lock */
562 (*lock->read.last)=data; /* Add to running FIFO */
563 data->prev=lock->read.last;
564 lock->read.last= &data->next;
565 if (lock_type == TL_READ_NO_INSERT)
566 lock->read_no_write_count++;
567 check_locks(lock,"read lock with old write lock",0);
568 if (lock->get_status)
569 (*lock->get_status)(data->status_param, 0);
570 locks_immediate++;
571 goto end;
572 }
573 if (lock->write.data->type == TL_WRITE_ONLY)
574 {
575 /* We are not allowed to get a READ lock in this case */
576 data->type=TL_UNLOCK;
577 result= THR_LOCK_ABORTED; /* Can't wait for this one */
578 goto end;
579 }
580 }
581 else if (!lock->write_wait.data ||
582 lock->write_wait.data->type <= TL_WRITE_LOW_PRIORITY ||
583 lock_type == TL_READ_HIGH_PRIORITY ||
584 has_old_lock(lock->read.data, data->owner)) /* Has old read lock */
585 { /* No important write-locks */
586 (*lock->read.last)=data; /* Add to running FIFO */
587 data->prev=lock->read.last;
588 lock->read.last= &data->next;
589 if (lock->get_status)
590 (*lock->get_status)(data->status_param, 0);
591 if (lock_type == TL_READ_NO_INSERT)
592 lock->read_no_write_count++;
593 check_locks(lock,"read lock with no write locks",0);
594 locks_immediate++;
595 goto end;
596 }
597 /*
598 We're here if there is an active write lock or no write
599 lock but a high priority write waiting in the write_wait queue.
600 In the latter case we should yield the lock to the writer.
601 */
602 wait_queue= &lock->read_wait;
603 }
604 else /* Request for WRITE lock */
605 {
606 if (lock_type == TL_WRITE_CONCURRENT_INSERT && ! lock->check_status)
607 data->type=lock_type= thr_upgraded_concurrent_insert_lock;
608
609 if (lock->write.data) /* If there is a write lock */
610 {
611 if (lock->write.data->type == TL_WRITE_ONLY)
612 {
613 /* purecov: begin tested */
614 /* Allow lock owner to bypass TL_WRITE_ONLY. */
615 if (!thr_lock_owner_equal(data->owner, lock->write.data->owner))
616 {
617 /* We are not allowed to get a lock in this case */
618 data->type=TL_UNLOCK;
619 result= THR_LOCK_ABORTED; /* Can't wait for this one */
620 goto end;
621 }
622 /* purecov: end */
623 }
624
625 /*
626 The idea is to allow us to get a lock at once if we already have
627 a write lock or if there is no pending write locks and if all
628 write locks are of TL_WRITE_ALLOW_WRITE type.
629
630 Note that, since lock requests for the same table are sorted in
631 such way that requests with higher thr_lock_type value come first
632 (with one exception (*)), lock being requested usually has
633 equal or "weaker" type than one which thread might have already
634 acquired.
635 *) The only exception to this rule is case when type of old lock
636 is TL_WRITE_LOW_PRIORITY and type of new lock is changed inside
637 of thr_lock() from TL_WRITE_CONCURRENT_INSERT to TL_WRITE since
638 engine turns out to be not supporting concurrent inserts.
639 Note that since TL_WRITE has the same compatibility rules as
640 TL_WRITE_LOW_PRIORITY (their only difference is priority),
641 it is OK to grant new lock without additional checks in such
642 situation.
643
644 Therefore it is OK to allow acquiring write lock on the table if
645 this thread already holds some write lock on it.
646
647 (INSERT INTO t1 VALUES (f1()), where f1() is stored function which
648 tries to update t1, is an example of statement which requests two
649 different types of write lock on the same table).
650 */
651 assert(! has_old_lock(lock->write.data, data->owner) ||
652 ((lock_type <= lock->write.data->type ||
653 (lock_type == TL_WRITE &&
654 lock->write.data->type == TL_WRITE_LOW_PRIORITY))));
655
656 if ((lock_type == TL_WRITE_ALLOW_WRITE &&
657 ! lock->write_wait.data &&
658 lock->write.data->type == TL_WRITE_ALLOW_WRITE) ||
659 has_old_lock(lock->write.data, data->owner))
660 {
661 /*
662 We have already got a write lock or all locks are
663 TL_WRITE_ALLOW_WRITE
664 */
665 DBUG_PRINT("info", ("write_wait.data: 0x%lx old_type: %d",
666 (ulong) lock->write_wait.data,
667 lock->write.data->type));
668
669 (*lock->write.last)=data; /* Add to running fifo */
670 data->prev=lock->write.last;
671 lock->write.last= &data->next;
672 check_locks(lock,"second write lock",0);
673 if (data->lock->get_status)
674 (*data->lock->get_status)(data->status_param, 0);
675 locks_immediate++;
676 goto end;
677 }
678 DBUG_PRINT("lock",("write locked 2 by thread: 0x%x",
679 lock->write.data->owner->thread_id));
680 }
681 else
682 {
683 DBUG_PRINT("info", ("write_wait.data: 0x%lx",
684 (ulong) lock->write_wait.data));
685 if (!lock->write_wait.data)
686 { /* no scheduled write locks */
687 my_bool concurrent_insert= 0;
688 if (lock_type == TL_WRITE_CONCURRENT_INSERT)
689 {
690 concurrent_insert= 1;
691 if ((*lock->check_status)(data->status_param))
692 {
693 concurrent_insert= 0;
694 data->type=lock_type= thr_upgraded_concurrent_insert_lock;
695 }
696 }
697
698 if (!lock->read.data ||
699 (lock_type <= TL_WRITE_CONCURRENT_INSERT &&
700 ((lock_type != TL_WRITE_CONCURRENT_INSERT &&
701 lock_type != TL_WRITE_ALLOW_WRITE) ||
702 !lock->read_no_write_count)))
703 {
704 (*lock->write.last)=data; /* Add as current write lock */
705 data->prev=lock->write.last;
706 lock->write.last= &data->next;
707 if (data->lock->get_status)
708 (*data->lock->get_status)(data->status_param, concurrent_insert);
709 check_locks(lock,"only write lock",0);
710 locks_immediate++;
711 goto end;
712 }
713 }
714 DBUG_PRINT("lock",("write locked 3 by thread: 0x%x type: %d",
715 lock->read.data->owner->thread_id, data->type));
716 }
717 wait_queue= &lock->write_wait;
718 }
719 /* Can't get lock yet; Wait for it */
720 result= wait_for_lock(wait_queue, data, owner, 0,
721 lock_wait_timeout);
722 MYSQL_END_TABLE_LOCK_WAIT(locker);
723 DBUG_RETURN(result);
724 end:
725 mysql_mutex_unlock(&lock->mutex);
726 MYSQL_END_TABLE_LOCK_WAIT(locker);
727 DBUG_RETURN(result);
728 }
729
730
free_all_read_locks(THR_LOCK * lock,my_bool using_concurrent_insert)731 static inline void free_all_read_locks(THR_LOCK *lock,
732 my_bool using_concurrent_insert)
733 {
734 THR_LOCK_DATA *data=lock->read_wait.data;
735
736 check_locks(lock,"before freeing read locks",1);
737
738 /* move all locks from read_wait list to read list */
739 (*lock->read.last)=data;
740 data->prev=lock->read.last;
741 lock->read.last=lock->read_wait.last;
742
743 /* Clear read_wait list */
744 lock->read_wait.last= &lock->read_wait.data;
745
746 do
747 {
748 mysql_cond_t *cond= data->cond;
749 if ((int) data->type == (int) TL_READ_NO_INSERT)
750 {
751 if (using_concurrent_insert)
752 {
753 /*
754 We can't free this lock;
755 Link lock away from read chain back into read_wait chain
756 */
757 if (((*data->prev)=data->next))
758 data->next->prev=data->prev;
759 else
760 lock->read.last=data->prev;
761 *lock->read_wait.last= data;
762 data->prev= lock->read_wait.last;
763 lock->read_wait.last= &data->next;
764 continue;
765 }
766 lock->read_no_write_count++;
767 }
768 /* purecov: begin inspected */
769 DBUG_PRINT("lock",("giving read lock to thread: 0x%x",
770 data->owner->thread_id));
771 /* purecov: end */
772 data->cond=0; /* Mark thread free */
773 mysql_cond_signal(cond);
774 } while ((data=data->next));
775 *lock->read_wait.last=0;
776 if (!lock->read_wait.data)
777 lock->write_lock_count=0;
778 check_locks(lock,"after giving read locks",0);
779 }
780
781 /* Unlock lock and free next thread on same lock */
782
thr_unlock(THR_LOCK_DATA * data)783 void thr_unlock(THR_LOCK_DATA *data)
784 {
785 THR_LOCK *lock=data->lock;
786 enum thr_lock_type lock_type=data->type;
787 DBUG_ENTER("thr_unlock");
788 DBUG_PRINT("lock",("data: 0x%lx thread: 0x%x lock: 0x%lx",
789 (long) data, data->owner->thread_id, (long) lock));
790 mysql_mutex_lock(&lock->mutex);
791 check_locks(lock,"start of release lock",0);
792
793 if (((*data->prev)=data->next)) /* remove from lock-list */
794 data->next->prev= data->prev;
795 else if (lock_type <= TL_READ_NO_INSERT)
796 lock->read.last=data->prev;
797 else
798 lock->write.last=data->prev;
799 if (lock_type >= TL_WRITE_CONCURRENT_INSERT)
800 {
801 if (lock->update_status)
802 (*lock->update_status)(data->status_param);
803 }
804 else
805 {
806 if (lock->restore_status)
807 (*lock->restore_status)(data->status_param);
808 }
809 if (lock_type == TL_READ_NO_INSERT)
810 lock->read_no_write_count--;
811 data->type=TL_UNLOCK; /* Mark unlocked */
812 MYSQL_UNLOCK_TABLE(data->m_psi);
813 check_locks(lock,"after releasing lock",1);
814 wake_up_waiters(lock);
815 mysql_mutex_unlock(&lock->mutex);
816 DBUG_VOID_RETURN;
817 }
818
819
820 /**
821 @brief Wake up all threads which pending requests for the lock
822 can be satisfied.
823
824 @param lock Lock for which threads should be woken up
825
826 */
827
wake_up_waiters(THR_LOCK * lock)828 static void wake_up_waiters(THR_LOCK *lock)
829 {
830 THR_LOCK_DATA *data;
831 enum thr_lock_type lock_type;
832
833 DBUG_ENTER("wake_up_waiters");
834
835 if (!lock->write.data) /* If no active write locks */
836 {
837 data=lock->write_wait.data;
838 if (!lock->read.data) /* If no more locks in use */
839 {
840 /* Release write-locks with TL_WRITE or TL_WRITE_ONLY priority first */
841 if (data &&
842 (data->type != TL_WRITE_LOW_PRIORITY || !lock->read_wait.data ||
843 lock->read_wait.data->type < TL_READ_HIGH_PRIORITY))
844 {
845 if (lock->write_lock_count++ > max_write_lock_count)
846 {
847 /* Too many write locks in a row; Release all waiting read locks */
848 lock->write_lock_count=0;
849 if (lock->read_wait.data)
850 {
851 DBUG_PRINT("info",("Freeing all read_locks because of max_write_lock_count"));
852 free_all_read_locks(lock,0);
853 goto end;
854 }
855 }
856 for (;;)
857 {
858 if (((*data->prev)=data->next)) /* remove from wait-list */
859 data->next->prev= data->prev;
860 else
861 lock->write_wait.last=data->prev;
862 (*lock->write.last)=data; /* Put in execute list */
863 data->prev=lock->write.last;
864 data->next=0;
865 lock->write.last= &data->next;
866 if (data->type == TL_WRITE_CONCURRENT_INSERT &&
867 (*lock->check_status)(data->status_param))
868 data->type=TL_WRITE; /* Upgrade lock */
869 /* purecov: begin inspected */
870 DBUG_PRINT("lock",("giving write lock of type %d to thread: 0x%x",
871 data->type, data->owner->thread_id));
872 /* purecov: end */
873 {
874 mysql_cond_t *cond= data->cond;
875 data->cond=0; /* Mark thread free */
876 mysql_cond_signal(cond); /* Start waiting thread */
877 }
878 if (data->type != TL_WRITE_ALLOW_WRITE ||
879 !lock->write_wait.data ||
880 lock->write_wait.data->type != TL_WRITE_ALLOW_WRITE)
881 break;
882 data=lock->write_wait.data; /* Free this too */
883 }
884 if (data->type >= TL_WRITE_LOW_PRIORITY)
885 goto end;
886 /* Release possible read locks together with the write lock */
887 }
888 if (lock->read_wait.data)
889 free_all_read_locks(lock,
890 data &&
891 (data->type == TL_WRITE_CONCURRENT_INSERT ||
892 data->type == TL_WRITE_ALLOW_WRITE));
893 else
894 {
895 DBUG_PRINT("lock",("No waiting read locks to free"));
896 }
897 }
898 else if (data &&
899 (lock_type= data->type) <= TL_WRITE_CONCURRENT_INSERT &&
900 ((lock_type != TL_WRITE_CONCURRENT_INSERT &&
901 lock_type != TL_WRITE_ALLOW_WRITE) ||
902 !lock->read_no_write_count))
903 {
904 /*
905 For ALLOW_READ, WRITE_ALLOW_WRITE or CONCURRENT_INSERT locks
906 start WRITE locks together with the READ locks
907 */
908 if (lock_type == TL_WRITE_CONCURRENT_INSERT &&
909 (*lock->check_status)(data->status_param))
910 {
911 data->type=TL_WRITE; /* Upgrade lock */
912 if (lock->read_wait.data)
913 free_all_read_locks(lock,0);
914 goto end;
915 }
916 do {
917 mysql_cond_t *cond= data->cond;
918 if (((*data->prev)=data->next)) /* remove from wait-list */
919 data->next->prev= data->prev;
920 else
921 lock->write_wait.last=data->prev;
922 (*lock->write.last)=data; /* Put in execute list */
923 data->prev=lock->write.last;
924 lock->write.last= &data->next;
925 data->next=0; /* Only one write lock */
926 data->cond=0; /* Mark thread free */
927 mysql_cond_signal(cond); /* Start waiting thread */
928 } while (lock_type == TL_WRITE_ALLOW_WRITE &&
929 (data=lock->write_wait.data) &&
930 data->type == TL_WRITE_ALLOW_WRITE);
931 if (lock->read_wait.data)
932 free_all_read_locks(lock,
933 (lock_type == TL_WRITE_CONCURRENT_INSERT ||
934 lock_type == TL_WRITE_ALLOW_WRITE));
935 }
936 else if (!data && lock->read_wait.data)
937 free_all_read_locks(lock,0);
938 }
939 end:
940 check_locks(lock, "after waking up waiters", 0);
941 DBUG_VOID_RETURN;
942 }
943
944
945 /*
946 ** Get all locks in a specific order to avoid dead-locks
947 ** Sort acording to lock position and put write_locks before read_locks if
948 ** lock on same lock.
949 */
950
951
952 #define LOCK_CMP(A,B) ((uchar*) (A->lock) - (uint) ((A)->type) < (uchar*) (B->lock)- (uint) ((B)->type))
953
sort_locks(THR_LOCK_DATA ** data,uint count)954 static void sort_locks(THR_LOCK_DATA **data,uint count)
955 {
956 THR_LOCK_DATA **pos,**end,**prev,*tmp;
957
958 /* Sort locks with insertion sort (fast because almost always few locks) */
959
960 for (pos=data+1,end=data+count; pos < end ; pos++)
961 {
962 tmp= *pos;
963 if (LOCK_CMP(tmp,pos[-1]))
964 {
965 prev=pos;
966 do {
967 prev[0]=prev[-1];
968 } while (--prev != data && LOCK_CMP(tmp,prev[-1]));
969 prev[0]=tmp;
970 }
971 }
972 }
973
974
975 enum enum_thr_lock_result
thr_multi_lock(THR_LOCK_DATA ** data,uint count,THR_LOCK_INFO * owner,ulong lock_wait_timeout)976 thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_INFO *owner,
977 ulong lock_wait_timeout)
978 {
979 THR_LOCK_DATA **pos,**end;
980 DBUG_ENTER("thr_multi_lock");
981 DBUG_PRINT("lock",("data: 0x%lx count: %d", (long) data, count));
982 if (count > 1)
983 sort_locks(data,count);
984 /* lock everything */
985 for (pos=data,end=data+count; pos < end ; pos++)
986 {
987 enum enum_thr_lock_result result= thr_lock(*pos, owner, (*pos)->type,
988 lock_wait_timeout);
989 if (result != THR_LOCK_SUCCESS)
990 { /* Aborted */
991 thr_multi_unlock(data,(uint) (pos-data));
992 DBUG_RETURN(result);
993 }
994 DEBUG_SYNC_C("thr_multi_lock_after_thr_lock");
995 #ifdef MAIN
996 printf("Thread: T@%u Got lock: 0x%lx type: %d\n",
997 pos[0]->owner->thread_id, (long) pos[0]->lock, pos[0]->type);
998 fflush(stdout);
999 #endif
1000 }
1001 thr_lock_merge_status(data, count);
1002 DBUG_RETURN(THR_LOCK_SUCCESS);
1003 }
1004
1005
1006 /**
1007 Ensure that all locks for a given table have the same
1008 status_param.
1009
1010 This is a MyISAM and possibly Maria specific crutch. MyISAM
1011 engine stores data file length, record count and other table
1012 properties in status_param member of handler. When a table is
1013 locked, connection-local copy is made from a global copy
1014 (myisam_share) by mi_get_status(). When a table is unlocked,
1015 the changed status is transferred back to the global share by
1016 mi_update_status().
1017
1018 One thing MyISAM doesn't do is to ensure that when the same
1019 table is opened twice in a connection all instances share the
1020 same status_param. This is necessary, however: for one, to keep
1021 all instances of a connection "on the same page" with regard to
1022 the current state of the table. For other, unless this is done,
1023 myisam_share will always get updated from the last unlocked
1024 instance (in mi_update_status()), and when this instance was not
1025 the one that was used to update data, records may be lost.
1026
1027 For each table, this function looks up the last lock_data in the
1028 list of acquired locks, and makes sure that all other instances
1029 share status_param with it.
1030 */
1031
1032 void
thr_lock_merge_status(THR_LOCK_DATA ** data,uint count)1033 thr_lock_merge_status(THR_LOCK_DATA **data, uint count)
1034 {
1035 THR_LOCK_DATA **pos= data;
1036 THR_LOCK_DATA **end= data + count;
1037 if (count > 1)
1038 {
1039 THR_LOCK_DATA *last_lock= end[-1];
1040 pos=end-1;
1041 do
1042 {
1043 pos--;
1044 if (last_lock->lock == (*pos)->lock &&
1045 last_lock->lock->copy_status)
1046 {
1047 if (last_lock->type <= TL_READ_NO_INSERT)
1048 {
1049 THR_LOCK_DATA **read_lock;
1050 /*
1051 If we are locking the same table with read locks we must ensure
1052 that all tables share the status of the last write lock or
1053 the same read lock.
1054 */
1055 for (;
1056 (*pos)->type <= TL_READ_NO_INSERT &&
1057 pos != data &&
1058 pos[-1]->lock == (*pos)->lock ;
1059 pos--) ;
1060
1061 read_lock = pos+1;
1062 do
1063 {
1064 (last_lock->lock->copy_status)((*read_lock)->status_param,
1065 (*pos)->status_param);
1066 } while (*(read_lock++) != last_lock);
1067 last_lock= (*pos); /* Point at last write lock */
1068 }
1069 else
1070 (*last_lock->lock->copy_status)((*pos)->status_param,
1071 last_lock->status_param);
1072 }
1073 else
1074 last_lock=(*pos);
1075 } while (pos != data);
1076 }
1077 }
1078
1079 /* free all locks */
1080
thr_multi_unlock(THR_LOCK_DATA ** data,uint count)1081 void thr_multi_unlock(THR_LOCK_DATA **data,uint count)
1082 {
1083 THR_LOCK_DATA **pos,**end;
1084 DBUG_ENTER("thr_multi_unlock");
1085 DBUG_PRINT("lock",("data: 0x%lx count: %d", (long) data, count));
1086
1087 for (pos=data,end=data+count; pos < end ; pos++)
1088 {
1089 #ifdef MAIN
1090 printf("Thread: T@%u Rel lock: 0x%lx type: %d\n",
1091 pos[0]->owner->thread_id, (long) pos[0]->lock, pos[0]->type);
1092 fflush(stdout);
1093 #endif
1094 if ((*pos)->type != TL_UNLOCK)
1095 thr_unlock(*pos);
1096 else
1097 {
1098 DBUG_PRINT("lock",("Free lock: data: 0x%lx thread: 0x%x lock: 0x%lx",
1099 (long) *pos, (*pos)->owner->thread_id,
1100 (long) (*pos)->lock));
1101 }
1102 }
1103 DBUG_VOID_RETURN;
1104 }
1105
1106 /*
1107 Abort all threads waiting for a lock. The lock will be upgraded to
1108 TL_WRITE_ONLY to abort any new accesses to the lock
1109 */
1110
thr_abort_locks(THR_LOCK * lock,my_bool upgrade_lock)1111 void thr_abort_locks(THR_LOCK *lock, my_bool upgrade_lock)
1112 {
1113 THR_LOCK_DATA *data;
1114 DBUG_ENTER("thr_abort_locks");
1115 mysql_mutex_lock(&lock->mutex);
1116
1117 for (data=lock->read_wait.data; data ; data=data->next)
1118 {
1119 data->type=TL_UNLOCK; /* Mark killed */
1120 /* It's safe to signal the cond first: we're still holding the mutex. */
1121 mysql_cond_signal(data->cond);
1122 data->cond=0; /* Removed from list */
1123 }
1124 for (data=lock->write_wait.data; data ; data=data->next)
1125 {
1126 data->type=TL_UNLOCK;
1127 mysql_cond_signal(data->cond);
1128 data->cond=0;
1129 }
1130 lock->read_wait.last= &lock->read_wait.data;
1131 lock->write_wait.last= &lock->write_wait.data;
1132 lock->read_wait.data=lock->write_wait.data=0;
1133 if (upgrade_lock && lock->write.data)
1134 lock->write.data->type=TL_WRITE_ONLY;
1135 mysql_mutex_unlock(&lock->mutex);
1136 DBUG_VOID_RETURN;
1137 }
1138
1139
1140 /*
1141 Abort all locks for specific table/thread combination
1142
1143 This is used to abort all locks for a specific thread
1144 */
1145
thr_abort_locks_for_thread(THR_LOCK * lock,my_thread_id thread_id)1146 void thr_abort_locks_for_thread(THR_LOCK *lock, my_thread_id thread_id)
1147 {
1148 THR_LOCK_DATA *data;
1149 DBUG_ENTER("thr_abort_locks_for_thread");
1150
1151 mysql_mutex_lock(&lock->mutex);
1152 for (data= lock->read_wait.data; data ; data= data->next)
1153 {
1154 if (data->owner->thread_id == thread_id) /* purecov: tested */
1155 {
1156 DBUG_PRINT("info",("Aborting read-wait lock"));
1157 data->type= TL_UNLOCK; /* Mark killed */
1158 /* It's safe to signal the cond first: we're still holding the mutex. */
1159 mysql_cond_signal(data->cond);
1160 data->cond= 0; /* Removed from list */
1161
1162 if (((*data->prev)= data->next))
1163 data->next->prev= data->prev;
1164 else
1165 lock->read_wait.last= data->prev;
1166 }
1167 }
1168 for (data= lock->write_wait.data; data ; data= data->next)
1169 {
1170 if (data->owner->thread_id == thread_id) /* purecov: tested */
1171 {
1172 DBUG_PRINT("info",("Aborting write-wait lock"));
1173 data->type= TL_UNLOCK;
1174 mysql_cond_signal(data->cond);
1175 data->cond= 0;
1176
1177 if (((*data->prev)= data->next))
1178 data->next->prev= data->prev;
1179 else
1180 lock->write_wait.last= data->prev;
1181 }
1182 }
1183 wake_up_waiters(lock);
1184 mysql_mutex_unlock(&lock->mutex);
1185 DBUG_VOID_RETURN;
1186 }
1187
1188
1189 /*
1190 Downgrade a WRITE_* to a lower WRITE level
1191 SYNOPSIS
1192 thr_downgrade_write_lock()
1193 in_data Lock data of thread downgrading its lock
1194 new_lock_type New write lock type
1195 RETURN VALUE
1196 NONE
1197 DESCRIPTION
1198 This can be used to downgrade a lock already owned. When the downgrade
1199 occurs also other waiters, both readers and writers can be allowed to
1200 start.
1201 The previous lock is often TL_WRITE_ONLY but can also be
1202 TL_WRITE. The normal downgrade variants are:
1203 TL_WRITE_ONLY => TL_WRITE after a short exclusive lock while holding a
1204 write table lock
1205 TL_WRITE_ONLY => TL_WRITE_ALLOW_WRITE After a short exclusive lock after
1206 already earlier having dongraded lock to TL_WRITE_ALLOW_WRITE
1207 The implementation is conservative and rather don't start rather than
1208 go on unknown paths to start, the common cases are handled.
1209
1210 NOTE:
1211 In its current implementation it is only allowed to downgrade from
1212 TL_WRITE_ONLY. In this case there are no waiters. Thus no wake up
1213 logic is required.
1214 */
1215
thr_downgrade_write_lock(THR_LOCK_DATA * in_data,enum thr_lock_type new_lock_type)1216 void thr_downgrade_write_lock(THR_LOCK_DATA *in_data,
1217 enum thr_lock_type new_lock_type)
1218 {
1219 THR_LOCK *lock=in_data->lock;
1220 #ifndef NDEBUG
1221 enum thr_lock_type old_lock_type= in_data->type;
1222 #endif
1223 DBUG_ENTER("thr_downgrade_write_only_lock");
1224
1225 mysql_mutex_lock(&lock->mutex);
1226 assert(old_lock_type == TL_WRITE_ONLY);
1227 assert(old_lock_type > new_lock_type);
1228 in_data->type= new_lock_type;
1229 check_locks(lock,"after downgrading lock",0);
1230
1231 mysql_mutex_unlock(&lock->mutex);
1232 DBUG_VOID_RETURN;
1233 }
1234
1235
1236 #include <my_sys.h>
1237
thr_print_lock(const char * name,struct st_lock_list * list)1238 static void thr_print_lock(const char* name,struct st_lock_list *list)
1239 {
1240 THR_LOCK_DATA *data,**prev;
1241 uint count=0;
1242
1243 if (list->data)
1244 {
1245 printf("%-10s: ",name);
1246 prev= &list->data;
1247 for (data=list->data; data && count++ < MAX_LOCKS ; data=data->next)
1248 {
1249 printf("0x%lx (%u:%d); ", (ulong) data, data->owner->thread_id,
1250 (int) data->type);
1251 if (data->prev != prev)
1252 printf("\nWarning: prev didn't point at previous lock\n");
1253 prev= &data->next;
1254 }
1255 puts("");
1256 if (prev != list->last)
1257 printf("Warning: last didn't point at last lock\n");
1258 }
1259 }
1260
thr_print_locks(void)1261 void thr_print_locks(void)
1262 {
1263 LIST *list;
1264 uint count=0;
1265
1266 mysql_mutex_lock(&THR_LOCK_lock);
1267 puts("Current locks:");
1268 for (list= thr_lock_thread_list; list && count++ < MAX_THREADS;
1269 list= list_rest(list))
1270 {
1271 THR_LOCK *lock=(THR_LOCK*) list->data;
1272 mysql_mutex_lock(&lock->mutex);
1273 printf("lock: 0x%lx:",(ulong) lock);
1274 if ((lock->write_wait.data || lock->read_wait.data) &&
1275 (! lock->read.data && ! lock->write.data))
1276 printf(" WARNING: ");
1277 if (lock->write.data)
1278 printf(" write");
1279 if (lock->write_wait.data)
1280 printf(" write_wait");
1281 if (lock->read.data)
1282 printf(" read");
1283 if (lock->read_wait.data)
1284 printf(" read_wait");
1285 puts("");
1286 thr_print_lock("write",&lock->write);
1287 thr_print_lock("write_wait",&lock->write_wait);
1288 thr_print_lock("read",&lock->read);
1289 thr_print_lock("read_wait",&lock->read_wait);
1290 mysql_mutex_unlock(&lock->mutex);
1291 puts("");
1292 }
1293 fflush(stdout);
1294 mysql_mutex_unlock(&THR_LOCK_lock);
1295 }
1296
1297
1298 /*****************************************************************************
1299 ** Test of thread locks
1300 ****************************************************************************/
1301
1302 #ifdef MAIN
1303
1304 struct st_test {
1305 uint lock_nr;
1306 enum thr_lock_type lock_type;
1307 };
1308
1309 THR_LOCK locks[5]; /* 4 locks */
1310
1311 struct st_test test_0[] = {{0,TL_READ}}; /* One lock */
1312 struct st_test test_1[] = {{0,TL_READ},{0,TL_WRITE}}; /* Read and write lock of lock 0 */
1313 struct st_test test_2[] = {{1,TL_WRITE},{0,TL_READ},{2,TL_READ}};
1314 struct st_test test_3[] = {{2,TL_WRITE},{1,TL_READ},{0,TL_READ}}; /* Deadlock with test_2 ? */
1315 struct st_test test_4[] = {{0,TL_WRITE},{0,TL_READ},{0,TL_WRITE},{0,TL_READ}};
1316 struct st_test test_5[] = {{0,TL_READ},{1,TL_READ},{2,TL_READ},{3,TL_READ}}; /* Many reads */
1317 struct st_test test_6[] = {{0,TL_WRITE},{1,TL_WRITE},{2,TL_WRITE},{3,TL_WRITE}}; /* Many writes */
1318 struct st_test test_7[] = {{3,TL_READ}};
1319 struct st_test test_8[] = {{1,TL_READ_NO_INSERT},{2,TL_READ_NO_INSERT},{3,TL_READ_NO_INSERT}}; /* Should be quick */
1320 struct st_test test_9[] = {{4,TL_READ_HIGH_PRIORITY}};
1321 struct st_test test_10[] ={{4,TL_WRITE}};
1322 struct st_test test_11[] = {{0,TL_WRITE_LOW_PRIORITY},{1,TL_WRITE_LOW_PRIORITY},{2,TL_WRITE_LOW_PRIORITY},{3,TL_WRITE_LOW_PRIORITY}}; /* Many writes */
1323 struct st_test test_12[] = {{0,TL_WRITE_CONCURRENT_INSERT},{1,TL_WRITE_CONCURRENT_INSERT},{2,TL_WRITE_CONCURRENT_INSERT},{3,TL_WRITE_CONCURRENT_INSERT}};
1324 struct st_test test_13[] = {{0,TL_WRITE_CONCURRENT_INSERT},{1,TL_READ}};
1325 struct st_test test_14[] = {{0,TL_WRITE_ALLOW_WRITE},{1,TL_READ}};
1326 struct st_test test_15[] = {{0,TL_WRITE_ALLOW_WRITE},{1,TL_WRITE_ALLOW_WRITE}};
1327
1328 struct st_test *tests[] = {test_0,test_1,test_2,test_3,test_4,test_5,test_6,
1329 test_7,test_8,test_9,test_10,test_11,test_12,
1330 test_13,test_14,test_15};
1331 int lock_counts[]= {sizeof(test_0)/sizeof(struct st_test),
1332 sizeof(test_1)/sizeof(struct st_test),
1333 sizeof(test_2)/sizeof(struct st_test),
1334 sizeof(test_3)/sizeof(struct st_test),
1335 sizeof(test_4)/sizeof(struct st_test),
1336 sizeof(test_5)/sizeof(struct st_test),
1337 sizeof(test_6)/sizeof(struct st_test),
1338 sizeof(test_7)/sizeof(struct st_test),
1339 sizeof(test_8)/sizeof(struct st_test),
1340 sizeof(test_9)/sizeof(struct st_test),
1341 sizeof(test_10)/sizeof(struct st_test),
1342 sizeof(test_11)/sizeof(struct st_test),
1343 sizeof(test_12)/sizeof(struct st_test),
1344 sizeof(test_13)/sizeof(struct st_test),
1345 sizeof(test_14)/sizeof(struct st_test),
1346 sizeof(test_15)/sizeof(struct st_test)
1347 };
1348
1349
1350 static mysql_cond_t COND_thread_count;
1351 static mysql_mutex_t LOCK_thread_count;
1352 static uint thread_count;
1353 static ulong sum=0;
1354
1355 #define MAX_LOCK_COUNT 8
1356 #define TEST_TIMEOUT 100000
1357
1358 /* The following functions is for WRITE_CONCURRENT_INSERT */
1359
test_get_status(void * param MY_ATTRIBUTE ((unused)),int concurrent_insert MY_ATTRIBUTE ((unused)))1360 static void test_get_status(void* param MY_ATTRIBUTE((unused)),
1361 int concurrent_insert MY_ATTRIBUTE((unused)))
1362 {
1363 }
1364
test_update_status(void * param MY_ATTRIBUTE ((unused)))1365 static void test_update_status(void* param MY_ATTRIBUTE((unused)))
1366 {
1367 }
1368
test_copy_status(void * to MY_ATTRIBUTE ((unused)),void * from MY_ATTRIBUTE ((unused)))1369 static void test_copy_status(void* to MY_ATTRIBUTE((unused)) ,
1370 void *from MY_ATTRIBUTE((unused)))
1371 {
1372 }
1373
test_check_status(void * param MY_ATTRIBUTE ((unused)))1374 static my_bool test_check_status(void* param MY_ATTRIBUTE((unused)))
1375 {
1376 return 0;
1377 }
1378
1379
test_thread(void * arg)1380 static void *test_thread(void *arg)
1381 {
1382 int i,j,param=*((int*) arg);
1383 THR_LOCK_DATA data[MAX_LOCK_COUNT];
1384 THR_LOCK_INFO lock_info;
1385 THR_LOCK_DATA *multi_locks[MAX_LOCK_COUNT];
1386 my_thread_id id;
1387 mysql_cond_t COND_thr_lock;
1388 COND_thr_lock.m_psi = NULL;
1389
1390 id= param + 1; /* Main thread uses value 0. */
1391 mysql_cond_init(0, &COND_thr_lock);
1392
1393 printf("Thread T@%d started\n", id);
1394 fflush(stdout);
1395
1396 thr_lock_info_init(&lock_info, id, &COND_thr_lock);
1397 for (i=0; i < lock_counts[param] ; i++)
1398 {
1399 thr_lock_data_init(locks+tests[param][i].lock_nr,data+i,NULL);
1400 data[i].m_psi= NULL;
1401 }
1402 for (j=1 ; j < 10 ; j++) /* try locking 10 times */
1403 {
1404 for (i=0; i < lock_counts[param] ; i++)
1405 { /* Init multi locks */
1406 multi_locks[i]= &data[i];
1407 data[i].type= tests[param][i].lock_type;
1408 }
1409 thr_multi_lock(multi_locks, lock_counts[param], &lock_info, TEST_TIMEOUT);
1410 mysql_mutex_lock(&LOCK_thread_count);
1411 {
1412 int tmp=rand() & 7; /* Do something from 0-2 sec */
1413 if (tmp == 0)
1414 sleep(1);
1415 else if (tmp == 1)
1416 sleep(2);
1417 else
1418 {
1419 ulong k;
1420 for (k=0 ; k < (ulong) (tmp-2)*100000L ; k++)
1421 sum+=k;
1422 }
1423 }
1424 mysql_mutex_unlock(&LOCK_thread_count);
1425 thr_multi_unlock(multi_locks,lock_counts[param]);
1426 }
1427
1428 printf("Thread T@%d ended\n", id);
1429 fflush(stdout);
1430 thr_print_locks();
1431 mysql_mutex_lock(&LOCK_thread_count);
1432 thread_count--;
1433 mysql_cond_signal(&COND_thread_count); /* Tell main we are ready */
1434 mysql_mutex_unlock(&LOCK_thread_count);
1435 mysql_cond_destroy(&COND_thr_lock);
1436 free((uchar*) arg);
1437 return 0;
1438 }
1439
1440
main(int argc MY_ATTRIBUTE ((unused)),char ** argv MY_ATTRIBUTE ((unused)))1441 int main(int argc MY_ATTRIBUTE((unused)),char **argv MY_ATTRIBUTE((unused)))
1442 {
1443 my_thread_handle tid;
1444 my_thread_attr_t thr_attr;
1445 int i,*param,error;
1446 MY_INIT(argv[0]);
1447 if (argc > 1 && argv[1][0] == '-' && argv[1][1] == '#')
1448 DBUG_PUSH(argv[1]+2);
1449
1450 printf("Main thread: T@%u\n", 0); /* 0 for main thread, 1+ for test_thread */
1451
1452 if ((error= mysql_cond_init(0, &COND_thread_count)))
1453 {
1454 my_message_stderr(0, "Got error %d from mysql_cond_init", errno);
1455 exit(1);
1456 }
1457 if ((error= mysql_mutex_init(0, &LOCK_thread_count, MY_MUTEX_INIT_FAST)))
1458 {
1459 my_message_stderr(0, "Got error %d from mysql_cond_init", errno);
1460 exit(1);
1461 }
1462
1463 for (i=0 ; i < (int) array_elements(locks) ; i++)
1464 {
1465 thr_lock_init(locks+i);
1466 locks[i].check_status= test_check_status;
1467 locks[i].update_status=test_update_status;
1468 locks[i].copy_status= test_copy_status;
1469 locks[i].get_status= test_get_status;
1470 }
1471 if ((error=my_thread_attr_init(&thr_attr)))
1472 {
1473 my_message_stderr(0, "Got error %d from pthread_attr_init",errno);
1474 exit(1);
1475 }
1476 if ((error= my_thread_attr_setdetachstate(&thr_attr, MY_THREAD_CREATE_DETACHED)))
1477 {
1478 my_message_stderr(0, "Got error %d from "
1479 "my_thread_attr_setdetachstate", errno);
1480 exit(1);
1481 }
1482 if ((error= my_thread_attr_setstacksize(&thr_attr,65536L)))
1483 {
1484 my_message_stderr(0, "Got error %d from "
1485 "my_thread_attr_setstacksize", error);
1486 exit(1);
1487 }
1488 for (i=0 ; i < (int) array_elements(lock_counts) ; i++)
1489 {
1490 param=(int*) malloc(sizeof(int));
1491 *param=i;
1492
1493 if ((error= mysql_mutex_lock(&LOCK_thread_count)))
1494 {
1495 my_message_stderr(0, "Got error %d from mysql_mutex_lock",
1496 errno);
1497 exit(1);
1498 }
1499 if ((error= mysql_thread_create(0,
1500 &tid, &thr_attr, test_thread,
1501 (void*) param)))
1502 {
1503 my_message_stderr(0, "Got error %d from mysql_thread_create",
1504 errno);
1505 mysql_mutex_unlock(&LOCK_thread_count);
1506 exit(1);
1507 }
1508 thread_count++;
1509 mysql_mutex_unlock(&LOCK_thread_count);
1510 }
1511
1512 my_thread_attr_destroy(&thr_attr);
1513 if ((error= mysql_mutex_lock(&LOCK_thread_count)))
1514 my_message_stderr(0, "Got error %d from mysql_mutex_lock", error);
1515 while (thread_count)
1516 {
1517 if ((error= mysql_cond_wait(&COND_thread_count, &LOCK_thread_count)))
1518 my_message_stderr(0, "Got error %d from mysql_cond_wait",
1519 error);
1520 }
1521 if ((error= mysql_mutex_unlock(&LOCK_thread_count)))
1522 my_message_stderr(0, "Got error %d from mysql_mutex_unlock",
1523 error);
1524 for (i=0 ; i < (int) array_elements(locks) ; i++)
1525 thr_lock_delete(locks+i);
1526 #ifdef EXTRA_DEBUG
1527 if (found_errors)
1528 printf("Got %d warnings\n", found_errors);
1529 else
1530 #endif
1531 printf("Test succeeded\n");
1532 return 0;
1533 }
1534
1535 #endif /* MAIN */
1536