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