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