Esempio n. 1
0
static my_bool wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data,
			     my_bool in_wait_list)
{
  pthread_cond_t *cond=get_cond();
  struct st_my_thread_var *thread_var=my_thread_var;
  int result;

  if (!in_wait_list)
  {
    (*wait->last)=data;				/* Wait for lock */
    data->prev= wait->last;
    wait->last= &data->next;
  }

  /* Set up control struct to allow others to abort locks */
  thread_var->current_mutex= &data->lock->mutex;
  thread_var->current_cond=  cond;

  data->cond=cond;
  while (!thread_var->abort || in_wait_list)
  {
    pthread_cond_wait(cond,&data->lock->mutex);
    if (data->cond != cond)
      break;
  }

  if (data->cond || data->type == TL_UNLOCK)
  {
    if (data->cond)				/* aborted */
    {
      if (((*data->prev)=data->next))		/* remove from wait-list */
	data->next->prev= data->prev;
      else
	wait->last=data->prev;
    }
    data->type=TL_UNLOCK;			/* No lock */
    result=1;					/* Didn't get lock */
    check_locks(data->lock,"failed wait_for_lock",0);
  }
  else
  {
    result=0;
    statistic_increment(locks_waited, &THR_LOCK_lock);
    if (data->lock->get_status)
      (*data->lock->get_status)(data->status_param);
    check_locks(data->lock,"got wait_for_lock",0);
  }
  pthread_mutex_unlock(&data->lock->mutex);

  /* The following must be done after unlock of lock->mutex */
  pthread_mutex_lock(&thread_var->mutex);
  thread_var->current_mutex= 0;
  thread_var->current_cond=  0;
  pthread_mutex_unlock(&thread_var->mutex);
  return result;
}
Esempio n. 2
0
enum enum_thr_lock_result
thr_lock(THR_LOCK_DATA *data, THR_LOCK_OWNER *owner,
         enum thr_lock_type lock_type)
{
  THR_LOCK *lock=data->lock;
  enum enum_thr_lock_result result= THR_LOCK_SUCCESS;
  struct st_lock_list *wait_queue;
  THR_LOCK_DATA *lock_owner;
  DBUG_ENTER("thr_lock");

  data->next=0;
  data->cond=0;					/* safety */
  data->type=lock_type;
  data->owner= owner;                           /* Must be reset ! */
  VOID(pthread_mutex_lock(&lock->mutex));
  DBUG_PRINT("lock",("data: 0x%lx  thread: %ld  lock: 0x%lx  type: %d",
                     (long) data, data->owner->info->thread_id,
                     (long) lock, (int) lock_type));
  check_locks(lock,(uint) lock_type <= (uint) TL_READ_NO_INSERT ?
	      "enter read_lock" : "enter write_lock",0);
  if ((int) lock_type <= (int) TL_READ_NO_INSERT)
  {
    /* Request for READ lock */
    if (lock->write.data)
    {
      /* We can allow a read lock even if there is already a write lock
	 on the table in one the following cases:
	 - This thread alread have a write lock on the table
	 - The write lock is TL_WRITE_ALLOW_READ or TL_WRITE_DELAYED
           and the read lock is TL_READ_HIGH_PRIORITY or TL_READ
         - The write lock is TL_WRITE_CONCURRENT_INSERT or TL_WRITE_ALLOW_WRITE
	   and the read lock is not TL_READ_NO_INSERT
      */

      DBUG_PRINT("lock",("write locked by thread: %ld",
			 lock->write.data->owner->info->thread_id));
      if (thr_lock_owner_equal(data->owner, lock->write.data->owner) ||
	  (lock->write.data->type <= TL_WRITE_DELAYED &&
	   (((int) lock_type <= (int) TL_READ_HIGH_PRIORITY) ||
	    (lock->write.data->type != TL_WRITE_CONCURRENT_INSERT &&
	     lock->write.data->type != TL_WRITE_ALLOW_READ))))
      {						/* Already got a write lock */
	(*lock->read.last)=data;		/* Add to running FIFO */
	data->prev=lock->read.last;
	lock->read.last= &data->next;
	if (lock_type == TL_READ_NO_INSERT)
	  lock->read_no_write_count++;
	check_locks(lock,"read lock with old write lock",0);
	if (lock->get_status)
	  (*lock->get_status)(data->status_param, 0);
	statistic_increment(locks_immediate,&THR_LOCK_lock);
	goto end;
      }
      if (lock->write.data->type == TL_WRITE_ONLY)
      {
	/* We are not allowed to get a READ lock in this case */
	data->type=TL_UNLOCK;
        result= THR_LOCK_ABORTED;               /* Can't wait for this one */
	goto end;
      }
    }
    else if (!lock->write_wait.data ||
	     lock->write_wait.data->type <= TL_WRITE_LOW_PRIORITY ||
	     lock_type == TL_READ_HIGH_PRIORITY ||
	     have_old_read_lock(lock->read.data, data->owner))
    {						/* No important write-locks */
      (*lock->read.last)=data;			/* Add to running FIFO */
      data->prev=lock->read.last;
      lock->read.last= &data->next;
      if (lock->get_status)
	(*lock->get_status)(data->status_param, 0);
      if (lock_type == TL_READ_NO_INSERT)
	lock->read_no_write_count++;
      check_locks(lock,"read lock with no write locks",0);
      statistic_increment(locks_immediate,&THR_LOCK_lock);
      goto end;
    }
    /*
      We're here if there is an active write lock or no write
      lock but a high priority write waiting in the write_wait queue.
      In the latter case we should yield the lock to the writer.
    */
    wait_queue= &lock->read_wait;
  }
  else						/* Request for WRITE lock */
  {
    if (lock_type == TL_WRITE_DELAYED)
    {
      if (lock->write.data && lock->write.data->type == TL_WRITE_ONLY)
      {
	data->type=TL_UNLOCK;
        result= THR_LOCK_ABORTED;               /* Can't wait for this one */
	goto end;
      }
      /*
	if there is a TL_WRITE_ALLOW_READ lock, we have to wait for a lock
	(TL_WRITE_ALLOW_READ is used for ALTER TABLE in MySQL)
      */
      if ((!lock->write.data ||
	   lock->write.data->type != TL_WRITE_ALLOW_READ) &&
	  !have_specific_lock(lock->write_wait.data,TL_WRITE_ALLOW_READ) &&
	  (lock->write.data || lock->read.data))
      {
	/* Add delayed write lock to write_wait queue, and return at once */
	(*lock->write_wait.last)=data;
	data->prev=lock->write_wait.last;
	lock->write_wait.last= &data->next;
	data->cond=get_cond();
        /*
          We don't have to do get_status here as we will do it when we change
          the delayed lock to a real write lock
        */
	statistic_increment(locks_immediate,&THR_LOCK_lock);
	goto end;
      }
    }
    else if (lock_type == TL_WRITE_CONCURRENT_INSERT && ! lock->check_status)
      data->type=lock_type= thr_upgraded_concurrent_insert_lock;

    if (lock->write.data)			/* If there is a write lock */
    {
      if (lock->write.data->type == TL_WRITE_ONLY)
      {
	/* We are not allowed to get a lock in this case */
	data->type=TL_UNLOCK;
        result= THR_LOCK_ABORTED;               /* Can't wait for this one */
	goto end;
      }

      /*
	The following test will not work if the old lock was a
	TL_WRITE_ALLOW_WRITE, TL_WRITE_ALLOW_READ or TL_WRITE_DELAYED in
	the same thread, but this will never happen within MySQL.
      */
      if (thr_lock_owner_equal(data->owner, lock->write.data->owner) ||
	  (lock_type == TL_WRITE_ALLOW_WRITE &&
	   !lock->write_wait.data &&
	   lock->write.data->type == TL_WRITE_ALLOW_WRITE))
      {
	/*
          We have already got a write lock or all locks are
          TL_WRITE_ALLOW_WRITE
        */
        DBUG_PRINT("info", ("write_wait.data: 0x%lx  old_type: %d",
                            (ulong) lock->write_wait.data,
                            lock->write.data->type));

	(*lock->write.last)=data;	/* Add to running fifo */
	data->prev=lock->write.last;
	lock->write.last= &data->next;
	check_locks(lock,"second write lock",0);
	if (data->lock->get_status)
	  (*data->lock->get_status)(data->status_param, 0);
	statistic_increment(locks_immediate,&THR_LOCK_lock);
	goto end;
      }
      DBUG_PRINT("lock",("write locked by thread: %ld",
			 lock->write.data->owner->info->thread_id));
    }
    else
    {
      DBUG_PRINT("info", ("write_wait.data: 0x%lx",
                          (ulong) lock->write_wait.data));
      if (!lock->write_wait.data)
      {						/* no scheduled write locks */
        my_bool concurrent_insert= 0;
	if (lock_type == TL_WRITE_CONCURRENT_INSERT)
        {
          concurrent_insert= 1;
          if ((*lock->check_status)(data->status_param))
          {
            concurrent_insert= 0;
            data->type=lock_type= thr_upgraded_concurrent_insert_lock;
          }
        }

	if (!lock->read.data ||
	    (lock_type <= TL_WRITE_DELAYED &&
	     ((lock_type != TL_WRITE_CONCURRENT_INSERT &&
	       lock_type != TL_WRITE_ALLOW_WRITE) ||
	      !lock->read_no_write_count)))
	{
	  (*lock->write.last)=data;		/* Add as current write lock */
	  data->prev=lock->write.last;
	  lock->write.last= &data->next;
	  if (data->lock->get_status)
	    (*data->lock->get_status)(data->status_param, concurrent_insert);
	  check_locks(lock,"only write lock",0);
	  statistic_increment(locks_immediate,&THR_LOCK_lock);
	  goto end;
	}
      }
      DBUG_PRINT("lock",("write locked by thread: %ld  type: %d",
			 lock->read.data->owner->info->thread_id, data->type));
    }
    wait_queue= &lock->write_wait;
  }
  /*
    Try to detect a trivial deadlock when using cursors: attempt to
    lock a table that is already locked by an open cursor within the
    same connection. lock_owner can be zero if we succumbed to a high
    priority writer in the write_wait queue.
  */
  lock_owner= lock->read.data ? lock->read.data : lock->write.data;
  if (lock_owner && lock_owner->owner->info == owner->info)
  {
    result= THR_LOCK_DEADLOCK;
    goto end;
  }
  /* Can't get lock yet;  Wait for it */
  DBUG_RETURN(wait_for_lock(wait_queue, data, 0));
end:
  pthread_mutex_unlock(&lock->mutex);
  DBUG_RETURN(result);
}
Esempio n. 3
0
static enum enum_thr_lock_result
wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data,
              my_bool in_wait_list)
{
  struct st_my_thread_var *thread_var= my_thread_var;
  pthread_cond_t *cond= &thread_var->suspend;
  struct timespec wait_timeout;
  enum enum_thr_lock_result result= THR_LOCK_ABORTED;
  my_bool can_deadlock= test(data->owner->info->n_cursors);

  if (!in_wait_list)
  {
    (*wait->last)=data;				/* Wait for lock */
    data->prev= wait->last;
    wait->last= &data->next;
  }

  /* Set up control struct to allow others to abort locks */
  thread_var->current_mutex= &data->lock->mutex;
  thread_var->current_cond=  cond;
  data->cond= cond;

  if (can_deadlock)
    set_timespec(wait_timeout, table_lock_wait_timeout);
  while (!thread_var->abort || in_wait_list)
  {
    int rc= (can_deadlock ?
             pthread_cond_timedwait(cond, &data->lock->mutex,
                                    &wait_timeout) :
             pthread_cond_wait(cond, &data->lock->mutex));
    /*
      We must break the wait if one of the following occurs:
      - the connection has been aborted (!thread_var->abort), but
        this is not a delayed insert thread (in_wait_list). For a delayed
        insert thread the proper action at shutdown is, apparently, to
        acquire the lock and complete the insert.
      - the lock has been granted (data->cond is set to NULL by the granter),
        or the waiting has been aborted (additionally data->type is set to
        TL_UNLOCK).
      - the wait has timed out (rc == ETIMEDOUT)
      Order of checks below is important to not report about timeout
      if the predicate is true.
    */
    if (data->cond == 0)
      break;
    if (rc == ETIMEDOUT || rc == ETIME)
    {
      result= THR_LOCK_WAIT_TIMEOUT;
      break;
    }
  }

  if (data->cond || data->type == TL_UNLOCK)
  {
    if (data->cond)                             /* aborted or timed out */
    {
      if (((*data->prev)=data->next))		/* remove from wait-list */
	data->next->prev= data->prev;
      else
	wait->last=data->prev;
      data->type= TL_UNLOCK;                    /* No lock */
      check_locks(data->lock, "killed or timed out wait_for_lock", 1);
      wake_up_waiters(data->lock);
    }
    else
    {
      check_locks(data->lock, "aborted wait_for_lock", 0);
    }
  }
  else
  {
    result= THR_LOCK_SUCCESS;
    statistic_increment(locks_waited, &THR_LOCK_lock);
    if (data->lock->get_status)
      (*data->lock->get_status)(data->status_param, 0);
    check_locks(data->lock,"got wait_for_lock",0);
  }
  pthread_mutex_unlock(&data->lock->mutex);

  /* The following must be done after unlock of lock->mutex */
  pthread_mutex_lock(&thread_var->mutex);
  thread_var->current_mutex= 0;
  thread_var->current_cond=  0;
  pthread_mutex_unlock(&thread_var->mutex);
  return result;
}
Esempio n. 4
0
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)
{
  THR_LOCK *lock=data->lock;
  enum enum_thr_lock_result result= THR_LOCK_SUCCESS;
  struct st_lock_list *wait_queue;
  DBUG_ENTER("thr_lock");

  data->next=0;
  data->cond=0;					/* safety */
  data->type=lock_type;
  data->owner= owner;                           /* Must be reset ! */
  mysql_mutex_lock(&lock->mutex);
  DBUG_PRINT("lock",("data: 0x%lx  thread: 0x%lx  lock: 0x%lx  type: %d",
                     (long) data, data->owner->thread_id,
                     (long) lock, (int) lock_type));
  check_locks(lock,(uint) lock_type <= (uint) TL_READ_NO_INSERT ?
	      "enter read_lock" : "enter write_lock",0);
  if ((int) lock_type <= (int) TL_READ_NO_INSERT)
  {
    /* Request for READ lock */
    if (lock->write.data)
    {
      /*
        We can allow a read lock even if there is already a
        write lock on the table if they are owned by the same
        thread or if they satisfy the following lock
        compatibility matrix:

           Request
          /-------
         H|++++  WRITE_ALLOW_WRITE
         e|+++-  WRITE_CONCURRENT_INSERT
         l|++++  WRITE_DELAYED
         d ||||
           |||\= READ_NO_INSERT
           ||\ = READ_HIGH_PRIORITY
           |\  = READ_WITH_SHARED_LOCKS
           \   = READ
          

        + = Request can be satisified.
        - = Request cannot be satisified.

        READ_NO_INSERT and WRITE_ALLOW_WRITE should in principle
        be incompatible. However this will cause starvation of
        LOCK TABLE READ in InnoDB under high write load.
        See Bug#42147 for more information.
      */

      DBUG_PRINT("lock",("write locked 1 by thread: 0x%lx",
			 lock->write.data->owner->thread_id));
      if (thr_lock_owner_equal(data->owner, lock->write.data->owner) ||
	  (lock->write.data->type <= TL_WRITE_DELAYED &&
	   (((int) lock_type <= (int) TL_READ_HIGH_PRIORITY) ||
	    (lock->write.data->type != TL_WRITE_CONCURRENT_INSERT))))
      {						/* Already got a write lock */
	(*lock->read.last)=data;		/* Add to running FIFO */
	data->prev=lock->read.last;
	lock->read.last= &data->next;
	if (lock_type == TL_READ_NO_INSERT)
	  lock->read_no_write_count++;
	check_locks(lock,"read lock with old write lock",0);
	if (lock->get_status)
	  (*lock->get_status)(data->status_param, 0);
	statistic_increment(locks_immediate,&THR_LOCK_lock);
	goto end;
      }
      if (lock->write.data->type == TL_WRITE_ONLY)
      {
	/* We are not allowed to get a READ lock in this case */
	data->type=TL_UNLOCK;
        result= THR_LOCK_ABORTED;               /* Can't wait for this one */
	goto end;
      }
    }
    else if (!lock->write_wait.data ||
	     lock->write_wait.data->type <= TL_WRITE_LOW_PRIORITY ||
	     lock_type == TL_READ_HIGH_PRIORITY ||
	     has_old_lock(lock->read.data, data->owner)) /* Has old read lock */
    {						/* No important write-locks */
      (*lock->read.last)=data;			/* Add to running FIFO */
      data->prev=lock->read.last;
      lock->read.last= &data->next;
      if (lock->get_status)
	(*lock->get_status)(data->status_param, 0);
      if (lock_type == TL_READ_NO_INSERT)
	lock->read_no_write_count++;
      check_locks(lock,"read lock with no write locks",0);
      statistic_increment(locks_immediate,&THR_LOCK_lock);
      goto end;
    }
    /*
      We're here if there is an active write lock or no write
      lock but a high priority write waiting in the write_wait queue.
      In the latter case we should yield the lock to the writer.
    */
    wait_queue= &lock->read_wait;
  }
  else						/* Request for WRITE lock */
  {
    if (lock_type == TL_WRITE_DELAYED)
    {
      if (lock->write.data && lock->write.data->type == TL_WRITE_ONLY)
      {
	data->type=TL_UNLOCK;
        result= THR_LOCK_ABORTED;               /* Can't wait for this one */
	goto end;
      }
      if (lock->write.data || lock->read.data)
      {
	/* Add delayed write lock to write_wait queue, and return at once */
	(*lock->write_wait.last)=data;
	data->prev=lock->write_wait.last;
	lock->write_wait.last= &data->next;
	data->cond=get_cond();
        /*
          We don't have to do get_status here as we will do it when we change
          the delayed lock to a real write lock
        */
	statistic_increment(locks_immediate,&THR_LOCK_lock);
	goto end;
      }
    }
    else if (lock_type == TL_WRITE_CONCURRENT_INSERT && ! lock->check_status)
      data->type=lock_type= thr_upgraded_concurrent_insert_lock;

    if (lock->write.data)			/* If there is a write lock */
    {
      if (lock->write.data->type == TL_WRITE_ONLY)
      {
        /* purecov: begin tested */
        /* Allow lock owner to bypass TL_WRITE_ONLY. */
        if (!thr_lock_owner_equal(data->owner, lock->write.data->owner))
        {
          /* We are not allowed to get a lock in this case */
          data->type=TL_UNLOCK;
          result= THR_LOCK_ABORTED;               /* Can't wait for this one */
          goto end;
        }
        /* purecov: end */
      }

      /*
        The idea is to allow us to get a lock at once if we already have
        a write lock or if there is no pending write locks and if all
        write locks are of TL_WRITE_ALLOW_WRITE type.

        Note that, since lock requests for the same table are sorted in
        such way that requests with higher thr_lock_type value come first
        (with one exception (*)), lock being requested usually (**) has
        equal or "weaker" type than one which thread might have already
        acquired.
        *)  The only exception to this rule is case when type of old lock
            is TL_WRITE_LOW_PRIORITY and type of new lock is changed inside
            of thr_lock() from TL_WRITE_CONCURRENT_INSERT to TL_WRITE since
            engine turns out to be not supporting concurrent inserts.
            Note that since TL_WRITE has the same compatibility rules as
            TL_WRITE_LOW_PRIORITY (their only difference is priority),
            it is OK to grant new lock without additional checks in such
            situation.
        **) The exceptions are situations when:
            - when old lock type is TL_WRITE_DELAYED
            But these should never happen within MySQL.
        Therefore it is OK to allow acquiring write lock on the table if
        this thread already holds some write lock on it.

        (INSERT INTO t1 VALUES (f1()), where f1() is stored function which
        tries to update t1, is an example of statement which requests two
        different types of write lock on the same table).
      */
      DBUG_ASSERT(! has_old_lock(lock->write.data, data->owner) ||
                  ((lock_type <= lock->write.data->type ||
                    (lock_type == TL_WRITE &&
                     lock->write.data->type == TL_WRITE_LOW_PRIORITY)) &&
                   lock->write.data->type != TL_WRITE_DELAYED));

      if ((lock_type == TL_WRITE_ALLOW_WRITE &&
           ! lock->write_wait.data &&
           lock->write.data->type == TL_WRITE_ALLOW_WRITE) ||
          has_old_lock(lock->write.data, data->owner))
      {
	/*
          We have already got a write lock or all locks are
          TL_WRITE_ALLOW_WRITE
        */
        DBUG_PRINT("info", ("write_wait.data: 0x%lx  old_type: %d",
                            (ulong) lock->write_wait.data,
                            lock->write.data->type));

	(*lock->write.last)=data;	/* Add to running fifo */
	data->prev=lock->write.last;
	lock->write.last= &data->next;
	check_locks(lock,"second write lock",0);
	if (data->lock->get_status)
	  (*data->lock->get_status)(data->status_param, 0);
	statistic_increment(locks_immediate,&THR_LOCK_lock);
	goto end;
      }
      DBUG_PRINT("lock",("write locked 2 by thread: 0x%lx",
			 lock->write.data->owner->thread_id));
    }
    else
    {
      DBUG_PRINT("info", ("write_wait.data: 0x%lx",
                          (ulong) lock->write_wait.data));
      if (!lock->write_wait.data)
      {						/* no scheduled write locks */
        my_bool concurrent_insert= 0;
	if (lock_type == TL_WRITE_CONCURRENT_INSERT)
        {
          concurrent_insert= 1;
          if ((*lock->check_status)(data->status_param))
          {
            concurrent_insert= 0;
            data->type=lock_type= thr_upgraded_concurrent_insert_lock;
          }
        }

	if (!lock->read.data ||
	    (lock_type <= TL_WRITE_DELAYED &&
	     ((lock_type != TL_WRITE_CONCURRENT_INSERT &&
	       lock_type != TL_WRITE_ALLOW_WRITE) ||
	      !lock->read_no_write_count)))
	{
	  (*lock->write.last)=data;		/* Add as current write lock */
	  data->prev=lock->write.last;
	  lock->write.last= &data->next;
	  if (data->lock->get_status)
	    (*data->lock->get_status)(data->status_param, concurrent_insert);
	  check_locks(lock,"only write lock",0);
	  statistic_increment(locks_immediate,&THR_LOCK_lock);
	  goto end;
	}
      }
      DBUG_PRINT("lock",("write locked 3 by thread: 0x%lx  type: %d",
			 lock->read.data->owner->thread_id, data->type));
    }
    wait_queue= &lock->write_wait;
  }
  /* Can't get lock yet;  Wait for it */
  DBUG_RETURN(wait_for_lock(wait_queue, data, 0, lock_wait_timeout));
end:
  mysql_mutex_unlock(&lock->mutex);
  DBUG_RETURN(result);
}
Esempio n. 5
0
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)
{
  struct st_my_thread_var *thread_var= my_thread_var;
  mysql_cond_t *cond= &thread_var->suspend;
  struct timespec wait_timeout;
  enum enum_thr_lock_result result= THR_LOCK_ABORTED;
  const char *old_proc_info;
  DBUG_ENTER("wait_for_lock");

  /*
    One can use this to signal when a thread is going to wait for a lock.
    See debug_sync.cc.

    Beware of waiting for a signal here. The lock has aquired its mutex.
    While waiting on a signal here, the locking thread could not aquire
    the mutex to release the lock. One could lock up the table
    completely.

    In detail it works so: When thr_lock() tries to acquire a table
    lock, it locks the lock->mutex, checks if it can have the lock, and
    if not, it calls wait_for_lock(). Here it unlocks the table lock
    while waiting on a condition. The sync point is located before this
    wait for condition. If we have a waiting action here, we hold the
    the table locks mutex all the time. Any attempt to look at the table
    lock by another thread blocks it immediately on lock->mutex. This
    can easily become an unexpected and unobvious blockage. So be
    warned: Do not request a WAIT_FOR action for the 'wait_for_lock'
    sync point unless you really know what you do.
  */
  DEBUG_SYNC_C("wait_for_lock");

  if (!in_wait_list)
  {
    (*wait->last)=data;				/* Wait for lock */
    data->prev= wait->last;
    wait->last= &data->next;
  }

  statistic_increment(locks_waited, &THR_LOCK_lock);

  /* Set up control struct to allow others to abort locks */
  thread_var->current_mutex= &data->lock->mutex;
  thread_var->current_cond=  cond;
  data->cond= cond;

  old_proc_info= proc_info_hook(NULL, "Waiting for table level lock",
                                __func__, __FILE__, __LINE__);

  /*
    Since before_lock_wait potentially can create more threads to
    scheduler work for, we don't want to call the before_lock_wait
    callback unless it will really start to wait.

    For similar reasons, we do not want to call before_lock_wait and
    after_lock_wait for each lap around the loop, so we restrict
    ourselves to call it before_lock_wait once before starting to wait
    and once after the thread has exited the wait loop.
   */
  if ((!thread_var->abort || in_wait_list) && before_lock_wait)
    (*before_lock_wait)();

  set_timespec(wait_timeout, lock_wait_timeout);
  while (!thread_var->abort || in_wait_list)
  {
    int rc= mysql_cond_timedwait(cond, &data->lock->mutex, &wait_timeout);
    /*
      We must break the wait if one of the following occurs:
      - the connection has been aborted (!thread_var->abort), but
        this is not a delayed insert thread (in_wait_list). For a delayed
        insert thread the proper action at shutdown is, apparently, to
        acquire the lock and complete the insert.
      - the lock has been granted (data->cond is set to NULL by the granter),
        or the waiting has been aborted (additionally data->type is set to
        TL_UNLOCK).
      - the wait has timed out (rc == ETIMEDOUT)
      Order of checks below is important to not report about timeout
      if the predicate is true.
    */
    if (data->cond == 0)
    {
      DBUG_PRINT("thr_lock", ("lock granted/aborted"));
      break;
    }
    if (rc == ETIMEDOUT || rc == ETIME)
    {
      /* purecov: begin inspected */
      DBUG_PRINT("thr_lock", ("lock timed out"));
      result= THR_LOCK_WAIT_TIMEOUT;
      break;
      /* purecov: end */
    }
  }

  /*
    We call the after_lock_wait callback once the wait loop has
    finished.
   */
  if (after_lock_wait)
    (*after_lock_wait)();

  DBUG_PRINT("thr_lock", ("aborted: %d  in_wait_list: %d",
                          thread_var->abort, in_wait_list));

  if (data->cond || data->type == TL_UNLOCK)
  {
    if (data->cond)                             /* aborted or timed out */
    {
      if (((*data->prev)=data->next))		/* remove from wait-list */
	data->next->prev= data->prev;
      else
	wait->last=data->prev;
      data->type= TL_UNLOCK;                    /* No lock */
      check_locks(data->lock, "killed or timed out wait_for_lock", 1);
      wake_up_waiters(data->lock);
    }
    else
    {
      DBUG_PRINT("thr_lock", ("lock aborted"));
      check_locks(data->lock, "aborted wait_for_lock", 0);
    }
  }
  else
  {
    result= THR_LOCK_SUCCESS;
    if (data->lock->get_status)
      (*data->lock->get_status)(data->status_param, 0);
    check_locks(data->lock,"got wait_for_lock",0);
  }
  mysql_mutex_unlock(&data->lock->mutex);

  /* The following must be done after unlock of lock->mutex */
  mysql_mutex_lock(&thread_var->mutex);
  thread_var->current_mutex= 0;
  thread_var->current_cond=  0;
  mysql_mutex_unlock(&thread_var->mutex);

  proc_info_hook(NULL, old_proc_info, __func__, __FILE__, __LINE__);

  DBUG_RETURN(result);
}
Esempio n. 6
0
int thr_lock(THR_LOCK_DATA *data,enum thr_lock_type lock_type)
{
  THR_LOCK *lock=data->lock;
  int result=0;
  DBUG_ENTER("thr_lock");

  data->next=0;
  data->cond=0;					/* safety */
  data->type=lock_type;
  data->thread=pthread_self();			/* Must be reset ! */
  data->thread_id=my_thread_id();		/* Must be reset ! */
  VOID(pthread_mutex_lock(&lock->mutex));
  DBUG_PRINT("lock",("data: %lx  thread: %ld  lock: %lx  type: %d",
		      data,data->thread_id,lock,(int) lock_type));
  check_locks(lock,(uint) lock_type <= (uint) TL_READ_NO_INSERT ?
	      "enter read_lock" : "enter write_lock",0);
  if ((int) lock_type <= (int) TL_READ_NO_INSERT)
  {
    /* Request for READ lock */
    if (lock->write.data)
    {
      /* We can allow a read lock even if there is already a write lock
	 on the table in one the following cases:
	 - This thread alread have a write lock on the table
	 - The write lock is TL_WRITE_ALLOW_READ or TL_WRITE_DELAYED
           and the read lock is TL_READ_HIGH_PRIORITY or TL_READ
         - The write lock is TL_WRITE_CONCURRENT_INSERT or TL_WRITE_ALLOW_WRITE
	   and the read lock is not TL_READ_NO_INSERT
      */

      DBUG_PRINT("lock",("write locked by thread: %ld",
			 lock->write.data->thread_id));
      if (pthread_equal(data->thread,lock->write.data->thread) ||
	  (lock->write.data->type <= TL_WRITE_DELAYED &&
	   (((int) lock_type <= (int) TL_READ_HIGH_PRIORITY) ||
	    (lock->write.data->type != TL_WRITE_CONCURRENT_INSERT &&
	     lock->write.data->type != TL_WRITE_ALLOW_READ))))
      {						/* Already got a write lock */
	(*lock->read.last)=data;		/* Add to running FIFO */
	data->prev=lock->read.last;
	lock->read.last= &data->next;
	if ((int) lock_type == (int) TL_READ_NO_INSERT)
	  lock->read_no_write_count++;
	check_locks(lock,"read lock with old write lock",0);
	if (lock->get_status)
	  (*lock->get_status)(data->status_param);
	statistic_increment(locks_immediate,&THR_LOCK_lock);
	goto end;
      }
      if (lock->write.data->type == TL_WRITE_ONLY)
      {
	/* We are not allowed to get a READ lock in this case */
	data->type=TL_UNLOCK;
	result=1;				/* Can't wait for this one */
	goto end;
      }
    }
    else if (!lock->write_wait.data ||
	     lock->write_wait.data->type <= TL_WRITE_LOW_PRIORITY ||
	     lock_type == TL_READ_HIGH_PRIORITY ||
	     have_old_read_lock(lock->read.data,data->thread))
    {						/* No important write-locks */
      (*lock->read.last)=data;			/* Add to running FIFO */
      data->prev=lock->read.last;
      lock->read.last= &data->next;
      if (lock->get_status)
	(*lock->get_status)(data->status_param);
      if ((int) lock_type == (int) TL_READ_NO_INSERT)
	lock->read_no_write_count++;
      check_locks(lock,"read lock with no write locks",0);
      statistic_increment(locks_immediate,&THR_LOCK_lock);
      goto end;
    }
    /* Can't get lock yet;  Wait for it */
    DBUG_RETURN(wait_for_lock(&lock->read_wait,data,0));
  }
  else						/* Request for WRITE lock */
  {
    if (lock_type == TL_WRITE_DELAYED)
    {
      if (lock->write.data && lock->write.data->type == TL_WRITE_ONLY)
      {
	data->type=TL_UNLOCK;
	result=1;				/* Can't wait for this one */
	goto end;
      }
      /*
	if there is a TL_WRITE_ALLOW_READ lock, we have to wait for a lock
	(TL_WRITE_ALLOW_READ is used for ALTER TABLE in MySQL)
      */
      if ((!lock->write.data ||
	   lock->write.data->type != TL_WRITE_ALLOW_READ) &&
	  !have_specific_lock(lock->write_wait.data,TL_WRITE_ALLOW_READ) &&
	  (lock->write.data || lock->read.data))
      {
	/* Add delayed write lock to write_wait queue, and return at once */
	(*lock->write_wait.last)=data;
	data->prev=lock->write_wait.last;
	lock->write_wait.last= &data->next;
	data->cond=get_cond();
	if (lock->get_status)
	  (*lock->get_status)(data->status_param);
	statistic_increment(locks_immediate,&THR_LOCK_lock);
	goto end;
      }
    }
    else if (lock_type == TL_WRITE_CONCURRENT_INSERT && ! lock->check_status)
      data->type=lock_type= thr_upgraded_concurrent_insert_lock;

    if (lock->write.data)			/* If there is a write lock */
    {
      if (lock->write.data->type == TL_WRITE_ONLY)
      {
	/* We are not allowed to get a lock in this case */
	data->type=TL_UNLOCK;
	result=1;				/* Can't wait for this one */
	goto end;
      }

      /*
	The following test will not work if the old lock was a
	TL_WRITE_ALLOW_WRITE, TL_WRITE_ALLOW_READ or TL_WRITE_DELAYED in
	the same thread, but this will never happen within MySQL.
      */
      if (pthread_equal(data->thread,lock->write.data->thread) ||
	  (lock_type == TL_WRITE_ALLOW_WRITE &&
	   !lock->write_wait.data &&
	   lock->write.data->type == TL_WRITE_ALLOW_WRITE))
      {
	/* We have already got a write lock or all locks are
	   TL_WRITE_ALLOW_WRITE */
	(*lock->write.last)=data;	/* Add to running fifo */
	data->prev=lock->write.last;
	lock->write.last= &data->next;
	check_locks(lock,"second write lock",0);
	if (data->lock->get_status)
	  (*data->lock->get_status)(data->status_param);
	statistic_increment(locks_immediate,&THR_LOCK_lock);
	goto end;
      }
      DBUG_PRINT("lock",("write locked by thread: %ld",
			 lock->write.data->thread_id));
    }
    else
    {
      if (!lock->write_wait.data)
      {						/* no scheduled write locks */
	if (lock_type == TL_WRITE_CONCURRENT_INSERT &&
	    (*lock->check_status)(data->status_param))
	  data->type=lock_type= thr_upgraded_concurrent_insert_lock;

	if (!lock->read.data ||
	    (lock_type <= TL_WRITE_DELAYED &&
	     ((lock_type != TL_WRITE_CONCURRENT_INSERT &&
	       lock_type != TL_WRITE_ALLOW_WRITE) ||
	      !lock->read_no_write_count)))
	{
	  (*lock->write.last)=data;		/* Add as current write lock */
	  data->prev=lock->write.last;
	  lock->write.last= &data->next;
	  if (data->lock->get_status)
	    (*data->lock->get_status)(data->status_param);
	  check_locks(lock,"only write lock",0);
	  statistic_increment(locks_immediate,&THR_LOCK_lock);
	  goto end;
	}
      }
      DBUG_PRINT("lock",("write locked by thread: %ld, type: %ld",
			 lock->read.data->thread_id,data->type));
    }
    DBUG_RETURN(wait_for_lock(&lock->write_wait,data,0));
  }
end:
  pthread_mutex_unlock(&lock->mutex);
  DBUG_RETURN(result);
}