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