enum enum_thr_lock_result thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_INFO *owner, ulong lock_wait_timeout, struct st_my_thread_var *thread_var) { THR_LOCK_DATA **pos,**end; DBUG_ENTER("thr_multi_lock"); DBUG_PRINT("lock",("data: 0x%lx count: %d", (long) data, count)); if (count > 1) sort_locks(data,count); /* lock everything */ for (pos=data,end=data+count; pos < end ; pos++) { enum enum_thr_lock_result result= thr_lock(*pos, owner, (*pos)->type, lock_wait_timeout, thread_var); if (result != THR_LOCK_SUCCESS) { /* Aborted */ thr_multi_unlock(data,(uint) (pos-data)); DBUG_RETURN(result); } DEBUG_SYNC_C("thr_multi_lock_after_thr_lock"); #ifdef MAIN printf("Thread: T@%u Got lock: 0x%lx type: %d\n", pos[0]->owner->thread_id, (long) pos[0]->lock, pos[0]->type); fflush(stdout); #endif } thr_lock_merge_status(data, count); DBUG_RETURN(THR_LOCK_SUCCESS); }
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) { struct timespec wait_timeout; enum enum_thr_lock_result result= THR_LOCK_ABORTED; PSI_stage_info old_stage; 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; } locks_waited++; /* Set up control struct to allow others to abort locks */ data->cond= &thread_var->suspend; enter_cond_hook(NULL, data->cond, &data->lock->mutex, &stage_waiting_for_table_level_lock, &old_stage, __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(data->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), - 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); exit_cond_hook(NULL, &old_stage, __func__, __FILE__, __LINE__); DBUG_RETURN(result); }
int mi_assign_to_key_cache(MI_INFO *info, ulonglong key_map __attribute__((unused)), KEY_CACHE *new_key_cache) { int error= 0; MYISAM_SHARE* share= info->s; KEY_CACHE *old_key_cache= share->key_cache; DBUG_ENTER("mi_assign_to_key_cache"); DBUG_PRINT("enter",("old_key_cache_handle: %p new_key_cache_handle: %p", old_key_cache, new_key_cache)); /* Skip operation if we didn't change key cache. This can happen if we call this for all open instances of the same table */ if (old_key_cache == new_key_cache) DBUG_RETURN(0); /* First flush all blocks for the table in the old key cache. This is to ensure that the disk is consistent with the data pages in memory (which may not be the case if the table uses delayed_key_write) Note that some other read thread may still fill in the key cache with new blocks during this call and after, but this doesn't matter as all threads will start using the new key cache for their next call to myisam library and we know that there will not be any changed blocks in the old key cache. */ pthread_mutex_lock(&old_key_cache->op_lock); DEBUG_SYNC_C("assign_key_cache_op_lock"); if (flush_key_blocks(old_key_cache, share->kfile, &share->dirty_part_map, FLUSH_RELEASE)) { error= my_errno; mi_print_error(info->s, HA_ERR_CRASHED); mi_mark_crashed(info); /* Mark that table must be checked */ } pthread_mutex_unlock(&old_key_cache->op_lock); DEBUG_SYNC_C("assign_key_cache_op_unlock"); /* Flush the new key cache for this file. This is needed to ensure that there is no old blocks (with outdated data) left in the new key cache from an earlier assign_to_keycache operation (This can never fail as there is never any not written data in the new key cache) */ (void) flush_key_blocks(new_key_cache, share->kfile, &share->dirty_part_map, FLUSH_RELEASE); /* ensure that setting the key cache and changing the multi_key_cache is done atomicly */ mysql_mutex_lock(&share->intern_lock); /* Tell all threads to use the new key cache This should be seen at the lastes for the next call to an myisam function. */ share->key_cache= new_key_cache; share->dirty_part_map= 0; /* store the key cache in the global hash structure for future opens */ if (multi_key_cache_set((uchar*) share->unique_file_name, share->unique_name_length, new_key_cache)) error= my_errno; mysql_mutex_unlock(&share->intern_lock); DBUG_RETURN(error); }