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 my_bool use_wait_callbacks= FALSE;
377 DBUG_ENTER("wait_for_lock");
378
379 /*
380 One can use this to signal when a thread is going to wait for a lock.
381 See debug_sync.cc.
382
383 Beware of waiting for a signal here. The lock has aquired its mutex.
384 While waiting on a signal here, the locking thread could not aquire
385 the mutex to release the lock. One could lock up the table
386 completely.
387
388 In detail it works so: When thr_lock() tries to acquire a table
389 lock, it locks the lock->mutex, checks if it can have the lock, and
390 if not, it calls wait_for_lock(). Here it unlocks the table lock
391 while waiting on a condition. The sync point is located before this
392 wait for condition. If we have a waiting action here, we hold the
393 the table locks mutex all the time. Any attempt to look at the table
394 lock by another thread blocks it immediately on lock->mutex. This
395 can easily become an unexpected and unobvious blockage. So be
396 warned: Do not request a WAIT_FOR action for the 'wait_for_lock'
397 sync point unless you really know what you do.
398 */
399 DEBUG_SYNC_C("wait_for_lock");
400
401 if (!in_wait_list)
402 {
403 (*wait->last)=data; /* Wait for lock */
404 data->prev= wait->last;
405 wait->last= &data->next;
406 }
407
408 locks_waited++;
409
410 /* Set up control struct to allow others to abort locks */
411 data->cond= owner->suspend;
412
413 enter_cond_hook(NULL, data->cond, &data->lock->mutex,
414 &stage_waiting_for_table_level_lock, &old_stage,
415 __func__, __FILE__, __LINE__);
416
417 /*
418 Since before_lock_wait potentially can create more threads to
419 scheduler work for, we don't want to call the before_lock_wait
420 callback unless it will really start to wait.
421
422 For similar reasons, we do not want to call before_lock_wait and
423 after_lock_wait for each lap around the loop, so we restrict
424 ourselves to call it before_lock_wait once before starting to wait
425 and once after the thread has exited the wait loop.
426 */
427 if ((!is_killed_hook(NULL) || in_wait_list) && before_lock_wait)
428 {
429 use_wait_callbacks= TRUE;
430 (*before_lock_wait)();
431 }
432
433 set_timespec(&wait_timeout, lock_wait_timeout);
434 while (!is_killed_hook(NULL) || in_wait_list)
435 {
436 int rc= mysql_cond_timedwait(data->cond, &data->lock->mutex, &wait_timeout);
437 /*
438 We must break the wait if one of the following occurs:
439 - the connection has been aborted (!is_killed_hook()),
440 - the lock has been granted (data->cond is set to NULL by the granter),
441 or the waiting has been aborted (additionally data->type is set to
442 TL_UNLOCK).
443 - the wait has timed out (rc == ETIMEDOUT)
444 Order of checks below is important to not report about timeout
445 if the predicate is true.
446 */
447 if (data->cond == 0)
448 {
449 DBUG_PRINT("thr_lock", ("lock granted/aborted"));
450 break;
451 }
452 if (rc == ETIMEDOUT || rc == ETIME)
453 {
454 /* purecov: begin inspected */
455 DBUG_PRINT("thr_lock", ("lock timed out"));
456 result= THR_LOCK_WAIT_TIMEOUT;
457 break;
458 /* purecov: end */
459 }
460 }
461
462 /*
463 We call the after_lock_wait callback once the wait loop has
464 finished.
465 */
466 if (after_lock_wait && use_wait_callbacks)
467 (*after_lock_wait)();
468
469 if (data->cond || data->type == TL_UNLOCK)
470 {
471 if (data->cond) /* aborted or timed out */
472 {
473 if (((*data->prev)=data->next)) /* remove from wait-list */
474 data->next->prev= data->prev;
475 else
476 wait->last=data->prev;
477 data->type= TL_UNLOCK; /* No lock */
478 check_locks(data->lock, "killed or timed out wait_for_lock", 1);
479 wake_up_waiters(data->lock);
480 }
481 else
482 {
483 DBUG_PRINT("thr_lock", ("lock aborted"));
484 check_locks(data->lock, "aborted wait_for_lock", 0);
485 }
486 }
487 else
488 {
489 result= THR_LOCK_SUCCESS;
490 if (data->lock->get_status)
491 (*data->lock->get_status)(data->status_param, 0);
492 check_locks(data->lock,"got wait_for_lock",0);
493 }
494 mysql_mutex_unlock(&data->lock->mutex);
495
496 exit_cond_hook(NULL, &old_stage, __func__, __FILE__, __LINE__);
497
498 DBUG_RETURN(result);
499 }
500
501
502 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)503 thr_lock(THR_LOCK_DATA *data, THR_LOCK_INFO *owner,
504 enum thr_lock_type lock_type, ulong lock_wait_timeout)
505 {
506 THR_LOCK *lock=data->lock;
507 enum enum_thr_lock_result result= THR_LOCK_SUCCESS;
508 struct st_lock_list *wait_queue;
509 MYSQL_TABLE_WAIT_VARIABLES(locker, state) /* no ';' */
510 DBUG_ENTER("thr_lock");
511
512 data->next=0;
513 data->cond=0; /* safety */
514 data->type=lock_type;
515 data->owner= owner; /* Must be reset ! */
516
517 MYSQL_START_TABLE_LOCK_WAIT(locker, &state, data->m_psi,
518 PSI_TABLE_LOCK, lock_type);
519
520 mysql_mutex_lock(&lock->mutex);
521 DBUG_PRINT("lock",("data: 0x%lx thread: 0x%x lock: 0x%lx type: %d",
522 (long) data, data->owner->thread_id,
523 (long) lock, (int) lock_type));
524 check_locks(lock,(uint) lock_type <= (uint) TL_READ_NO_INSERT ?
525 "enter read_lock" : "enter write_lock",0);
526 if ((int) lock_type <= (int) TL_READ_NO_INSERT)
527 {
528 /* Request for READ lock */
529 if (lock->write.data)
530 {
531 /*
532 We can allow a read lock even if there is already a
533 write lock on the table if they are owned by the same
534 thread or if they satisfy the following lock
535 compatibility matrix:
536
537 Request
538 /-------
539 H|++++ WRITE_ALLOW_WRITE
540 e|+++- WRITE_CONCURRENT_INSERT
541 l ||||
542 d ||||
543 |||\= READ_NO_INSERT
544 ||\ = READ_HIGH_PRIORITY
545 |\ = READ_WITH_SHARED_LOCKS
546 \ = READ
547
548 + = Request can be satisified.
549 - = Request cannot be satisified.
550
551 READ_NO_INSERT and WRITE_ALLOW_WRITE should in principle
552 be incompatible. Before this could have caused starvation of
553 LOCK TABLE READ in InnoDB under high write load. However
554 now READ_NO_INSERT is only used for LOCK TABLES READ and this
555 statement is handled by the MDL subsystem.
556 See Bug#42147 for more information.
557 */
558
559 DBUG_PRINT("lock",("write locked 1 by thread: 0x%x",
560 lock->write.data->owner->thread_id));
561 if (thr_lock_owner_equal(data->owner, lock->write.data->owner) ||
562 (lock->write.data->type < TL_WRITE_CONCURRENT_INSERT) ||
563 ((lock->write.data->type == TL_WRITE_CONCURRENT_INSERT) &&
564 ((int) lock_type <= (int) TL_READ_HIGH_PRIORITY)))
565 { /* Already got a write lock */
566 (*lock->read.last)=data; /* Add to running FIFO */
567 data->prev=lock->read.last;
568 lock->read.last= &data->next;
569 if (lock_type == TL_READ_NO_INSERT)
570 lock->read_no_write_count++;
571 check_locks(lock,"read lock with old write lock",0);
572 if (lock->get_status)
573 (*lock->get_status)(data->status_param, 0);
574 locks_immediate++;
575 goto end;
576 }
577 if (lock->write.data->type == TL_WRITE_ONLY)
578 {
579 /* We are not allowed to get a READ lock in this case */
580 data->type=TL_UNLOCK;
581 result= THR_LOCK_ABORTED; /* Can't wait for this one */
582 goto end;
583 }
584 }
585 else if (!lock->write_wait.data ||
586 lock->write_wait.data->type <= TL_WRITE_LOW_PRIORITY ||
587 lock_type == TL_READ_HIGH_PRIORITY ||
588 has_old_lock(lock->read.data, data->owner)) /* Has old read lock */
589 { /* No important write-locks */
590 (*lock->read.last)=data; /* Add to running FIFO */
591 data->prev=lock->read.last;
592 lock->read.last= &data->next;
593 if (lock->get_status)
594 (*lock->get_status)(data->status_param, 0);
595 if (lock_type == TL_READ_NO_INSERT)
596 lock->read_no_write_count++;
597 check_locks(lock,"read lock with no write locks",0);
598 locks_immediate++;
599 goto end;
600 }
601 /*
602 We're here if there is an active write lock or no write
603 lock but a high priority write waiting in the write_wait queue.
604 In the latter case we should yield the lock to the writer.
605 */
606 wait_queue= &lock->read_wait;
607 }
608 else /* Request for WRITE lock */
609 {
610 if (lock_type == TL_WRITE_CONCURRENT_INSERT && ! lock->check_status)
611 data->type=lock_type= thr_upgraded_concurrent_insert_lock;
612
613 if (lock->write.data) /* If there is a write lock */
614 {
615 if (lock->write.data->type == TL_WRITE_ONLY)
616 {
617 /* purecov: begin tested */
618 /* Allow lock owner to bypass TL_WRITE_ONLY. */
619 if (!thr_lock_owner_equal(data->owner, lock->write.data->owner))
620 {
621 /* We are not allowed to get a lock in this case */
622 data->type=TL_UNLOCK;
623 result= THR_LOCK_ABORTED; /* Can't wait for this one */
624 goto end;
625 }
626 /* purecov: end */
627 }
628
629 /*
630 The idea is to allow us to get a lock at once if we already have
631 a write lock or if there is no pending write locks and if all
632 write locks are of TL_WRITE_ALLOW_WRITE type.
633
634 Note that, since lock requests for the same table are sorted in
635 such way that requests with higher thr_lock_type value come first
636 (with one exception (*)), lock being requested usually has
637 equal or "weaker" type than one which thread might have already
638 acquired.
639 *) The only exception to this rule is case when type of old lock
640 is TL_WRITE_LOW_PRIORITY and type of new lock is changed inside
641 of thr_lock() from TL_WRITE_CONCURRENT_INSERT to TL_WRITE since
642 engine turns out to be not supporting concurrent inserts.
643 Note that since TL_WRITE has the same compatibility rules as
644 TL_WRITE_LOW_PRIORITY (their only difference is priority),
645 it is OK to grant new lock without additional checks in such
646 situation.
647
648 Therefore it is OK to allow acquiring write lock on the table if
649 this thread already holds some write lock on it.
650
651 (INSERT INTO t1 VALUES (f1()), where f1() is stored function which
652 tries to update t1, is an example of statement which requests two
653 different types of write lock on the same table).
654 */
655 assert(! has_old_lock(lock->write.data, data->owner) ||
656 ((lock_type <= lock->write.data->type ||
657 (lock_type == TL_WRITE &&
658 lock->write.data->type == TL_WRITE_LOW_PRIORITY))));
659
660 if ((lock_type == TL_WRITE_ALLOW_WRITE &&
661 ! lock->write_wait.data &&
662 lock->write.data->type == TL_WRITE_ALLOW_WRITE) ||
663 has_old_lock(lock->write.data, data->owner))
664 {
665 /*
666 We have already got a write lock or all locks are
667 TL_WRITE_ALLOW_WRITE
668 */
669 DBUG_PRINT("info", ("write_wait.data: 0x%lx old_type: %d",
670 (ulong) lock->write_wait.data,
671 lock->write.data->type));
672
673 (*lock->write.last)=data; /* Add to running fifo */
674 data->prev=lock->write.last;
675 lock->write.last= &data->next;
676 check_locks(lock,"second write lock",0);
677 if (data->lock->get_status)
678 (*data->lock->get_status)(data->status_param, 0);
679 locks_immediate++;
680 goto end;
681 }
682 DBUG_PRINT("lock",("write locked 2 by thread: 0x%x",
683 lock->write.data->owner->thread_id));
684 }
685 else
686 {
687 DBUG_PRINT("info", ("write_wait.data: 0x%lx",
688 (ulong) lock->write_wait.data));
689 if (!lock->write_wait.data)
690 { /* no scheduled write locks */
691 my_bool concurrent_insert= 0;
692 if (lock_type == TL_WRITE_CONCURRENT_INSERT)
693 {
694 concurrent_insert= 1;
695 if ((*lock->check_status)(data->status_param))
696 {
697 concurrent_insert= 0;
698 data->type=lock_type= thr_upgraded_concurrent_insert_lock;
699 }
700 }
701
702 if (!lock->read.data ||
703 (lock_type <= TL_WRITE_CONCURRENT_INSERT &&
704 ((lock_type != TL_WRITE_CONCURRENT_INSERT &&
705 lock_type != TL_WRITE_ALLOW_WRITE) ||
706 !lock->read_no_write_count)))
707 {
708 (*lock->write.last)=data; /* Add as current write lock */
709 data->prev=lock->write.last;
710 lock->write.last= &data->next;
711 if (data->lock->get_status)
712 (*data->lock->get_status)(data->status_param, concurrent_insert);
713 check_locks(lock,"only write lock",0);
714 locks_immediate++;
715 goto end;
716 }
717 }
718 DBUG_PRINT("lock",("write locked 3 by thread: 0x%x type: %d",
719 lock->read.data->owner->thread_id, data->type));
720 }
721 wait_queue= &lock->write_wait;
722 }
723 /* Can't get lock yet; Wait for it */
724 result= wait_for_lock(wait_queue, data, owner, 0,
725 lock_wait_timeout);
726 MYSQL_END_TABLE_LOCK_WAIT(locker);
727 DBUG_RETURN(result);
728 end:
729 mysql_mutex_unlock(&lock->mutex);
730 MYSQL_END_TABLE_LOCK_WAIT(locker);
731 DBUG_RETURN(result);
732 }
733
734
free_all_read_locks(THR_LOCK * lock,my_bool using_concurrent_insert)735 static inline void free_all_read_locks(THR_LOCK *lock,
736 my_bool using_concurrent_insert)
737 {
738 THR_LOCK_DATA *data=lock->read_wait.data;
739
740 check_locks(lock,"before freeing read locks",1);
741
742 /* move all locks from read_wait list to read list */
743 (*lock->read.last)=data;
744 data->prev=lock->read.last;
745 lock->read.last=lock->read_wait.last;
746
747 /* Clear read_wait list */
748 lock->read_wait.last= &lock->read_wait.data;
749
750 do
751 {
752 mysql_cond_t *cond= data->cond;
753 if ((int) data->type == (int) TL_READ_NO_INSERT)
754 {
755 if (using_concurrent_insert)
756 {
757 /*
758 We can't free this lock;
759 Link lock away from read chain back into read_wait chain
760 */
761 if (((*data->prev)=data->next))
762 data->next->prev=data->prev;
763 else
764 lock->read.last=data->prev;
765 *lock->read_wait.last= data;
766 data->prev= lock->read_wait.last;
767 lock->read_wait.last= &data->next;
768 continue;
769 }
770 lock->read_no_write_count++;
771 }
772 /* purecov: begin inspected */
773 DBUG_PRINT("lock",("giving read lock to thread: 0x%x",
774 data->owner->thread_id));
775 /* purecov: end */
776 data->cond=0; /* Mark thread free */
777 mysql_cond_signal(cond);
778 } while ((data=data->next));
779 *lock->read_wait.last=0;
780 if (!lock->read_wait.data)
781 lock->write_lock_count=0;
782 check_locks(lock,"after giving read locks",0);
783 }
784
785 /* Unlock lock and free next thread on same lock */
786
thr_unlock(THR_LOCK_DATA * data)787 void thr_unlock(THR_LOCK_DATA *data)
788 {
789 THR_LOCK *lock=data->lock;
790 enum thr_lock_type lock_type=data->type;
791 DBUG_ENTER("thr_unlock");
792 DBUG_PRINT("lock",("data: 0x%lx thread: 0x%x lock: 0x%lx",
793 (long) data, data->owner->thread_id, (long) lock));
794 mysql_mutex_lock(&lock->mutex);
795 check_locks(lock,"start of release lock",0);
796
797 if (((*data->prev)=data->next)) /* remove from lock-list */
798 data->next->prev= data->prev;
799 else if (lock_type <= TL_READ_NO_INSERT)
800 lock->read.last=data->prev;
801 else
802 lock->write.last=data->prev;
803 if (lock_type >= TL_WRITE_CONCURRENT_INSERT)
804 {
805 if (lock->update_status)
806 (*lock->update_status)(data->status_param);
807 }
808 else
809 {
810 if (lock->restore_status)
811 (*lock->restore_status)(data->status_param);
812 }
813 if (lock_type == TL_READ_NO_INSERT)
814 lock->read_no_write_count--;
815 data->type=TL_UNLOCK; /* Mark unlocked */
816 MYSQL_UNLOCK_TABLE(data->m_psi);
817 check_locks(lock,"after releasing lock",1);
818 wake_up_waiters(lock);
819 mysql_mutex_unlock(&lock->mutex);
820 DBUG_VOID_RETURN;
821 }
822
823
824 /**
825 @brief Wake up all threads which pending requests for the lock
826 can be satisfied.
827
828 @param lock Lock for which threads should be woken up
829
830 */
831
wake_up_waiters(THR_LOCK * lock)832 static void wake_up_waiters(THR_LOCK *lock)
833 {
834 THR_LOCK_DATA *data;
835 enum thr_lock_type lock_type;
836
837 DBUG_ENTER("wake_up_waiters");
838
839 if (!lock->write.data) /* If no active write locks */
840 {
841 data=lock->write_wait.data;
842 if (!lock->read.data) /* If no more locks in use */
843 {
844 /* Release write-locks with TL_WRITE or TL_WRITE_ONLY priority first */
845 if (data &&
846 (data->type != TL_WRITE_LOW_PRIORITY || !lock->read_wait.data ||
847 lock->read_wait.data->type < TL_READ_HIGH_PRIORITY))
848 {
849 if (lock->write_lock_count++ > max_write_lock_count)
850 {
851 /* Too many write locks in a row; Release all waiting read locks */
852 lock->write_lock_count=0;
853 if (lock->read_wait.data)
854 {
855 DBUG_PRINT("info",("Freeing all read_locks because of max_write_lock_count"));
856 free_all_read_locks(lock,0);
857 goto end;
858 }
859 }
860 for (;;)
861 {
862 if (((*data->prev)=data->next)) /* remove from wait-list */
863 data->next->prev= data->prev;
864 else
865 lock->write_wait.last=data->prev;
866 (*lock->write.last)=data; /* Put in execute list */
867 data->prev=lock->write.last;
868 data->next=0;
869 lock->write.last= &data->next;
870 if (data->type == TL_WRITE_CONCURRENT_INSERT &&
871 (*lock->check_status)(data->status_param))
872 data->type=TL_WRITE; /* Upgrade lock */
873 /* purecov: begin inspected */
874 DBUG_PRINT("lock",("giving write lock of type %d to thread: 0x%x",
875 data->type, data->owner->thread_id));
876 /* purecov: end */
877 {
878 mysql_cond_t *cond= data->cond;
879 data->cond=0; /* Mark thread free */
880 mysql_cond_signal(cond); /* Start waiting thread */
881 }
882 if (data->type != TL_WRITE_ALLOW_WRITE ||
883 !lock->write_wait.data ||
884 lock->write_wait.data->type != TL_WRITE_ALLOW_WRITE)
885 break;
886 data=lock->write_wait.data; /* Free this too */
887 }
888 if (data->type >= TL_WRITE_LOW_PRIORITY)
889 goto end;
890 /* Release possible read locks together with the write lock */
891 }
892 if (lock->read_wait.data)
893 free_all_read_locks(lock,
894 data &&
895 (data->type == TL_WRITE_CONCURRENT_INSERT ||
896 data->type == TL_WRITE_ALLOW_WRITE));
897 else
898 {
899 DBUG_PRINT("lock",("No waiting read locks to free"));
900 }
901 }
902 else if (data &&
903 (lock_type= data->type) <= TL_WRITE_CONCURRENT_INSERT &&
904 ((lock_type != TL_WRITE_CONCURRENT_INSERT &&
905 lock_type != TL_WRITE_ALLOW_WRITE) ||
906 !lock->read_no_write_count))
907 {
908 /*
909 For ALLOW_READ, WRITE_ALLOW_WRITE or CONCURRENT_INSERT locks
910 start WRITE locks together with the READ locks
911 */
912 if (lock_type == TL_WRITE_CONCURRENT_INSERT &&
913 (*lock->check_status)(data->status_param))
914 {
915 data->type=TL_WRITE; /* Upgrade lock */
916 if (lock->read_wait.data)
917 free_all_read_locks(lock,0);
918 goto end;
919 }
920 do {
921 mysql_cond_t *cond= data->cond;
922 if (((*data->prev)=data->next)) /* remove from wait-list */
923 data->next->prev= data->prev;
924 else
925 lock->write_wait.last=data->prev;
926 (*lock->write.last)=data; /* Put in execute list */
927 data->prev=lock->write.last;
928 lock->write.last= &data->next;
929 data->next=0; /* Only one write lock */
930 data->cond=0; /* Mark thread free */
931 mysql_cond_signal(cond); /* Start waiting thread */
932 } while (lock_type == TL_WRITE_ALLOW_WRITE &&
933 (data=lock->write_wait.data) &&
934 data->type == TL_WRITE_ALLOW_WRITE);
935 if (lock->read_wait.data)
936 free_all_read_locks(lock,
937 (lock_type == TL_WRITE_CONCURRENT_INSERT ||
938 lock_type == TL_WRITE_ALLOW_WRITE));
939 }
940 else if (!data && lock->read_wait.data)
941 free_all_read_locks(lock,0);
942 }
943 end:
944 check_locks(lock, "after waking up waiters", 0);
945 DBUG_VOID_RETURN;
946 }
947
948
949 /*
950 ** Get all locks in a specific order to avoid dead-locks
951 ** Sort acording to lock position and put write_locks before read_locks if
952 ** lock on same lock.
953 */
954
955
956 #define LOCK_CMP(A,B) ((uchar*) (A->lock) - (uint) ((A)->type) < (uchar*) (B->lock)- (uint) ((B)->type))
957
sort_locks(THR_LOCK_DATA ** data,uint count)958 static void sort_locks(THR_LOCK_DATA **data,uint count)
959 {
960 THR_LOCK_DATA **pos,**end,**prev,*tmp;
961
962 /* Sort locks with insertion sort (fast because almost always few locks) */
963
964 for (pos=data+1,end=data+count; pos < end ; pos++)
965 {
966 tmp= *pos;
967 if (LOCK_CMP(tmp,pos[-1]))
968 {
969 prev=pos;
970 do {
971 prev[0]=prev[-1];
972 } while (--prev != data && LOCK_CMP(tmp,prev[-1]));
973 prev[0]=tmp;
974 }
975 }
976 }
977
978
979 enum enum_thr_lock_result
thr_multi_lock(THR_LOCK_DATA ** data,uint count,THR_LOCK_INFO * owner,ulong lock_wait_timeout)980 thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_INFO *owner,
981 ulong lock_wait_timeout)
982 {
983 THR_LOCK_DATA **pos,**end;
984 DBUG_ENTER("thr_multi_lock");
985 DBUG_PRINT("lock",("data: 0x%lx count: %d", (long) data, count));
986 if (count > 1)
987 sort_locks(data,count);
988 /* lock everything */
989 for (pos=data,end=data+count; pos < end ; pos++)
990 {
991 enum enum_thr_lock_result result= thr_lock(*pos, owner, (*pos)->type,
992 lock_wait_timeout);
993 if (result != THR_LOCK_SUCCESS)
994 { /* Aborted */
995 thr_multi_unlock(data,(uint) (pos-data));
996 DBUG_RETURN(result);
997 }
998 DEBUG_SYNC_C("thr_multi_lock_after_thr_lock");
999 #ifdef MAIN
1000 printf("Thread: T@%u Got lock: 0x%lx type: %d\n",
1001 pos[0]->owner->thread_id, (long) pos[0]->lock, pos[0]->type);
1002 fflush(stdout);
1003 #endif
1004 }
1005 thr_lock_merge_status(data, count);
1006 DBUG_RETURN(THR_LOCK_SUCCESS);
1007 }
1008
1009
1010 /**
1011 Ensure that all locks for a given table have the same
1012 status_param.
1013
1014 This is a MyISAM and possibly Maria specific crutch. MyISAM
1015 engine stores data file length, record count and other table
1016 properties in status_param member of handler. When a table is
1017 locked, connection-local copy is made from a global copy
1018 (myisam_share) by mi_get_status(). When a table is unlocked,
1019 the changed status is transferred back to the global share by
1020 mi_update_status().
1021
1022 One thing MyISAM doesn't do is to ensure that when the same
1023 table is opened twice in a connection all instances share the
1024 same status_param. This is necessary, however: for one, to keep
1025 all instances of a connection "on the same page" with regard to
1026 the current state of the table. For other, unless this is done,
1027 myisam_share will always get updated from the last unlocked
1028 instance (in mi_update_status()), and when this instance was not
1029 the one that was used to update data, records may be lost.
1030
1031 For each table, this function looks up the last lock_data in the
1032 list of acquired locks, and makes sure that all other instances
1033 share status_param with it.
1034 */
1035
1036 void
thr_lock_merge_status(THR_LOCK_DATA ** data,uint count)1037 thr_lock_merge_status(THR_LOCK_DATA **data, uint count)
1038 {
1039 THR_LOCK_DATA **pos= data;
1040 THR_LOCK_DATA **end= data + count;
1041 if (count > 1)
1042 {
1043 THR_LOCK_DATA *last_lock= end[-1];
1044 pos=end-1;
1045 do
1046 {
1047 pos--;
1048 if (last_lock->lock == (*pos)->lock &&
1049 last_lock->lock->copy_status)
1050 {
1051 if (last_lock->type <= TL_READ_NO_INSERT)
1052 {
1053 THR_LOCK_DATA **read_lock;
1054 /*
1055 If we are locking the same table with read locks we must ensure
1056 that all tables share the status of the last write lock or
1057 the same read lock.
1058 */
1059 for (;
1060 (*pos)->type <= TL_READ_NO_INSERT &&
1061 pos != data &&
1062 pos[-1]->lock == (*pos)->lock ;
1063 pos--) ;
1064
1065 read_lock = pos+1;
1066 do
1067 {
1068 (last_lock->lock->copy_status)((*read_lock)->status_param,
1069 (*pos)->status_param);
1070 } while (*(read_lock++) != last_lock);
1071 last_lock= (*pos); /* Point at last write lock */
1072 }
1073 else
1074 (*last_lock->lock->copy_status)((*pos)->status_param,
1075 last_lock->status_param);
1076 }
1077 else
1078 last_lock=(*pos);
1079 } while (pos != data);
1080 }
1081 }
1082
1083 /* free all locks */
1084
thr_multi_unlock(THR_LOCK_DATA ** data,uint count)1085 void thr_multi_unlock(THR_LOCK_DATA **data,uint count)
1086 {
1087 THR_LOCK_DATA **pos,**end;
1088 DBUG_ENTER("thr_multi_unlock");
1089 DBUG_PRINT("lock",("data: 0x%lx count: %d", (long) data, count));
1090
1091 for (pos=data,end=data+count; pos < end ; pos++)
1092 {
1093 #ifdef MAIN
1094 printf("Thread: T@%u Rel lock: 0x%lx type: %d\n",
1095 pos[0]->owner->thread_id, (long) pos[0]->lock, pos[0]->type);
1096 fflush(stdout);
1097 #endif
1098 if ((*pos)->type != TL_UNLOCK)
1099 thr_unlock(*pos);
1100 else
1101 {
1102 DBUG_PRINT("lock",("Free lock: data: 0x%lx thread: 0x%x lock: 0x%lx",
1103 (long) *pos, (*pos)->owner->thread_id,
1104 (long) (*pos)->lock));
1105 }
1106 }
1107 DBUG_VOID_RETURN;
1108 }
1109
1110 /*
1111 Abort all threads waiting for a lock. The lock will be upgraded to
1112 TL_WRITE_ONLY to abort any new accesses to the lock
1113 */
1114
thr_abort_locks(THR_LOCK * lock,my_bool upgrade_lock)1115 void thr_abort_locks(THR_LOCK *lock, my_bool upgrade_lock)
1116 {
1117 THR_LOCK_DATA *data;
1118 DBUG_ENTER("thr_abort_locks");
1119 mysql_mutex_lock(&lock->mutex);
1120
1121 for (data=lock->read_wait.data; data ; data=data->next)
1122 {
1123 data->type=TL_UNLOCK; /* Mark killed */
1124 /* It's safe to signal the cond first: we're still holding the mutex. */
1125 mysql_cond_signal(data->cond);
1126 data->cond=0; /* Removed from list */
1127 }
1128 for (data=lock->write_wait.data; data ; data=data->next)
1129 {
1130 data->type=TL_UNLOCK;
1131 mysql_cond_signal(data->cond);
1132 data->cond=0;
1133 }
1134 lock->read_wait.last= &lock->read_wait.data;
1135 lock->write_wait.last= &lock->write_wait.data;
1136 lock->read_wait.data=lock->write_wait.data=0;
1137 if (upgrade_lock && lock->write.data)
1138 lock->write.data->type=TL_WRITE_ONLY;
1139 mysql_mutex_unlock(&lock->mutex);
1140 DBUG_VOID_RETURN;
1141 }
1142
1143
1144 /*
1145 Abort all locks for specific table/thread combination
1146
1147 This is used to abort all locks for a specific thread
1148 */
1149
thr_abort_locks_for_thread(THR_LOCK * lock,my_thread_id thread_id)1150 void thr_abort_locks_for_thread(THR_LOCK *lock, my_thread_id thread_id)
1151 {
1152 THR_LOCK_DATA *data;
1153 DBUG_ENTER("thr_abort_locks_for_thread");
1154
1155 mysql_mutex_lock(&lock->mutex);
1156 for (data= lock->read_wait.data; data ; data= data->next)
1157 {
1158 if (data->owner->thread_id == thread_id) /* purecov: tested */
1159 {
1160 DBUG_PRINT("info",("Aborting read-wait lock"));
1161 data->type= TL_UNLOCK; /* Mark killed */
1162 /* It's safe to signal the cond first: we're still holding the mutex. */
1163 mysql_cond_signal(data->cond);
1164 data->cond= 0; /* Removed from list */
1165
1166 if (((*data->prev)= data->next))
1167 data->next->prev= data->prev;
1168 else
1169 lock->read_wait.last= data->prev;
1170 }
1171 }
1172 for (data= lock->write_wait.data; data ; data= data->next)
1173 {
1174 if (data->owner->thread_id == thread_id) /* purecov: tested */
1175 {
1176 DBUG_PRINT("info",("Aborting write-wait lock"));
1177 data->type= TL_UNLOCK;
1178 mysql_cond_signal(data->cond);
1179 data->cond= 0;
1180
1181 if (((*data->prev)= data->next))
1182 data->next->prev= data->prev;
1183 else
1184 lock->write_wait.last= data->prev;
1185 }
1186 }
1187 wake_up_waiters(lock);
1188 mysql_mutex_unlock(&lock->mutex);
1189 DBUG_VOID_RETURN;
1190 }
1191
1192
1193 /*
1194 Downgrade a WRITE_* to a lower WRITE level
1195 SYNOPSIS
1196 thr_downgrade_write_lock()
1197 in_data Lock data of thread downgrading its lock
1198 new_lock_type New write lock type
1199 RETURN VALUE
1200 NONE
1201 DESCRIPTION
1202 This can be used to downgrade a lock already owned. When the downgrade
1203 occurs also other waiters, both readers and writers can be allowed to
1204 start.
1205 The previous lock is often TL_WRITE_ONLY but can also be
1206 TL_WRITE. The normal downgrade variants are:
1207 TL_WRITE_ONLY => TL_WRITE after a short exclusive lock while holding a
1208 write table lock
1209 TL_WRITE_ONLY => TL_WRITE_ALLOW_WRITE After a short exclusive lock after
1210 already earlier having dongraded lock to TL_WRITE_ALLOW_WRITE
1211 The implementation is conservative and rather don't start rather than
1212 go on unknown paths to start, the common cases are handled.
1213
1214 NOTE:
1215 In its current implementation it is only allowed to downgrade from
1216 TL_WRITE_ONLY. In this case there are no waiters. Thus no wake up
1217 logic is required.
1218 */
1219
thr_downgrade_write_lock(THR_LOCK_DATA * in_data,enum thr_lock_type new_lock_type)1220 void thr_downgrade_write_lock(THR_LOCK_DATA *in_data,
1221 enum thr_lock_type new_lock_type)
1222 {
1223 THR_LOCK *lock=in_data->lock;
1224 #ifndef NDEBUG
1225 enum thr_lock_type old_lock_type= in_data->type;
1226 #endif
1227 DBUG_ENTER("thr_downgrade_write_only_lock");
1228
1229 mysql_mutex_lock(&lock->mutex);
1230 assert(old_lock_type == TL_WRITE_ONLY);
1231 assert(old_lock_type > new_lock_type);
1232 in_data->type= new_lock_type;
1233 check_locks(lock,"after downgrading lock",0);
1234
1235 mysql_mutex_unlock(&lock->mutex);
1236 DBUG_VOID_RETURN;
1237 }
1238
1239
1240 #include <my_sys.h>
1241
thr_print_lock(const char * name,struct st_lock_list * list)1242 static void thr_print_lock(const char* name,struct st_lock_list *list)
1243 {
1244 THR_LOCK_DATA *data,**prev;
1245 uint count=0;
1246
1247 if (list->data)
1248 {
1249 printf("%-10s: ",name);
1250 prev= &list->data;
1251 for (data=list->data; data && count++ < MAX_LOCKS ; data=data->next)
1252 {
1253 printf("0x%lx (%u:%d); ", (ulong) data, data->owner->thread_id,
1254 (int) data->type);
1255 if (data->prev != prev)
1256 printf("\nWarning: prev didn't point at previous lock\n");
1257 prev= &data->next;
1258 }
1259 puts("");
1260 if (prev != list->last)
1261 printf("Warning: last didn't point at last lock\n");
1262 }
1263 }
1264
thr_print_locks(void)1265 void thr_print_locks(void)
1266 {
1267 LIST *list;
1268 uint count=0;
1269
1270 mysql_mutex_lock(&THR_LOCK_lock);
1271 puts("Current locks:");
1272 for (list= thr_lock_thread_list; list && count++ < MAX_THREADS;
1273 list= list_rest(list))
1274 {
1275 THR_LOCK *lock=(THR_LOCK*) list->data;
1276 mysql_mutex_lock(&lock->mutex);
1277 printf("lock: 0x%lx:",(ulong) lock);
1278 if ((lock->write_wait.data || lock->read_wait.data) &&
1279 (! lock->read.data && ! lock->write.data))
1280 printf(" WARNING: ");
1281 if (lock->write.data)
1282 printf(" write");
1283 if (lock->write_wait.data)
1284 printf(" write_wait");
1285 if (lock->read.data)
1286 printf(" read");
1287 if (lock->read_wait.data)
1288 printf(" read_wait");
1289 puts("");
1290 thr_print_lock("write",&lock->write);
1291 thr_print_lock("write_wait",&lock->write_wait);
1292 thr_print_lock("read",&lock->read);
1293 thr_print_lock("read_wait",&lock->read_wait);
1294 mysql_mutex_unlock(&lock->mutex);
1295 puts("");
1296 }
1297 fflush(stdout);
1298 mysql_mutex_unlock(&THR_LOCK_lock);
1299 }
1300
1301
1302 /*****************************************************************************
1303 ** Test of thread locks
1304 ****************************************************************************/
1305
1306 #ifdef MAIN
1307
1308 struct st_test {
1309 uint lock_nr;
1310 enum thr_lock_type lock_type;
1311 };
1312
1313 THR_LOCK locks[5]; /* 4 locks */
1314
1315 struct st_test test_0[] = {{0,TL_READ}}; /* One lock */
1316 struct st_test test_1[] = {{0,TL_READ},{0,TL_WRITE}}; /* Read and write lock of lock 0 */
1317 struct st_test test_2[] = {{1,TL_WRITE},{0,TL_READ},{2,TL_READ}};
1318 struct st_test test_3[] = {{2,TL_WRITE},{1,TL_READ},{0,TL_READ}}; /* Deadlock with test_2 ? */
1319 struct st_test test_4[] = {{0,TL_WRITE},{0,TL_READ},{0,TL_WRITE},{0,TL_READ}};
1320 struct st_test test_5[] = {{0,TL_READ},{1,TL_READ},{2,TL_READ},{3,TL_READ}}; /* Many reads */
1321 struct st_test test_6[] = {{0,TL_WRITE},{1,TL_WRITE},{2,TL_WRITE},{3,TL_WRITE}}; /* Many writes */
1322 struct st_test test_7[] = {{3,TL_READ}};
1323 struct st_test test_8[] = {{1,TL_READ_NO_INSERT},{2,TL_READ_NO_INSERT},{3,TL_READ_NO_INSERT}}; /* Should be quick */
1324 struct st_test test_9[] = {{4,TL_READ_HIGH_PRIORITY}};
1325 struct st_test test_10[] ={{4,TL_WRITE}};
1326 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 */
1327 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}};
1328 struct st_test test_13[] = {{0,TL_WRITE_CONCURRENT_INSERT},{1,TL_READ}};
1329 struct st_test test_14[] = {{0,TL_WRITE_ALLOW_WRITE},{1,TL_READ}};
1330 struct st_test test_15[] = {{0,TL_WRITE_ALLOW_WRITE},{1,TL_WRITE_ALLOW_WRITE}};
1331
1332 struct st_test *tests[] = {test_0,test_1,test_2,test_3,test_4,test_5,test_6,
1333 test_7,test_8,test_9,test_10,test_11,test_12,
1334 test_13,test_14,test_15};
1335 int lock_counts[]= {sizeof(test_0)/sizeof(struct st_test),
1336 sizeof(test_1)/sizeof(struct st_test),
1337 sizeof(test_2)/sizeof(struct st_test),
1338 sizeof(test_3)/sizeof(struct st_test),
1339 sizeof(test_4)/sizeof(struct st_test),
1340 sizeof(test_5)/sizeof(struct st_test),
1341 sizeof(test_6)/sizeof(struct st_test),
1342 sizeof(test_7)/sizeof(struct st_test),
1343 sizeof(test_8)/sizeof(struct st_test),
1344 sizeof(test_9)/sizeof(struct st_test),
1345 sizeof(test_10)/sizeof(struct st_test),
1346 sizeof(test_11)/sizeof(struct st_test),
1347 sizeof(test_12)/sizeof(struct st_test),
1348 sizeof(test_13)/sizeof(struct st_test),
1349 sizeof(test_14)/sizeof(struct st_test),
1350 sizeof(test_15)/sizeof(struct st_test)
1351 };
1352
1353
1354 static mysql_cond_t COND_thread_count;
1355 static mysql_mutex_t LOCK_thread_count;
1356 static uint thread_count;
1357 static ulong sum=0;
1358
1359 #define MAX_LOCK_COUNT 8
1360 #define TEST_TIMEOUT 100000
1361
1362 /* The following functions is for WRITE_CONCURRENT_INSERT */
1363
test_get_status(void * param MY_ATTRIBUTE ((unused)),int concurrent_insert MY_ATTRIBUTE ((unused)))1364 static void test_get_status(void* param MY_ATTRIBUTE((unused)),
1365 int concurrent_insert MY_ATTRIBUTE((unused)))
1366 {
1367 }
1368
test_update_status(void * param MY_ATTRIBUTE ((unused)))1369 static void test_update_status(void* param MY_ATTRIBUTE((unused)))
1370 {
1371 }
1372
test_copy_status(void * to MY_ATTRIBUTE ((unused)),void * from MY_ATTRIBUTE ((unused)))1373 static void test_copy_status(void* to MY_ATTRIBUTE((unused)) ,
1374 void *from MY_ATTRIBUTE((unused)))
1375 {
1376 }
1377
test_check_status(void * param MY_ATTRIBUTE ((unused)))1378 static my_bool test_check_status(void* param MY_ATTRIBUTE((unused)))
1379 {
1380 return 0;
1381 }
1382
1383
test_thread(void * arg)1384 static void *test_thread(void *arg)
1385 {
1386 int i,j,param=*((int*) arg);
1387 THR_LOCK_DATA data[MAX_LOCK_COUNT];
1388 THR_LOCK_INFO lock_info;
1389 THR_LOCK_DATA *multi_locks[MAX_LOCK_COUNT];
1390 my_thread_id id;
1391 mysql_cond_t COND_thr_lock;
1392 memset(&COND_thr_lock, 0, sizeof(COND_thr_lock));
1393
1394 id= param + 1; /* Main thread uses value 0. */
1395 mysql_cond_init(0, &COND_thr_lock);
1396
1397 printf("Thread T@%d started\n", id);
1398 fflush(stdout);
1399
1400 thr_lock_info_init(&lock_info, id, &COND_thr_lock);
1401 for (i=0; i < lock_counts[param] ; i++)
1402 {
1403 thr_lock_data_init(locks+tests[param][i].lock_nr,data+i,NULL);
1404 data[i].m_psi= NULL;
1405 }
1406 for (j=1 ; j < 10 ; j++) /* try locking 10 times */
1407 {
1408 for (i=0; i < lock_counts[param] ; i++)
1409 { /* Init multi locks */
1410 multi_locks[i]= &data[i];
1411 data[i].type= tests[param][i].lock_type;
1412 }
1413 thr_multi_lock(multi_locks, lock_counts[param], &lock_info, TEST_TIMEOUT);
1414 mysql_mutex_lock(&LOCK_thread_count);
1415 {
1416 int tmp=rand() & 7; /* Do something from 0-2 sec */
1417 if (tmp == 0)
1418 sleep(1);
1419 else if (tmp == 1)
1420 sleep(2);
1421 else
1422 {
1423 ulong k;
1424 for (k=0 ; k < (ulong) (tmp-2)*100000L ; k++)
1425 sum+=k;
1426 }
1427 }
1428 mysql_mutex_unlock(&LOCK_thread_count);
1429 thr_multi_unlock(multi_locks,lock_counts[param]);
1430 }
1431
1432 printf("Thread T@%d ended\n", id);
1433 fflush(stdout);
1434 thr_print_locks();
1435 mysql_mutex_lock(&LOCK_thread_count);
1436 thread_count--;
1437 mysql_cond_signal(&COND_thread_count); /* Tell main we are ready */
1438 mysql_mutex_unlock(&LOCK_thread_count);
1439 mysql_cond_destroy(&COND_thr_lock);
1440 free((uchar*) arg);
1441 return 0;
1442 }
1443
1444
main(int argc MY_ATTRIBUTE ((unused)),char ** argv MY_ATTRIBUTE ((unused)))1445 int main(int argc MY_ATTRIBUTE((unused)),char **argv MY_ATTRIBUTE((unused)))
1446 {
1447 my_thread_handle tid;
1448 my_thread_attr_t thr_attr;
1449 int i,*param,error;
1450 MY_INIT(argv[0]);
1451 if (argc > 1 && argv[1][0] == '-' && argv[1][1] == '#')
1452 DBUG_PUSH(argv[1]+2);
1453
1454 printf("Main thread: T@%u\n", 0); /* 0 for main thread, 1+ for test_thread */
1455
1456 if ((error= mysql_cond_init(0, &COND_thread_count)))
1457 {
1458 my_message_stderr(0, "Got error %d from mysql_cond_init", errno);
1459 exit(1);
1460 }
1461 if ((error= mysql_mutex_init(0, &LOCK_thread_count, MY_MUTEX_INIT_FAST)))
1462 {
1463 my_message_stderr(0, "Got error %d from mysql_cond_init", errno);
1464 exit(1);
1465 }
1466
1467 for (i=0 ; i < (int) array_elements(locks) ; i++)
1468 {
1469 thr_lock_init(locks+i);
1470 locks[i].check_status= test_check_status;
1471 locks[i].update_status=test_update_status;
1472 locks[i].copy_status= test_copy_status;
1473 locks[i].get_status= test_get_status;
1474 }
1475 if ((error=my_thread_attr_init(&thr_attr)))
1476 {
1477 my_message_stderr(0, "Got error %d from pthread_attr_init",errno);
1478 exit(1);
1479 }
1480 if ((error= my_thread_attr_setdetachstate(&thr_attr, MY_THREAD_CREATE_DETACHED)))
1481 {
1482 my_message_stderr(0, "Got error %d from "
1483 "my_thread_attr_setdetachstate", errno);
1484 exit(1);
1485 }
1486 if ((error= my_thread_attr_setstacksize(&thr_attr,65536L)))
1487 {
1488 my_message_stderr(0, "Got error %d from "
1489 "my_thread_attr_setstacksize", error);
1490 exit(1);
1491 }
1492 for (i=0 ; i < (int) array_elements(lock_counts) ; i++)
1493 {
1494 param=(int*) malloc(sizeof(int));
1495 *param=i;
1496
1497 if ((error= mysql_mutex_lock(&LOCK_thread_count)))
1498 {
1499 my_message_stderr(0, "Got error %d from mysql_mutex_lock",
1500 errno);
1501 exit(1);
1502 }
1503 if ((error= mysql_thread_create(0,
1504 &tid, &thr_attr, test_thread,
1505 (void*) param)))
1506 {
1507 my_message_stderr(0, "Got error %d from mysql_thread_create",
1508 errno);
1509 mysql_mutex_unlock(&LOCK_thread_count);
1510 exit(1);
1511 }
1512 thread_count++;
1513 mysql_mutex_unlock(&LOCK_thread_count);
1514 }
1515
1516 my_thread_attr_destroy(&thr_attr);
1517 if ((error= mysql_mutex_lock(&LOCK_thread_count)))
1518 my_message_stderr(0, "Got error %d from mysql_mutex_lock", error);
1519 while (thread_count)
1520 {
1521 if ((error= mysql_cond_wait(&COND_thread_count, &LOCK_thread_count)))
1522 my_message_stderr(0, "Got error %d from mysql_cond_wait",
1523 error);
1524 }
1525 if ((error= mysql_mutex_unlock(&LOCK_thread_count)))
1526 my_message_stderr(0, "Got error %d from mysql_mutex_unlock",
1527 error);
1528 for (i=0 ; i < (int) array_elements(locks) ; i++)
1529 thr_lock_delete(locks+i);
1530 #ifdef EXTRA_DEBUG
1531 if (found_errors)
1532 printf("Got %d warnings\n", found_errors);
1533 else
1534 #endif
1535 printf("Test succeeded\n");
1536 return 0;
1537 }
1538
1539 #endif /* MAIN */
1540