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