diff options
Diffstat (limited to 'mysys')
84 files changed, 11210 insertions, 3146 deletions
diff --git a/mysys/CMakeLists.txt b/mysys/CMakeLists.txt index 648a87d1e95..e0f2de4a61b 100644 --- a/mysys/CMakeLists.txt +++ b/mysys/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2006, 2013, Oracle and/or its affiliates # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -29,25 +29,26 @@ SET(MYSYS_SOURCES array.c charset-def.c charset.c checksum.c default.c my_mkdir.c my_mmap.c my_once.c my_open.c my_pread.c my_pthread.c my_quick.c my_read.c my_redel.c my_rename.c my_seek.c my_sleep.c my_static.c my_symlink.c my_symlink2.c my_sync.c my_thr_init.c + my_basename.c my_write.c ptr_cmp.c queues.c stacktrace.c rijndael.c sha1.c string.c thr_alarm.c thr_lock.c thr_mutex.c thr_rwlock.c tree.c typelib.c base64.c my_memmem.c my_getpagesize.c lf_alloc-pin.c lf_dynarray.c lf_hash.c - my_atomic.c my_getncpus.c - my_rdtsc.c) + safemalloc.c my_new.cc + my_atomic.c my_getncpus.c my_safehash.c my_chmod.c my_rnd.c + my_uuid.c wqueue.c waiting_threads.c ma_dyncol.c + my_rdtsc.c my_context.c file_logger.c) IF (WIN32) SET (MYSYS_SOURCES ${MYSYS_SOURCES} my_winthread.c my_wincond.c my_winerr.c my_winfile.c my_windac.c my_conio.c) ENDIF() -IF(HAVE_ALARM) - SET(MYSYS_SOURCES ${MYSYS_SOURCES} my_alarm.c) +IF(UNIX) + SET (MYSYS_SOURCES ${MYSYS_SOURCES} my_addr_resolve.c) ENDIF() -IF(NOT HAVE_CXX_NEW) - # gcc as C++ compiler does not have new/delete - SET(MYSYS_SOURCES ${MYSYS_SOURCES} my_new.cc) - ADD_DEFINITIONS( -DUSE_MYSYS_NEW) +IF(HAVE_ALARM) + SET(MYSYS_SOURCES ${MYSYS_SOURCES} my_alarm.c) ENDIF() IF(CMAKE_SYSTEM_NAME MATCHES "SunOS" AND CMAKE_C_COMPILER_ID MATCHES "SunPro") @@ -66,9 +67,17 @@ ENDIF() ADD_CONVENIENCE_LIBRARY(mysys ${MYSYS_SOURCES}) TARGET_LINK_LIBRARIES(mysys dbug strings ${ZLIB_LIBRARY} - ${LIBNSL} ${LIBM} ${LIBRT}) + ${LIBNSL} ${LIBM} ${LIBRT} ${LIBSOCKET} ${LIBEXECINFO}) DTRACE_INSTRUMENT(mysys) +IF(HAVE_BFD_H) + TARGET_LINK_LIBRARIES(mysys bfd) +ENDIF(HAVE_BFD_H) + +IF (WIN32) + TARGET_LINK_LIBRARIES(mysys IPHLPAPI) +ENDIF(WIN32) + # Need explicit pthread for gcc -fsanitize=address IF(CMAKE_USE_PTHREADS_INIT AND CMAKE_C_FLAGS MATCHES "-fsanitize=") TARGET_LINK_LIBRARIES(mysys pthread) diff --git a/mysys/array.c b/mysys/array.c index f7ec1267427..c969da83586 100644 --- a/mysys/array.c +++ b/mysys/array.c @@ -30,8 +30,8 @@ alloc_increment Increment for adding new elements DESCRIPTION - init_dynamic_array() initiates array and allocate space for - init_alloc eilements. + init_dynamic_array() initiates array and allocate space for + init_alloc eilements. Array is usable even if space allocation failed, hence, the function never returns TRUE. Static buffers must begin immediately after the array structure. @@ -44,19 +44,13 @@ my_bool init_dynamic_array2(DYNAMIC_ARRAY *array, uint element_size, void *init_buffer, uint init_alloc, uint alloc_increment) { - DBUG_ENTER("init_dynamic_array"); + DBUG_ENTER("init_dynamic_array2"); if (!alloc_increment) { alloc_increment=max((8192-MALLOC_OVERHEAD)/element_size,16); if (init_alloc > 8 && alloc_increment > init_alloc * 2) alloc_increment=init_alloc*2; } - - if (!init_alloc) - { - init_alloc=alloc_increment; - init_buffer= 0; - } array->elements=0; array->max_element=init_alloc; array->alloc_increment=alloc_increment; @@ -67,16 +61,17 @@ my_bool init_dynamic_array2(DYNAMIC_ARRAY *array, uint element_size, Since the dynamic array is usable even if allocation fails here malloc should not throw an error */ - if (!(array->buffer= (uchar*) my_malloc(element_size*init_alloc, MYF(0)))) + if (init_alloc && + !(array->buffer= (uchar*) my_malloc(element_size*init_alloc, MYF(0)))) array->max_element=0; DBUG_RETURN(FALSE); -} +} my_bool init_dynamic_array(DYNAMIC_ARRAY *array, uint element_size, uint init_alloc, uint alloc_increment) { /* placeholder to preserve ABI */ - return my_init_dynamic_array_ci(array, element_size, init_alloc, + return my_init_dynamic_array_ci(array, element_size, init_alloc, alloc_increment); } /* @@ -92,7 +87,7 @@ my_bool init_dynamic_array(DYNAMIC_ARRAY *array, uint element_size, FALSE Ok */ -my_bool insert_dynamic(DYNAMIC_ARRAY *array, uchar* element) +my_bool insert_dynamic(DYNAMIC_ARRAY *array, const uchar* element) { uchar* buffer; if (array->elements == array->max_element) @@ -111,7 +106,7 @@ my_bool insert_dynamic(DYNAMIC_ARRAY *array, uchar* element) /* - Alloc space for next element(s) + Alloc space for next element(s) SYNOPSIS alloc_dynamic() @@ -129,6 +124,7 @@ my_bool insert_dynamic(DYNAMIC_ARRAY *array, uchar* element) uchar *alloc_dynamic(DYNAMIC_ARRAY *array) { + DBUG_ENTER("alloc_dynamic"); if (array->elements == array->max_element) { char *new_ptr; @@ -142,20 +138,20 @@ uchar *alloc_dynamic(DYNAMIC_ARRAY *array) array->alloc_increment) * array->size_of_element, MYF(MY_WME)))) - return 0; - memcpy(new_ptr, array->buffer, + DBUG_RETURN(0); + memcpy(new_ptr, array->buffer, array->elements * array->size_of_element); } - else - if (!(new_ptr=(char*) my_realloc(array->buffer,(array->max_element+ - array->alloc_increment)* - array->size_of_element, - MYF(MY_WME | MY_ALLOW_ZERO_PTR)))) - return 0; + else if (!(new_ptr=(char*) + my_realloc(array->buffer,(array->max_element+ + array->alloc_increment)* + array->size_of_element, + MYF(MY_WME | MY_ALLOW_ZERO_PTR)))) + DBUG_RETURN(0); array->buffer= (uchar*) new_ptr; array->max_element+=array->alloc_increment; } - return array->buffer+(array->elements++ * array->size_of_element); + DBUG_RETURN(array->buffer+(array->elements++ * array->size_of_element)); } @@ -165,8 +161,8 @@ uchar *alloc_dynamic(DYNAMIC_ARRAY *array) SYNOPSIS pop_dynamic() array - - RETURN VALUE + + RETURN VALUE pointer Ok 0 Array is empty */ @@ -188,9 +184,9 @@ uchar *pop_dynamic(DYNAMIC_ARRAY *array) idx Index where element is to be inserted DESCRIPTION - set_dynamic() replaces element in array. - If idx > max_element insert new element. Allocate memory if needed. - + set_dynamic() replaces element in array. + If idx > max_element insert new element. Allocate memory if needed. + RETURN VALUE TRUE Idx was out of range and allocation of new memory failed FALSE Ok @@ -230,6 +226,8 @@ my_bool set_dynamic(DYNAMIC_ARRAY *array, uchar* element, uint idx) my_bool allocate_dynamic(DYNAMIC_ARRAY *array, uint max_elements) { + DBUG_ENTER("allocate_dynamic"); + if (max_elements >= array->max_element) { uint size; @@ -243,23 +241,20 @@ my_bool allocate_dynamic(DYNAMIC_ARRAY *array, uint max_elements) so we have to create an all-new malloc since we overflowed */ if (!(new_ptr= (uchar *) my_malloc(size * - array->size_of_element, - MYF(MY_WME)))) - return 0; - memcpy(new_ptr, array->buffer, + array->size_of_element, + MYF(MY_WME)))) + DBUG_RETURN(0); + memcpy(new_ptr, array->buffer, array->elements * array->size_of_element); } - else - - - if (!(new_ptr= (uchar*) my_realloc(array->buffer,size* - array->size_of_element, - MYF(MY_WME | MY_ALLOW_ZERO_PTR)))) - return TRUE; + else if (!(new_ptr= (uchar*) my_realloc(array->buffer,size* + array->size_of_element, + MYF(MY_WME | MY_ALLOW_ZERO_PTR)))) + DBUG_RETURN(TRUE); array->buffer= new_ptr; array->max_element= size; } - return FALSE; + DBUG_RETURN(FALSE); } @@ -268,9 +263,9 @@ my_bool allocate_dynamic(DYNAMIC_ARRAY *array, uint max_elements) SYNOPSIS get_dynamic() - array + array uchar* Element to be returned. If idx > elements contain zeroes. - idx Index of element wanted. + idx Index of element wanted. */ void get_dynamic(DYNAMIC_ARRAY *array, uchar* element, uint idx) @@ -347,7 +342,7 @@ void freeze_size(DYNAMIC_ARRAY *array) */ if (array->buffer == (uchar *)(array + 1)) return; - + if (array->buffer && array->max_element != elements) { array->buffer=(uchar*) my_realloc(array->buffer, @@ -364,7 +359,7 @@ void freeze_size(DYNAMIC_ARRAY *array) SYNOPSIS get_index_dynamic() array Array - element Whose element index + element Whose element index */ diff --git a/mysys/charset-def.c b/mysys/charset-def.c index 987b071a720..e9f2ecdea49 100644 --- a/mysys/charset-def.c +++ b/mysys/charset-def.c @@ -24,119 +24,124 @@ #ifdef HAVE_UCA_COLLATIONS #ifdef HAVE_CHARSET_ucs2 -extern CHARSET_INFO my_charset_ucs2_icelandic_uca_ci; -extern CHARSET_INFO my_charset_ucs2_latvian_uca_ci; -extern CHARSET_INFO my_charset_ucs2_romanian_uca_ci; -extern CHARSET_INFO my_charset_ucs2_slovenian_uca_ci; -extern CHARSET_INFO my_charset_ucs2_polish_uca_ci; -extern CHARSET_INFO my_charset_ucs2_estonian_uca_ci; -extern CHARSET_INFO my_charset_ucs2_spanish_uca_ci; -extern CHARSET_INFO my_charset_ucs2_swedish_uca_ci; -extern CHARSET_INFO my_charset_ucs2_turkish_uca_ci; -extern CHARSET_INFO my_charset_ucs2_czech_uca_ci; -extern CHARSET_INFO my_charset_ucs2_danish_uca_ci; -extern CHARSET_INFO my_charset_ucs2_lithuanian_uca_ci; -extern CHARSET_INFO my_charset_ucs2_slovak_uca_ci; -extern CHARSET_INFO my_charset_ucs2_spanish2_uca_ci; -extern CHARSET_INFO my_charset_ucs2_roman_uca_ci; -extern CHARSET_INFO my_charset_ucs2_persian_uca_ci; -extern CHARSET_INFO my_charset_ucs2_esperanto_uca_ci; -extern CHARSET_INFO my_charset_ucs2_hungarian_uca_ci; -extern CHARSET_INFO my_charset_ucs2_sinhala_uca_ci; +extern struct charset_info_st my_charset_ucs2_icelandic_uca_ci; +extern struct charset_info_st my_charset_ucs2_latvian_uca_ci; +extern struct charset_info_st my_charset_ucs2_romanian_uca_ci; +extern struct charset_info_st my_charset_ucs2_slovenian_uca_ci; +extern struct charset_info_st my_charset_ucs2_polish_uca_ci; +extern struct charset_info_st my_charset_ucs2_estonian_uca_ci; +extern struct charset_info_st my_charset_ucs2_spanish_uca_ci; +extern struct charset_info_st my_charset_ucs2_swedish_uca_ci; +extern struct charset_info_st my_charset_ucs2_turkish_uca_ci; +extern struct charset_info_st my_charset_ucs2_czech_uca_ci; +extern struct charset_info_st my_charset_ucs2_danish_uca_ci; +extern struct charset_info_st my_charset_ucs2_lithuanian_uca_ci; +extern struct charset_info_st my_charset_ucs2_slovak_uca_ci; +extern struct charset_info_st my_charset_ucs2_spanish2_uca_ci; +extern struct charset_info_st my_charset_ucs2_roman_uca_ci; +extern struct charset_info_st my_charset_ucs2_persian_uca_ci; +extern struct charset_info_st my_charset_ucs2_esperanto_uca_ci; +extern struct charset_info_st my_charset_ucs2_hungarian_uca_ci; +extern struct charset_info_st my_charset_ucs2_sinhala_uca_ci; +extern struct charset_info_st my_charset_ucs2_croatian_uca_ci; #endif #ifdef HAVE_CHARSET_utf32 -extern CHARSET_INFO my_charset_utf32_icelandic_uca_ci; -extern CHARSET_INFO my_charset_utf32_latvian_uca_ci; -extern CHARSET_INFO my_charset_utf32_romanian_uca_ci; -extern CHARSET_INFO my_charset_utf32_slovenian_uca_ci; -extern CHARSET_INFO my_charset_utf32_polish_uca_ci; -extern CHARSET_INFO my_charset_utf32_estonian_uca_ci; -extern CHARSET_INFO my_charset_utf32_spanish_uca_ci; -extern CHARSET_INFO my_charset_utf32_swedish_uca_ci; -extern CHARSET_INFO my_charset_utf32_turkish_uca_ci; -extern CHARSET_INFO my_charset_utf32_czech_uca_ci; -extern CHARSET_INFO my_charset_utf32_danish_uca_ci; -extern CHARSET_INFO my_charset_utf32_lithuanian_uca_ci; -extern CHARSET_INFO my_charset_utf32_slovak_uca_ci; -extern CHARSET_INFO my_charset_utf32_spanish2_uca_ci; -extern CHARSET_INFO my_charset_utf32_roman_uca_ci; -extern CHARSET_INFO my_charset_utf32_persian_uca_ci; -extern CHARSET_INFO my_charset_utf32_esperanto_uca_ci; -extern CHARSET_INFO my_charset_utf32_hungarian_uca_ci; -extern CHARSET_INFO my_charset_utf32_sinhala_uca_ci; +extern struct charset_info_st my_charset_utf32_icelandic_uca_ci; +extern struct charset_info_st my_charset_utf32_latvian_uca_ci; +extern struct charset_info_st my_charset_utf32_romanian_uca_ci; +extern struct charset_info_st my_charset_utf32_slovenian_uca_ci; +extern struct charset_info_st my_charset_utf32_polish_uca_ci; +extern struct charset_info_st my_charset_utf32_estonian_uca_ci; +extern struct charset_info_st my_charset_utf32_spanish_uca_ci; +extern struct charset_info_st my_charset_utf32_swedish_uca_ci; +extern struct charset_info_st my_charset_utf32_turkish_uca_ci; +extern struct charset_info_st my_charset_utf32_czech_uca_ci; +extern struct charset_info_st my_charset_utf32_danish_uca_ci; +extern struct charset_info_st my_charset_utf32_lithuanian_uca_ci; +extern struct charset_info_st my_charset_utf32_slovak_uca_ci; +extern struct charset_info_st my_charset_utf32_spanish2_uca_ci; +extern struct charset_info_st my_charset_utf32_roman_uca_ci; +extern struct charset_info_st my_charset_utf32_persian_uca_ci; +extern struct charset_info_st my_charset_utf32_esperanto_uca_ci; +extern struct charset_info_st my_charset_utf32_hungarian_uca_ci; +extern struct charset_info_st my_charset_utf32_sinhala_uca_ci; +extern struct charset_info_st my_charset_utf32_croatian_uca_ci; #endif /* HAVE_CHARSET_utf32 */ #ifdef HAVE_CHARSET_utf16 -extern CHARSET_INFO my_charset_utf16_icelandic_uca_ci; -extern CHARSET_INFO my_charset_utf16_latvian_uca_ci; -extern CHARSET_INFO my_charset_utf16_romanian_uca_ci; -extern CHARSET_INFO my_charset_utf16_slovenian_uca_ci; -extern CHARSET_INFO my_charset_utf16_polish_uca_ci; -extern CHARSET_INFO my_charset_utf16_estonian_uca_ci; -extern CHARSET_INFO my_charset_utf16_spanish_uca_ci; -extern CHARSET_INFO my_charset_utf16_swedish_uca_ci; -extern CHARSET_INFO my_charset_utf16_turkish_uca_ci; -extern CHARSET_INFO my_charset_utf16_czech_uca_ci; -extern CHARSET_INFO my_charset_utf16_danish_uca_ci; -extern CHARSET_INFO my_charset_utf16_lithuanian_uca_ci; -extern CHARSET_INFO my_charset_utf16_slovak_uca_ci; -extern CHARSET_INFO my_charset_utf16_spanish2_uca_ci; -extern CHARSET_INFO my_charset_utf16_roman_uca_ci; -extern CHARSET_INFO my_charset_utf16_persian_uca_ci; -extern CHARSET_INFO my_charset_utf16_esperanto_uca_ci; -extern CHARSET_INFO my_charset_utf16_hungarian_uca_ci; -extern CHARSET_INFO my_charset_utf16_sinhala_uca_ci; +extern struct charset_info_st my_charset_utf16_icelandic_uca_ci; +extern struct charset_info_st my_charset_utf16_latvian_uca_ci; +extern struct charset_info_st my_charset_utf16_romanian_uca_ci; +extern struct charset_info_st my_charset_utf16_slovenian_uca_ci; +extern struct charset_info_st my_charset_utf16_polish_uca_ci; +extern struct charset_info_st my_charset_utf16_estonian_uca_ci; +extern struct charset_info_st my_charset_utf16_spanish_uca_ci; +extern struct charset_info_st my_charset_utf16_swedish_uca_ci; +extern struct charset_info_st my_charset_utf16_turkish_uca_ci; +extern struct charset_info_st my_charset_utf16_czech_uca_ci; +extern struct charset_info_st my_charset_utf16_danish_uca_ci; +extern struct charset_info_st my_charset_utf16_lithuanian_uca_ci; +extern struct charset_info_st my_charset_utf16_slovak_uca_ci; +extern struct charset_info_st my_charset_utf16_spanish2_uca_ci; +extern struct charset_info_st my_charset_utf16_roman_uca_ci; +extern struct charset_info_st my_charset_utf16_persian_uca_ci; +extern struct charset_info_st my_charset_utf16_esperanto_uca_ci; +extern struct charset_info_st my_charset_utf16_hungarian_uca_ci; +extern struct charset_info_st my_charset_utf16_sinhala_uca_ci; +extern struct charset_info_st my_charset_utf16_croatian_uca_ci; #endif /* HAVE_CHARSET_utf16 */ #ifdef HAVE_CHARSET_utf8 -extern CHARSET_INFO my_charset_utf8_icelandic_uca_ci; -extern CHARSET_INFO my_charset_utf8_latvian_uca_ci; -extern CHARSET_INFO my_charset_utf8_romanian_uca_ci; -extern CHARSET_INFO my_charset_utf8_slovenian_uca_ci; -extern CHARSET_INFO my_charset_utf8_polish_uca_ci; -extern CHARSET_INFO my_charset_utf8_estonian_uca_ci; -extern CHARSET_INFO my_charset_utf8_spanish_uca_ci; -extern CHARSET_INFO my_charset_utf8_swedish_uca_ci; -extern CHARSET_INFO my_charset_utf8_turkish_uca_ci; -extern CHARSET_INFO my_charset_utf8_czech_uca_ci; -extern CHARSET_INFO my_charset_utf8_danish_uca_ci; -extern CHARSET_INFO my_charset_utf8_lithuanian_uca_ci; -extern CHARSET_INFO my_charset_utf8_slovak_uca_ci; -extern CHARSET_INFO my_charset_utf8_spanish2_uca_ci; -extern CHARSET_INFO my_charset_utf8_roman_uca_ci; -extern CHARSET_INFO my_charset_utf8_persian_uca_ci; -extern CHARSET_INFO my_charset_utf8_esperanto_uca_ci; -extern CHARSET_INFO my_charset_utf8_hungarian_uca_ci; -extern CHARSET_INFO my_charset_utf8_sinhala_uca_ci; +extern struct charset_info_st my_charset_utf8_icelandic_uca_ci; +extern struct charset_info_st my_charset_utf8_latvian_uca_ci; +extern struct charset_info_st my_charset_utf8_romanian_uca_ci; +extern struct charset_info_st my_charset_utf8_slovenian_uca_ci; +extern struct charset_info_st my_charset_utf8_polish_uca_ci; +extern struct charset_info_st my_charset_utf8_estonian_uca_ci; +extern struct charset_info_st my_charset_utf8_spanish_uca_ci; +extern struct charset_info_st my_charset_utf8_swedish_uca_ci; +extern struct charset_info_st my_charset_utf8_turkish_uca_ci; +extern struct charset_info_st my_charset_utf8_czech_uca_ci; +extern struct charset_info_st my_charset_utf8_danish_uca_ci; +extern struct charset_info_st my_charset_utf8_lithuanian_uca_ci; +extern struct charset_info_st my_charset_utf8_slovak_uca_ci; +extern struct charset_info_st my_charset_utf8_spanish2_uca_ci; +extern struct charset_info_st my_charset_utf8_roman_uca_ci; +extern struct charset_info_st my_charset_utf8_persian_uca_ci; +extern struct charset_info_st my_charset_utf8_esperanto_uca_ci; +extern struct charset_info_st my_charset_utf8_hungarian_uca_ci; +extern struct charset_info_st my_charset_utf8_sinhala_uca_ci; +extern struct charset_info_st my_charset_utf8_croatian_uca_ci; #ifdef HAVE_UTF8_GENERAL_CS -extern CHARSET_INFO my_charset_utf8_general_cs; +extern struct charset_info_st my_charset_utf8_general_cs; #endif #endif #ifdef HAVE_CHARSET_utf8mb4 -extern CHARSET_INFO my_charset_utf8mb4_icelandic_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_latvian_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_romanian_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_slovenian_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_polish_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_estonian_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_spanish_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_swedish_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_turkish_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_czech_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_danish_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_lithuanian_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_slovak_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_spanish2_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_roman_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_persian_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_esperanto_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_hungarian_uca_ci; -extern CHARSET_INFO my_charset_utf8mb4_sinhala_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_icelandic_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_latvian_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_romanian_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_slovenian_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_polish_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_estonian_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_spanish_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_swedish_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_turkish_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_czech_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_danish_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_lithuanian_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_slovak_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_spanish2_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_roman_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_persian_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_esperanto_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_hungarian_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_sinhala_uca_ci; +extern struct charset_info_st my_charset_utf8mb4_croatian_uca_ci; #endif /* HAVE_CHARSET_utf8mb4 */ #endif /* HAVE_UCA_COLLATIONS */ @@ -225,6 +230,7 @@ my_bool init_compiled_charsets(myf flags __attribute__((unused))) add_compiled_collation(&my_charset_ucs2_esperanto_uca_ci); add_compiled_collation(&my_charset_ucs2_hungarian_uca_ci); add_compiled_collation(&my_charset_ucs2_sinhala_uca_ci); + add_compiled_collation(&my_charset_ucs2_croatian_uca_ci); #endif #endif @@ -261,6 +267,7 @@ my_bool init_compiled_charsets(myf flags __attribute__((unused))) add_compiled_collation(&my_charset_utf8_esperanto_uca_ci); add_compiled_collation(&my_charset_utf8_hungarian_uca_ci); add_compiled_collation(&my_charset_utf8_sinhala_uca_ci); + add_compiled_collation(&my_charset_utf8_croatian_uca_ci); #endif #endif /* HAVE_CHARSET_utf8 */ @@ -289,6 +296,7 @@ my_bool init_compiled_charsets(myf flags __attribute__((unused))) add_compiled_collation(&my_charset_utf8mb4_esperanto_uca_ci); add_compiled_collation(&my_charset_utf8mb4_hungarian_uca_ci); add_compiled_collation(&my_charset_utf8mb4_sinhala_uca_ci); + add_compiled_collation(&my_charset_utf8mb4_croatian_uca_ci); #endif /* HAVE_UCA_COLLATIONS */ #endif /* HAVE_CHARSET_utf8mb4 */ @@ -317,7 +325,8 @@ my_bool init_compiled_charsets(myf flags __attribute__((unused))) add_compiled_collation(&my_charset_utf16_esperanto_uca_ci); add_compiled_collation(&my_charset_utf16_hungarian_uca_ci); add_compiled_collation(&my_charset_utf16_sinhala_uca_ci); -#endif /* HAVE_UCA_COLLATIOINS */ + add_compiled_collation(&my_charset_utf16_croatian_uca_ci); +#endif /* HAVE_UCA_COLLATIONS */ #endif /* HAVE_CHARSET_utf16 */ @@ -345,12 +354,13 @@ my_bool init_compiled_charsets(myf flags __attribute__((unused))) add_compiled_collation(&my_charset_utf32_esperanto_uca_ci); add_compiled_collation(&my_charset_utf32_hungarian_uca_ci); add_compiled_collation(&my_charset_utf32_sinhala_uca_ci); + add_compiled_collation(&my_charset_utf32_croatian_uca_ci); #endif /* HAVE_UCA_COLLATIONS */ #endif /* HAVE_CHARSET_utf32 */ /* Copy compiled charsets */ for (cs=compiled_charsets; cs->name; cs++) - add_compiled_collation(cs); + add_compiled_collation((struct charset_info_st *) cs); return FALSE; } diff --git a/mysys/charset.c b/mysys/charset.c index 9f9a96a3089..cc1d0a0111e 100644 --- a/mysys/charset.c +++ b/mysys/charset.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2000, 2011, Oracle and/or its affiliates This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -53,21 +54,18 @@ get_collation_number_internal(const char *name) } -static my_bool init_state_maps(CHARSET_INFO *cs) +static my_bool init_state_maps(struct charset_info_st *cs) { uint i; uchar *state_map; uchar *ident_map; - if (!(cs->state_map= (uchar*) my_once_alloc(256, MYF(MY_WME)))) + if (!(cs->state_map= state_map= (uchar*) my_once_alloc(256, MYF(MY_WME)))) return 1; - if (!(cs->ident_map= (uchar*) my_once_alloc(256, MYF(MY_WME)))) + if (!(cs->ident_map= ident_map= (uchar*) my_once_alloc(256, MYF(MY_WME)))) return 1; - state_map= cs->state_map; - ident_map= cs->ident_map; - /* Fill state_map with states to get a faster parser */ for (i=0; i < 256 ; i++) { @@ -100,6 +98,9 @@ static my_bool init_state_maps(CHARSET_INFO *cs) state_map[(uchar)'@']= (uchar) MY_LEX_USER_END; state_map[(uchar) '`']= (uchar) MY_LEX_USER_VARIABLE_DELIMITER; state_map[(uchar)'"']= (uchar) MY_LEX_STRING_OR_DELIMITER; + state_map[(uchar)'-']= (uchar) MY_LEX_MINUS_OR_COMMENT; + state_map[(uchar)',']= (uchar) MY_LEX_COMMA; + state_map[(uchar)'?']= (uchar) MY_LEX_PLACEHOLDER; /* Create a second map to make it faster to find identifiers @@ -118,7 +119,7 @@ static my_bool init_state_maps(CHARSET_INFO *cs) } -static void simple_cs_init_functions(CHARSET_INFO *cs) +static void simple_cs_init_functions(struct charset_info_st *cs) { if (cs->state & MY_CS_BINSORT) cs->coll= &my_collation_8bit_bin_handler; @@ -130,7 +131,7 @@ static void simple_cs_init_functions(CHARSET_INFO *cs) -static int cs_copy_data(CHARSET_INFO *to, CHARSET_INFO *from) +static int cs_copy_data(struct charset_info_st *to, CHARSET_INFO *from) { to->number= from->number ? from->number : to->number; @@ -202,8 +203,9 @@ static my_bool simple_cs_is_full(CHARSET_INFO *cs) } +#if defined(HAVE_UCA_COLLATIONS) && (defined(HAVE_CHARSET_ucs2) || defined(HAVE_CHARSET_utf8)) static void -copy_uca_collation(CHARSET_INFO *to, CHARSET_INFO *from) +copy_uca_collation(struct charset_info_st *to, CHARSET_INFO *from) { to->cset= from->cset; to->coll= from->coll; @@ -215,20 +217,22 @@ copy_uca_collation(CHARSET_INFO *to, CHARSET_INFO *from) to->state|= MY_CS_AVAILABLE | MY_CS_LOADED | MY_CS_STRNXFRM | MY_CS_UNICODE; } +#endif -static int add_collation(CHARSET_INFO *cs) +static int add_collation(struct charset_info_st *cs) { if (cs->name && (cs->number || (cs->number=get_collation_number_internal(cs->name))) && cs->number < array_elements(all_charsets)) { - if (!all_charsets[cs->number]) + struct charset_info_st *newcs; + if (!(newcs= (struct charset_info_st*) all_charsets[cs->number])) { - if (!(all_charsets[cs->number]= - (CHARSET_INFO*) my_once_alloc(sizeof(CHARSET_INFO),MYF(0)))) + if (!(all_charsets[cs->number]= newcs= + (struct charset_info_st*) my_once_alloc(sizeof(CHARSET_INFO),MYF(0)))) return MY_XML_ERROR; - bzero((void*)all_charsets[cs->number],sizeof(CHARSET_INFO)); + bzero(newcs,sizeof(CHARSET_INFO)); } if (cs->primary_number == cs->number) @@ -237,12 +241,11 @@ static int add_collation(CHARSET_INFO *cs) if (cs->binary_number == cs->number) cs->state |= MY_CS_BINSORT; - all_charsets[cs->number]->state|= cs->state; + newcs->state|= cs->state; - if (!(all_charsets[cs->number]->state & MY_CS_COMPILED)) + if (!(newcs->state & MY_CS_COMPILED)) { - CHARSET_INFO *newcs= all_charsets[cs->number]; - if (cs_copy_data(all_charsets[cs->number],cs)) + if (cs_copy_data(newcs,cs)) return MY_XML_ERROR; newcs->caseup_multiply= newcs->casedn_multiply= 1; @@ -287,15 +290,15 @@ static int add_collation(CHARSET_INFO *cs) } else { - uchar *sort_order= all_charsets[cs->number]->sort_order; - simple_cs_init_functions(all_charsets[cs->number]); + const uchar *sort_order= newcs->sort_order; + simple_cs_init_functions(newcs); newcs->mbminlen= 1; newcs->mbmaxlen= 1; - if (simple_cs_is_full(all_charsets[cs->number])) + if (simple_cs_is_full(newcs)) { - all_charsets[cs->number]->state |= MY_CS_LOADED; + newcs->state |= MY_CS_LOADED; } - all_charsets[cs->number]->state|= MY_CS_AVAILABLE; + newcs->state|= MY_CS_AVAILABLE; /* Check if case sensitive sort order: A < a < B. @@ -305,12 +308,12 @@ static int add_collation(CHARSET_INFO *cs) */ if (sort_order && sort_order['A'] < sort_order['a'] && sort_order['a'] < sort_order['B']) - all_charsets[cs->number]->state|= MY_CS_CSSORT; + newcs->state|= MY_CS_CSSORT; - if (my_charset_is_8bit_pure_ascii(all_charsets[cs->number])) - all_charsets[cs->number]->state|= MY_CS_PUREASCII; + if (my_charset_is_8bit_pure_ascii(newcs)) + newcs->state|= MY_CS_PUREASCII; if (!my_charset_is_ascii_compatible(cs)) - all_charsets[cs->number]->state|= MY_CS_NONASCII; + newcs->state|= MY_CS_NONASCII; } } else @@ -324,16 +327,15 @@ static int add_collation(CHARSET_INFO *cs) If a character set was compiled, this information will get lost and overwritten in add_compiled_collation(). */ - CHARSET_INFO *dst= all_charsets[cs->number]; - dst->number= cs->number; + newcs->number= cs->number; if (cs->comment) - if (!(dst->comment= my_once_strdup(cs->comment,MYF(MY_WME)))) + if (!(newcs->comment= my_once_strdup(cs->comment,MYF(MY_WME)))) return MY_XML_ERROR; if (cs->csname) - if (!(dst->csname= my_once_strdup(cs->csname,MYF(MY_WME)))) + if (!(newcs->csname= my_once_strdup(cs->csname,MYF(MY_WME)))) return MY_XML_ERROR; if (cs->name) - if (!(dst->name= my_once_strdup(cs->name,MYF(MY_WME)))) + if (!(newcs->name= my_once_strdup(cs->name,MYF(MY_WME)))) return MY_XML_ERROR; } cs->number= 0; @@ -417,7 +419,7 @@ char *get_charsets_dir(char *buf) CHARSET_INFO *all_charsets[MY_ALL_CHARSETS_SIZE]={NULL}; CHARSET_INFO *default_charset_info = &my_charset_latin1; -void add_compiled_collation(CHARSET_INFO *cs) +void add_compiled_collation(struct charset_info_st *cs) { DBUG_ASSERT(cs->number < array_elements(all_charsets)); all_charsets[cs->number]= cs; @@ -436,14 +438,15 @@ static my_pthread_once_t charsets_template= MY_PTHREAD_ONCE_INIT; static void init_available_charsets(void) { char fname[FN_REFLEN + sizeof(MY_CHARSET_INDEX)]; - CHARSET_INFO **cs; + struct charset_info_st **cs; - bzero(&all_charsets,sizeof(all_charsets)); + bzero((char*) &all_charsets,sizeof(all_charsets)); init_compiled_charsets(MYF(0)); /* Copy compiled charsets */ - for (cs=all_charsets; - cs < all_charsets+array_elements(all_charsets)-1 ; + for (cs= (struct charset_info_st**) all_charsets; + cs < (struct charset_info_st**) all_charsets + + array_elements(all_charsets)-1 ; cs++) { if (*cs) @@ -547,11 +550,11 @@ const char *get_charset_name(uint charset_number) static CHARSET_INFO *get_internal_charset(uint cs_number, myf flags) { char buf[FN_REFLEN]; - CHARSET_INFO *cs; + struct charset_info_st *cs; DBUG_ASSERT(cs_number < array_elements(all_charsets)); - if ((cs= all_charsets[cs_number])) + if ((cs= (struct charset_info_st*) all_charsets[cs_number])) { if (cs->state & MY_CS_READY) /* if CS is already initialized */ return cs; diff --git a/mysys/checksum.c b/mysys/checksum.c index 87f3056cf19..7bc52c6a178 100644 --- a/mysys/checksum.c +++ b/mysys/checksum.c @@ -18,6 +18,8 @@ #include <my_sys.h> #include <zlib.h> +ulong my_crc_dbug_check= ~0; /* Cannot happen */ + /* Calculate a long checksum for a memoryblock. @@ -30,6 +32,9 @@ ha_checksum my_checksum(ha_checksum crc, const uchar *pos, size_t length) { - return (ha_checksum)crc32((uint)crc, pos, (uint)length); + crc= (ha_checksum) crc32((uint)crc, pos, (uint) length); + DBUG_PRINT("info", ("crc: %lu", (ulong) crc)); + if ((ulong)crc == my_crc_dbug_check) + my_debug_put_break_here(); + return crc; } - diff --git a/mysys/default.c b/mysys/default.c index 6fe00af087e..08653b1a3c4 100644 --- a/mysys/default.c +++ b/mysys/default.c @@ -814,7 +814,7 @@ static int search_default_file_with_ext(Process_option_func opt_handler, continue; /* Configuration File Directives */ - if ((*ptr == '!')) + if (*ptr == '!') { if (recursion_level >= max_recursion_level) { @@ -1198,10 +1198,11 @@ static const char **init_default_directories(MEM_ROOT *alloc) const char **dirs; char *env; int errors= 0; + DBUG_ENTER("init_default_directories"); dirs= (const char **)alloc_root(alloc, DEFAULT_DIRS_SIZE * sizeof(char *)); if (dirs == NULL) - return NULL; + DBUG_RETURN(NULL); bzero((char *) dirs, DEFAULT_DIRS_SIZE * sizeof(char *)); #ifdef __WIN__ @@ -1242,5 +1243,5 @@ static const char **init_default_directories(MEM_ROOT *alloc) errors += add_directory(alloc, "~/", dirs); #endif - return (errors > 0 ? NULL : dirs); + DBUG_RETURN(errors > 0 ? NULL : dirs); } diff --git a/mysys/errors.c b/mysys/errors.c index ddd65836b30..84b406792af 100644 --- a/mysys/errors.c +++ b/mysys/errors.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2000, 2013, Oracle and/or its affiliates This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -52,7 +53,9 @@ const char *globerrs[GLOBERRS]= "File '%s' (fileno: %d) was not closed", "Can't change ownership of the file '%s' (Errcode: %d)", "Can't change permissions of the file '%s' (Errcode: %d)", - "Can't seek in file '%s' (Errcode: %d)" + "Can't seek in file '%s' (Errcode: %d)", + "Can't change mode for file '%s' to 0x%lx (Error: %d)", + "Warning: Can't copy ownership for file '%s' (Error: %d)" }; void init_glob_errs(void) @@ -96,32 +99,22 @@ void init_glob_errs() EE(EE_CHANGE_OWNERSHIP) = "Can't change ownership of the file '%s' (Errcode: %d)"; EE(EE_CHANGE_PERMISSIONS) = "Can't change permissions of the file '%s' (Errcode: %d)"; EE(EE_CANT_SEEK) = "Can't seek in file '%s' (Errcode: %d)"; + EE(EE_CANT_CHMOD) = "Can't change mode for file '%s' to 0x%lx (Error: %d)"; + EE(EE_CANT_COPY_OWNERSHIP)= "Warning: Can't copy ownership for file '%s' (Error: %d)"; } #endif -/* - We cannot call my_error/my_printf_error here in this function. - Those functions will set status variable in diagnostic area - and there is no provision to reset them back. - Here we are waiting for free space and will wait forever till - space is created. So just giving warning in the error file - should be enough. -*/ void wait_for_free_space(const char *filename, int errors) { - if (!(errors % MY_WAIT_GIVE_USER_A_MESSAGE)) - { - my_printf_warning(EE(EE_DISK_FULL), + if (errors == 0) + my_error(EE_DISK_FULL,MYF(ME_BELL | ME_NOREFRESH | ME_JUST_WARNING), filename,my_errno,MY_WAIT_FOR_USER_TO_FIX_PANIC); - my_printf_warning("Retry in %d secs. Message reprinted in %d secs", + if (!(errors % MY_WAIT_GIVE_USER_A_MESSAGE)) + my_printf_error(EE_DISK_FULL, + "Retry in %d secs. Message reprinted in %d secs", + MYF(ME_BELL | ME_NOREFRESH | ME_JUST_WARNING), MY_WAIT_FOR_USER_TO_FIX_PANIC, MY_WAIT_GIVE_USER_A_MESSAGE * MY_WAIT_FOR_USER_TO_FIX_PANIC ); - } - DBUG_EXECUTE_IF("simulate_no_free_space_error", - { - (void) sleep(1); - return; - }); (void) sleep(MY_WAIT_FOR_USER_TO_FIX_PANIC); } diff --git a/mysys/file_logger.c b/mysys/file_logger.c new file mode 100644 index 00000000000..4c07b8c7854 --- /dev/null +++ b/mysys/file_logger.c @@ -0,0 +1,223 @@ +/* Copyright (C) 2012 Monty Program Ab + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +#include "my_global.h" +#include <my_sys.h> +#include <mysql/service_logger.h> +#include <my_pthread.h> + +#ifdef HAVE_PSI_INTERFACE +/* These belong to the service initialization */ +static PSI_mutex_key key_LOCK_logger_service; +static PSI_mutex_info mutex_list[]= +{{ &key_LOCK_logger_service, "logger_service_file_st::lock", PSI_FLAG_GLOBAL}}; +#endif + +typedef struct logger_handle_st { + File file; + char path[FN_REFLEN]; + unsigned long long size_limit; + unsigned int rotations; + size_t path_len; + mysql_mutex_t lock; +} LSFS; + + +#define LOG_FLAGS (O_APPEND | O_CREAT | O_WRONLY) + +static unsigned int n_dig(unsigned int i) +{ + return (i == 0) ? 0 : ((i < 10) ? 1 : ((i < 100) ? 2 : 3)); +} + + +LOGGER_HANDLE *logger_open(const char *path, + unsigned long long size_limit, + unsigned int rotations) +{ + LOGGER_HANDLE new_log, *l_perm; + /* + I don't think we ever need more rotations, + but if it's so, the rotation procedure should be adapted to it. + */ + if (rotations > 999) + return 0; + + new_log.rotations= rotations; + new_log.size_limit= size_limit; + new_log.path_len= strlen(fn_format(new_log.path, path, + mysql_data_home, "", MY_UNPACK_FILENAME)); + + if (new_log.path_len+n_dig(rotations)+1 > FN_REFLEN) + { + errno= ENAMETOOLONG; + /* File path too long */ + return 0; + } + if ((new_log.file= my_open(new_log.path, LOG_FLAGS, MYF(0))) < 0) + { + errno= my_errno; + /* Check errno for the cause */ + return 0; + } + + if (!(l_perm= (LOGGER_HANDLE *) my_malloc(sizeof(LOGGER_HANDLE), MYF(0)))) + { + my_close(new_log.file, MYF(0)); + new_log.file= -1; + return 0; /* End of memory */ + } + *l_perm= new_log; + mysql_mutex_init(key_LOCK_logger_service, &l_perm->lock, MY_MUTEX_INIT_FAST); + return l_perm; +} + +int logger_close(LOGGER_HANDLE *log) +{ + int result; + File file= log->file; + mysql_mutex_destroy(&log->lock); + my_free(log); + if ((result= my_close(file, MYF(0)))) + errno= my_errno; + return result; +} + + +static char *logname(LOGGER_HANDLE *log, char *buf, unsigned int n_log) +{ + sprintf(buf+log->path_len, ".%0*u", n_dig(log->rotations), n_log); + return buf; +} + + +static int do_rotate(LOGGER_HANDLE *log) +{ + char namebuf[FN_REFLEN]; + int result; + unsigned int i; + char *buf_old, *buf_new, *tmp; + + if (log->rotations == 0) + return 0; + + memcpy(namebuf, log->path, log->path_len); + + buf_new= logname(log, namebuf, log->rotations); + buf_old= log->path; + for (i=log->rotations-1; i>0; i--) + { + logname(log, buf_old, i); + if (!access(buf_old, F_OK) && + (result= my_rename(buf_old, buf_new, MYF(0)))) + goto exit; + tmp= buf_old; + buf_old= buf_new; + buf_new= tmp; + } + if ((result= my_close(log->file, MYF(0)))) + goto exit; + namebuf[log->path_len]= 0; + result= my_rename(namebuf, logname(log, log->path, 1), MYF(0)); + log->file= my_open(namebuf, LOG_FLAGS, MYF(0)); +exit: + errno= my_errno; + return log->file < 0 || result; +} + + +int logger_vprintf(LOGGER_HANDLE *log, const char* fmt, va_list ap) +{ + int result; + my_off_t filesize; + char cvtbuf[1024]; + size_t n_bytes; + + mysql_mutex_lock(&log->lock); + if (log->rotations > 0) + if ((filesize= my_tell(log->file, MYF(0))) == (my_off_t) -1 || + ((unsigned long long)filesize >= log->size_limit && + do_rotate(log))) + { + result= -1; + errno= my_errno; + goto exit; /* Log rotation needed but failed */ + } + + n_bytes= my_vsnprintf(cvtbuf, sizeof(cvtbuf), fmt, ap); + if (n_bytes >= sizeof(cvtbuf)) + n_bytes= sizeof(cvtbuf) - 1; + + result= my_write(log->file, (uchar *) cvtbuf, n_bytes, MYF(0)); + +exit: + mysql_mutex_unlock(&log->lock); + return result; +} + + +int logger_write(LOGGER_HANDLE *log, const char *buffer, size_t size) +{ + int result; + my_off_t filesize; + + mysql_mutex_lock(&log->lock); + if (log->rotations > 0) + if ((filesize= my_tell(log->file, MYF(0))) == (my_off_t) -1 || + ((unsigned long long)filesize >= log->size_limit && + do_rotate(log))) + { + result= -1; + errno= my_errno; + goto exit; /* Log rotation needed but failed */ + } + + result= my_write(log->file, (uchar *) buffer, size, MYF(0)); + +exit: + mysql_mutex_unlock(&log->lock); + return result; +} + + +int logger_rotate(LOGGER_HANDLE *log) +{ + int result; + mysql_mutex_lock(&log->lock); + result= do_rotate(log); + mysql_mutex_unlock(&log->lock); + return result; +} + + +int logger_printf(LOGGER_HANDLE *log, const char *fmt, ...) +{ + int result; + va_list args; + va_start(args,fmt); + result= logger_vprintf(log, fmt, args); + va_end(args); + return result; +} + +void logger_init_mutexes() +{ +#ifdef HAVE_PSI_INTERFACE + if (PSI_server) + PSI_server->register_mutex("sql_logger", mutex_list, 1); +#endif +} + diff --git a/mysys/hash.c b/mysys/hash.c index ef478a7175d..b93f6b666d6 100644 --- a/mysys/hash.c +++ b/mysys/hash.c @@ -77,6 +77,7 @@ _my_hash_init(HASH *hash, uint growth_size, CHARSET_INFO *charset, my_hash_get_key get_key, void (*free_element)(void*), uint flags) { + my_bool res; DBUG_ENTER("my_hash_init"); DBUG_PRINT("enter",("hash: 0x%lx size: %u", (long) hash, (uint) size)); @@ -88,8 +89,9 @@ _my_hash_init(HASH *hash, uint growth_size, CHARSET_INFO *charset, hash->free=free_element; hash->flags=flags; hash->charset=charset; - DBUG_RETURN(my_init_dynamic_array_ci(&hash->array, - sizeof(HASH_LINK), size, growth_size)); + res= my_init_dynamic_array_ci(&hash->array, + sizeof(HASH_LINK), size, growth_size); + DBUG_RETURN(res); } @@ -130,7 +132,8 @@ static inline void my_hash_free_elements(HASH *hash) void my_hash_free(HASH *hash) { DBUG_ENTER("my_hash_free"); - DBUG_PRINT("enter",("hash: 0x%lx", (long) hash)); + DBUG_PRINT("enter",("hash: 0x%lx elements: %ld", + (long) hash, hash->records)); my_hash_free_elements(hash); hash->free= 0; @@ -182,8 +185,9 @@ my_hash_key(const HASH *hash, const uchar *record, size_t *length, static uint my_hash_mask(my_hash_value_type hashnr, size_t buffmax, size_t maxlength) { - if ((hashnr & (buffmax-1)) < maxlength) return (hashnr & (buffmax-1)); - return (hashnr & ((buffmax >> 1) -1)); + if ((hashnr & (buffmax-1)) < maxlength) + return (uint) (hashnr & (buffmax-1)); + return (uint) (hashnr & ((buffmax >> 1) -1)); } static uint my_hash_rec_mask(const HASH *hash, HASH_LINK *pos, @@ -361,7 +365,13 @@ static int hashcmp(const HASH *hash, HASH_LINK *pos, const uchar *key, } - /* Write a hash-key to the hash-index */ +/** + Write a hash-key to the hash-index + + @return + @retval 0 ok + @retval 1 Duplicate key or out of memory +*/ my_bool my_hash_insert(HASH *info, const uchar *record) { @@ -371,7 +381,7 @@ my_bool my_hash_insert(HASH *info, const uchar *record) uchar *UNINIT_VAR(ptr_to_rec),*UNINIT_VAR(ptr_to_rec2); HASH_LINK *data,*empty,*UNINIT_VAR(gpos),*UNINIT_VAR(gpos2),*pos; - if (HASH_UNIQUE & info->flags) + if (info->flags & HASH_UNIQUE) { uchar *key= (uchar*) my_hash_key(info, record, &idx, 1); if (my_hash_search(info, key, idx)) @@ -495,16 +505,27 @@ my_bool my_hash_insert(HASH *info, const uchar *record) } -/****************************************************************************** -** Remove one record from hash-table. The record with the same record -** ptr is removed. -** if there is a free-function it's called for record if found -******************************************************************************/ +/** + Remove one record from hash-table. + + @fn hash_delete() + @param hash Hash tree + @param record Row to be deleted + + @notes + The record with the same record ptr is removed. + If there is a free-function it's called if record was found. + + @return + @retval 0 ok + @retval 1 Record not found +*/ my_bool my_hash_delete(HASH *hash, uchar *record) { - uint blength,pos2,idx,empty_index; + uint pos2,idx,empty_index; my_hash_value_type pos_hashnr, lastpos_hashnr; + size_t blength; HASH_LINK *data,*lastpos,*gpos,*pos,*pos3,*empty; DBUG_ENTER("my_hash_delete"); if (!hash->records) @@ -584,16 +605,17 @@ exit: DBUG_RETURN(0); } - /* - Update keys when record has changed. - This is much more efficent than using a delete & insert. - */ + +/** + Update keys when record has changed. + This is much more efficent than using a delete & insert. +*/ my_bool my_hash_update(HASH *hash, uchar *record, uchar *old_key, size_t old_key_length) { - uint new_index,new_pos_index,blength,records; - size_t idx,empty; + uint new_index,new_pos_index,records; + size_t idx, empty, blength; HASH_LINK org_link,*data,*previous,*pos; DBUG_ENTER("my_hash_update"); @@ -675,7 +697,7 @@ my_bool my_hash_update(HASH *hash, uchar *record, uchar *old_key, if (new_index != new_pos_index) { /* Other record in wrong position */ data[empty] = *pos; - movelink(data,new_index,new_pos_index,empty); + movelink(data,new_index,new_pos_index, (uint) empty); org_link.next=NO_RECORD; data[new_index]= org_link; } @@ -683,7 +705,7 @@ my_bool my_hash_update(HASH *hash, uchar *record, uchar *old_key, { /* Link in chain at right position */ org_link.next=data[new_index].next; data[empty]=org_link; - data[new_index].next=empty; + data[new_index].next= (uint) empty; } DBUG_RETURN(0); } @@ -710,13 +732,45 @@ void my_hash_replace(HASH *hash, HASH_SEARCH_STATE *current_record, } +/** + Iterate over all elements in hash and call function with the element + + @param hash hash array + @param action function to call for each argument + @param argument second argument for call to action + + @notes + If one of functions calls returns 1 then the iteration aborts + + @retval 0 ok + @retval 1 iteration aborted becasue action returned 1 +*/ + +my_bool my_hash_iterate(HASH *hash, my_hash_walk_action action, void *argument) +{ + uint records, i; + HASH_LINK *data; + + records= hash->records; + data= dynamic_element(&hash->array,0,HASH_LINK*); + + for (i= 0 ; i < records ; i++) + { + if ((*action)(data[i].data, argument)) + return 1; + } + return 0; +} + + #ifndef DBUG_OFF my_bool my_hash_check(HASH *hash) { int error; uint i,rec_link,found,max_links,seek,links,idx; - uint records,blength; + uint records; + size_t blength; HASH_LINK *data,*hash_info; records=hash->records; blength=hash->blength; diff --git a/mysys/lf_alloc-pin.c b/mysys/lf_alloc-pin.c index 7af87bca111..6ab6ba3aae0 100644 --- a/mysys/lf_alloc-pin.c +++ b/mysys/lf_alloc-pin.c @@ -115,7 +115,7 @@ void lf_pinbox_init(LF_PINBOX *pinbox, uint free_ptr_offset, lf_pinbox_free_func *free_func, void *free_func_arg) { DBUG_ASSERT(free_ptr_offset % sizeof(void *) == 0); - compile_time_assert(sizeof(LF_PINS) == 64); + compile_time_assert(sizeof(LF_PINS) == 128); lf_dynarray_init(&pinbox->pinarray, sizeof(LF_PINS)); pinbox->pinstack_top_ver= 0; pinbox->pins_in_array= 0; @@ -271,7 +271,7 @@ static int ptr_cmp(void **a, void **b) void _lf_pinbox_free(LF_PINS *pins, void *addr) { add_to_purgatory(pins, addr); - if (pins->purgatory_count % LF_PURGATORY_SIZE) + if (pins->purgatory_count % LF_PURGATORY_SIZE == 0) _lf_pinbox_real_free(pins); } @@ -459,6 +459,8 @@ void lf_alloc_init(LF_ALLOCATOR *allocator, uint size, uint free_ptr_offset) allocator->top= 0; allocator->mallocs= 0; allocator->element_size= size; + allocator->constructor= 0; + allocator->destructor= 0; DBUG_ASSERT(size >= sizeof(void*) + free_ptr_offset); } @@ -479,6 +481,8 @@ void lf_alloc_destroy(LF_ALLOCATOR *allocator) while (node) { uchar *tmp= anext_node(node); + if (allocator->destructor) + allocator->destructor(node); my_free(node); node= tmp; } @@ -507,6 +511,8 @@ void *_lf_alloc_new(LF_PINS *pins) if (!node) { node= (void *)my_malloc(allocator->element_size, MYF(MY_WME)); + if (allocator->constructor) + allocator->constructor(node); #ifdef MY_LF_EXTRA_DEBUG if (likely(node != 0)) my_atomic_add32(&allocator->mallocs, 1); diff --git a/mysys/lf_hash.c b/mysys/lf_hash.c index 4f0a3a32d86..38b212c65f0 100644 --- a/mysys/lf_hash.c +++ b/mysys/lf_hash.c @@ -41,6 +41,8 @@ typedef struct { */ } LF_SLIST; +const int LF_HASH_OVERHEAD= sizeof(LF_SLIST); + /* a structure to pass the context (pointers two the three successive elements in a list) from lfind to linsert/ldelete @@ -121,8 +123,8 @@ retry: we found a deleted node - be nice, help the other thread and remove this deleted node */ - if (my_atomic_casptr((void **)cursor->prev, - (void **)&cursor->curr, cursor->next)) + if (my_atomic_casptr((void **) cursor->prev, + (void **)(char*) &cursor->curr, cursor->next)) _lf_alloc_free(pins, cursor->curr); else { @@ -168,7 +170,8 @@ static LF_SLIST *linsert(LF_SLIST * volatile *head, CHARSET_INFO *cs, node->link= (intptr)cursor.curr; DBUG_ASSERT(node->link != (intptr)node); /* no circular references */ DBUG_ASSERT(cursor.prev != &node->link); /* no circular references */ - if (my_atomic_casptr((void **)cursor.prev, (void **)&cursor.curr, node)) + if (my_atomic_casptr((void **) cursor.prev, + (void **)(char*) &cursor.curr, node)) { res= 1; /* inserted ok */ break; @@ -215,13 +218,13 @@ static int ldelete(LF_SLIST * volatile *head, CHARSET_INFO *cs, uint32 hashnr, else { /* mark the node deleted */ - if (my_atomic_casptr((void **)&(cursor.curr->link), - (void **)&cursor.next, + if (my_atomic_casptr((void **) (char*) &(cursor.curr->link), + (void **) (char*) &cursor.next, (void *)(((intptr)cursor.next) | 1))) { /* and remove it from the list */ if (my_atomic_casptr((void **)cursor.prev, - (void **)&cursor.curr, cursor.next)) + (void **)(char*)&cursor.curr, cursor.next)) _lf_alloc_free(pins, cursor.curr); else { @@ -265,8 +268,10 @@ static LF_SLIST *lsearch(LF_SLIST * volatile *head, CHARSET_INFO *cs, int res= lfind(head, cs, hashnr, key, keylen, &cursor, pins); if (res) _lf_pin(pins, 2, cursor.curr); - _lf_unpin(pins, 0); + else + _lf_unpin(pins, 2); _lf_unpin(pins, 1); + _lf_unpin(pins, 0); return res ? cursor.curr : 0; } @@ -332,18 +337,18 @@ void lf_hash_destroy(LF_HASH *hash) { LF_SLIST *el, **head= (LF_SLIST **)_lf_dynarray_value(&hash->array, 0); - if (unlikely(!head)) - return; - el= *head; - - while (el) + if (head) { - intptr next= el->link; - if (el->hashnr & 1) - lf_alloc_direct_free(&hash->alloc, el); /* normal node */ - else - my_free(el); /* dummy node */ - el= (LF_SLIST *)next; + el= *head; + while (el) + { + intptr next= el->link; + if (el->hashnr & 1) + lf_alloc_direct_free(&hash->alloc, el); /* normal node */ + else + my_free(el); /* dummy node */ + el= (LF_SLIST *)next; + } } lf_alloc_destroy(&hash->alloc); lf_dynarray_destroy(&hash->array); @@ -490,7 +495,7 @@ static int initialize_bucket(LF_HASH *hash, LF_SLIST * volatile *node, my_free(dummy); dummy= cur; } - my_atomic_casptr((void **)node, (void **)&tmp, dummy); + my_atomic_casptr((void **)node, (void **)(char*) &tmp, dummy); /* note that if the CAS above failed (after linsert() succeeded), it would mean that some other thread has executed linsert() for diff --git a/mysys/ma_dyncol.c b/mysys/ma_dyncol.c new file mode 100644 index 00000000000..62227ab6834 --- /dev/null +++ b/mysys/ma_dyncol.c @@ -0,0 +1,2121 @@ +/* Copyright (c) 2011, Monty Program Ab + Copyright (c) 2011, Oleksandr Byelkin + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. +*/ + +#include "mysys_priv.h" +#include <m_string.h> +#include <ma_dyncol.h> + +/* + Flag byte bits + + 2 bits which determinate size of offset in the header -1 +*/ +/* mask to get above bits */ +#define DYNCOL_FLG_OFFSET 3 +/* All known flags mask */ +#define DYNCOL_FLG_KNOWN 3 + +/* dynamic column size reserve */ +#define DYNCOL_SYZERESERVE 80 + +/* length of fixed string header 1 byte - flags, 2 bytes - columns counter */ +#define FIXED_HEADER_SIZE 3 + +#define COLUMN_NUMBER_SIZE 2 + +#define MAX_OFFSET_LENGTH 5 + +static enum enum_dyncol_func_result +dynamic_column_time_store(DYNAMIC_COLUMN *str, + MYSQL_TIME *value); +static enum enum_dyncol_func_result +dynamic_column_date_store(DYNAMIC_COLUMN *str, + MYSQL_TIME *value); +static enum enum_dyncol_func_result +dynamic_column_time_read_internal(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length); +static enum enum_dyncol_func_result +dynamic_column_date_read_internal(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length); + +/** + Initialize dynamic column string with (make it empty but correct format) + + @param str The string to initialize + @param size Amount of preallocated memory for the string. + + @retval FALSE OK + @retval TRUE error +*/ + +static my_bool dynamic_column_init_str(DYNAMIC_COLUMN *str, size_t size) +{ + DBUG_ASSERT(size != 0); + + /* + Make string with no fields (empty header) + - First \0 is flags + - other 2 \0 is number of fields + */ + if (init_dynamic_string(str, NULL, + size + FIXED_HEADER_SIZE, DYNCOL_SYZERESERVE)) + return TRUE; + bzero(str->str, FIXED_HEADER_SIZE); + str->length= FIXED_HEADER_SIZE; + return FALSE; +} + + +/** + Calculate how many bytes needed to store val as variable length integer + where first bit indicate continuation of the sequence. + + @param val The value for which we are calculating length + + @return number of bytes +*/ + +static size_t dynamic_column_var_uint_bytes(ulonglong val) +{ + size_t len= 0; + do + { + len++; + val>>= 7; + } while (val); + return len; +} + + +/** + Stores variable length unsigned integer value to a string + + @param str The string where to append the value + @param val The value to put in the string + + @return ER_DYNCOL_* return code + + @notes + This is used to store a number together with other data in the same + object. (Like decimals, length of string etc) + (As we don't know the length of this object, we can't store 0 in 0 bytes) +*/ + +static enum enum_dyncol_func_result +dynamic_column_var_uint_store(DYNAMIC_COLUMN *str, ulonglong val) +{ + if (dynstr_realloc(str, 10)) /* max what we can use */ + return ER_DYNCOL_RESOURCE; + + do + { + ulonglong rest= val >> 7; + str->str[str->length++]= ((val & 0x7f) | (rest ? 0x80 : 0x00)); + val= rest; + } while (val); + return ER_DYNCOL_OK; +} + + +/** + Reads variable length unsigned integer value from a string + + @param data The string from which the int should be read + @param data_length Max length of data + @param len Where to put length of the string read in bytes + + @return value of the unsigned integer read from the string + + In case of error, *len is set to 0 +*/ + +static ulonglong +dynamic_column_var_uint_get(uchar *data, size_t data_length, + size_t *len) +{ + ulonglong val= 0; + uint length; + uchar *end= data + data_length; + + for (length=0; data < end ; data++) + { + val+= (((ulonglong)((*data) & 0x7f)) << (length * 7)); + length++; + if (!((*data) & 0x80)) + { + /* End of data */ + *len= length; + return val; + } + } + /* Something was wrong with data */ + *len= 0; /* Mark error */ + return 0; +} + + +/** + Calculate how many bytes needed to store val as unsigned. + + @param val The value for which we are calculating length + + @return number of bytes (0-8) +*/ + +static size_t dynamic_column_uint_bytes(ulonglong val) +{ + size_t len; + + for (len= 0; val ; val>>= 8, len++) + ; + return len; +} + + +/** + Append the string with given unsigned int value. + + @param str The string where to put the value + @param val The value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_uint_store(DYNAMIC_COLUMN *str, ulonglong val) +{ + if (dynstr_realloc(str, 8)) /* max what we can use */ + return ER_DYNCOL_RESOURCE; + + for (; val; val>>= 8) + str->str[str->length++]= (char) (val & 0xff); + return ER_DYNCOL_OK; +} + + +/** + Read unsigned int value of given length from the string + + @param store_it_here The structure to store the value + @param data The string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_uint_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + ulonglong value= 0; + size_t i; + + for (i= 0; i < length; i++) + value+= ((ulonglong)data[i]) << (i*8); + + store_it_here->x.ulong_value= value; + return ER_DYNCOL_OK; +} + +/** + Calculate how many bytes needed to store val as signed in following encoding: + 0 -> 0 + -1 -> 1 + 1 -> 2 + -2 -> 3 + 2 -> 4 + ... + + @param val The value for which we are calculating length + + @return number of bytes +*/ + +static size_t dynamic_column_sint_bytes(longlong val) +{ + return dynamic_column_uint_bytes((val << 1) ^ + (val < 0 ? ULL(0xffffffffffffffff) : 0)); +} + + +/** + Append the string with given signed int value. + + @param str the string where to put the value + @param val the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_sint_store(DYNAMIC_COLUMN *str, longlong val) +{ + return dynamic_column_uint_store(str, + (val << 1) ^ + (val < 0 ? ULL(0xffffffffffffffff) : 0)); +} + + +/** + Read signed int value of given length from the string + + @param store_it_here The structure to store the value + @param data The string which should be read + @param length The length (in bytes) of the value in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_sint_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + ulonglong val; + dynamic_column_uint_read(store_it_here, data, length); + val= store_it_here->x.ulong_value; + if (val & 1) + val= (val >> 1) ^ ULL(0xffffffffffffffff); + else + val>>= 1; + store_it_here->x.long_value= (longlong) val; + return ER_DYNCOL_OK; +} + + +/** + Calculate how many bytes needed to store the value. + + @param value The value for which we are calculating length + + @return + Error: (size_t) ~0 + ok number of bytes +*/ + +static size_t +dynamic_column_value_len(DYNAMIC_COLUMN_VALUE *value) +{ + switch (value->type) { + case DYN_COL_NULL: + return 0; + case DYN_COL_INT: + return dynamic_column_sint_bytes(value->x.long_value); + case DYN_COL_UINT: + return dynamic_column_uint_bytes(value->x.ulong_value); + case DYN_COL_DOUBLE: + return 8; + case DYN_COL_STRING: + return (dynamic_column_var_uint_bytes(value->x.string.charset->number) + + value->x.string.value.length); + case DYN_COL_DECIMAL: + { + int precision= value->x.decimal.value.intg + value->x.decimal.value.frac; + int scale= value->x.decimal.value.frac; + + if (precision == 0 || decimal_is_zero(&value->x.decimal.value)) + { + /* This is here to simplify dynamic_column_decimal_store() */ + value->x.decimal.value.intg= value->x.decimal.value.frac= 0; + return 0; + } + /* + Check if legal decimal; This is needed to not get an assert in + decimal_bin_size(). However this should be impossible as all + decimals entered here should be valid and we have the special check + above to handle the unlikely but possible case that decimal.value.intg + and decimal.frac is 0. + */ + if (scale < 0 || precision <= 0) + { + DBUG_ASSERT(0); /* Impossible */ + return (size_t) ~0; + } + return (dynamic_column_var_uint_bytes(value->x.decimal.value.intg) + + dynamic_column_var_uint_bytes(value->x.decimal.value.frac) + + decimal_bin_size(precision, scale)); + } + case DYN_COL_DATETIME: + /* date+time in bits: 14 + 4 + 5 + 10 + 6 + 6 + 20 + 1 66bits ~= 9 bytes */ + return 9; + case DYN_COL_DATE: + /* date in dits: 14 + 4 + 5 = 23bits ~= 3bytes*/ + return 3; + case DYN_COL_TIME: + /* time in bits: 10 + 6 + 6 + 20 + 1 = 43bits ~= 6bytes*/ + return 6; + } + DBUG_ASSERT(0); + return 0; +} + + +/** + Append double value to a string + + @param str the string where to put the value + @param val the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_double_store(DYNAMIC_COLUMN *str, double val) +{ + if (dynstr_realloc(str, 8)) + return ER_DYNCOL_RESOURCE; + float8store(str->str + str->length, val); + str->length+= 8; + return ER_DYNCOL_OK; +} + + +/** + Read double value of given length from the string + + @param store_it_here The structure to store the value + @param data The string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_double_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + if (length != 8) + return ER_DYNCOL_FORMAT; + float8get(store_it_here->x.double_value, data); + return ER_DYNCOL_OK; +} + + +/** + Append the string with given string value. + + @param str the string where to put the value + @param val the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_string_store(DYNAMIC_COLUMN *str, LEX_STRING *string, + CHARSET_INFO *charset) +{ + enum enum_dyncol_func_result rc; + if ((rc= dynamic_column_var_uint_store(str, charset->number))) + return rc; + if (dynstr_append_mem(str, string->str, string->length)) + return ER_DYNCOL_RESOURCE; + return ER_DYNCOL_OK; +} + + +/** + Read string value of given length from the packed string + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_string_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + size_t len; + uint charset_nr= (uint)dynamic_column_var_uint_get(data, length, &len); + if (len == 0) /* Wrong packed number */ + return ER_DYNCOL_FORMAT; + store_it_here->x.string.charset= get_charset(charset_nr, MYF(MY_WME)); + if (store_it_here->x.string.charset == NULL) + return ER_DYNCOL_UNKNOWN_CHARSET; + data+= len; + store_it_here->x.string.value.length= (length-= len); + store_it_here->x.string.value.str= (char*) data; + return ER_DYNCOL_OK; +} + + +/** + Append the string with given decimal value. + + @param str the string where to put the value + @param val the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_decimal_store(DYNAMIC_COLUMN *str, + decimal_t *value) +{ + uint bin_size; + int precision= value->intg + value->frac; + + /* Store decimal zero as empty string */ + if (precision == 0) + return ER_DYNCOL_OK; + + bin_size= decimal_bin_size(precision, value->frac); + if (dynstr_realloc(str, bin_size + 20)) + return ER_DYNCOL_RESOURCE; + + /* The following can't fail as memory is already allocated */ + (void) dynamic_column_var_uint_store(str, value->intg); + (void) dynamic_column_var_uint_store(str, value->frac); + + decimal2bin(value, (uchar *) str->str + str->length, + precision, value->frac); + str->length+= bin_size; + return ER_DYNCOL_OK; +} + + +/** + Prepare the value to be used as decimal. + + @param value The value structure which sould be setup. +*/ + +void dynamic_column_prepare_decimal(DYNAMIC_COLUMN_VALUE *value) +{ + value->x.decimal.value.buf= value->x.decimal.buffer; + value->x.decimal.value.len= DECIMAL_BUFF_LENGTH; + /* just to be safe */ + value->type= DYN_COL_DECIMAL; + decimal_make_zero(&value->x.decimal.value); +} + + +/** + Read decimal value of given length from the string + + @param store_it_here The structure to store the value + @param data The string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_decimal_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + size_t intg_len, frac_len; + int intg, frac, precision, scale; + + dynamic_column_prepare_decimal(store_it_here); + /* Decimals 0.0 is stored as a zero length string */ + if (length == 0) + return ER_DYNCOL_OK; /* value contains zero */ + + intg= (int)dynamic_column_var_uint_get(data, length, &intg_len); + data+= intg_len; + frac= (int)dynamic_column_var_uint_get(data, length - intg_len, &frac_len); + data+= frac_len; + + /* Check the size of data is correct */ + precision= intg + frac; + scale= frac; + if (scale < 0 || precision <= 0 || scale > precision || + (length - intg_len - frac_len) > + (size_t) (DECIMAL_BUFF_LENGTH*sizeof(decimal_digit_t)) || + decimal_bin_size(intg + frac, frac) != + (int) (length - intg_len - frac_len)) + return ER_DYNCOL_FORMAT; + + if (bin2decimal(data, &store_it_here->x.decimal.value, precision, scale) != + E_DEC_OK) + return ER_DYNCOL_FORMAT; + return ER_DYNCOL_OK; +} + + +/** + Append the string with given datetime value. + + @param str the string where to put the value + @param value the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_time_store(DYNAMIC_COLUMN *str, MYSQL_TIME *value) +{ + enum enum_dyncol_func_result rc; + /* + 0<----year----><mn><day>00000!<-hours--><min-><sec-><---microseconds---> + 12345678901234123412345 1123456789012345612345612345678901234567890 + <123456><123456><123456><123456><123456><123456><123456><123456><123456> + */ + if ((rc= dynamic_column_date_store(str, value)) || + (rc= dynamic_column_time_store(str, value))) + return rc; + return ER_DYNCOL_OK; +} + + +/** + Read datetime value of given length from the packed string + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_time_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + enum enum_dyncol_func_result rc= ER_DYNCOL_FORMAT; + /* + 0<----year----><mn><day>00000!<-hours--><min-><sec-><---microseconds---> + 12345678901234123412345 1123456789012345612345612345678901234567890 + <123456><123456><123456><123456><123456><123456><123456><123456><123456> + */ + if (length != 9) + goto err; + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_DATETIME; + if ((rc= dynamic_column_date_read_internal(store_it_here, data, 3)) || + (rc= dynamic_column_time_read_internal(store_it_here, data + 3, 6))) + goto err; + return ER_DYNCOL_OK; + +err: + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_ERROR; + return rc; +} + + +/** + Append the string with given time value. + + @param str the string where to put the value + @param value the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_time_store(DYNAMIC_COLUMN *str, MYSQL_TIME *value) +{ + uchar *buf; + if (dynstr_realloc(str, 6)) + return ER_DYNCOL_RESOURCE; + + buf= ((uchar *)str->str) + str->length; + + if (value->time_type == MYSQL_TIMESTAMP_NONE || + value->time_type == MYSQL_TIMESTAMP_ERROR || + value->time_type == MYSQL_TIMESTAMP_DATE) + { + value->neg= 0; + value->second_part= 0; + value->hour= 0; + value->minute= 0; + value->second= 0; + } + DBUG_ASSERT(value->hour <= 838); + DBUG_ASSERT(value->minute <= 59); + DBUG_ASSERT(value->second <= 59); + DBUG_ASSERT(value->second_part <= 999999); + /* + 00000!<-hours--><min-><sec-><---microseconds---> + 1123456789012345612345612345678901234567890 + <123456><123456><123456><123456><123456><123456> + */ + buf[0]= (value->second_part & 0xff); + buf[1]= ((value->second_part & 0xff00) >> 8); + buf[2]= (uchar)(((value->second & 0xf) << 4) | + ((value->second_part & 0xf0000) >> 16)); + buf[3]= ((value->minute << 2) | ((value->second & 0x30) >> 4)); + buf[4]= (value->hour & 0xff); + buf[5]= ((value->neg ? 0x4 : 0) | (value->hour >> 8)); + str->length+= 6; + return ER_DYNCOL_OK; +} + + +/** + Read time value of given length from the packed string + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_time_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + store_it_here->x.time_value.year= store_it_here->x.time_value.month= + store_it_here->x.time_value.day= 0; + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_TIME; + return dynamic_column_time_read_internal(store_it_here, data, length); +} + +/** + Internal function for reading time part from the string. + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_time_read_internal(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + if (length != 6) + goto err; + /* + 00000!<-hours--><min-><sec-><---microseconds---> + 1123456789012345612345612345678901234567890 + <123456><123456><123456><123456><123456><123456> + */ + store_it_here->x.time_value.second_part= (data[0] | + (data[1] << 8) | + ((data[2] & 0xf) << 16)); + store_it_here->x.time_value.second= ((data[2] >> 4) | + ((data[3] & 0x3) << 4)); + store_it_here->x.time_value.minute= (data[3] >> 2); + store_it_here->x.time_value.hour= (((((uint)data[5]) & 0x3 ) << 8) | data[4]); + store_it_here->x.time_value.neg= ((data[5] & 0x4) ? 1 : 0); + if (store_it_here->x.time_value.second > 59 || + store_it_here->x.time_value.minute > 59 || + store_it_here->x.time_value.hour > 838 || + store_it_here->x.time_value.second_part > 999999) + goto err; + return ER_DYNCOL_OK; + +err: + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_ERROR; + return ER_DYNCOL_FORMAT; +} + + +/** + Append the string with given date value. + + @param str the string where to put the value + @param value the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_store(DYNAMIC_COLUMN *str, MYSQL_TIME *value) +{ + uchar *buf; + if (dynstr_realloc(str, 3)) + return ER_DYNCOL_RESOURCE; + + buf= ((uchar *)str->str) + str->length; + if (value->time_type == MYSQL_TIMESTAMP_NONE || + value->time_type == MYSQL_TIMESTAMP_ERROR || + value->time_type == MYSQL_TIMESTAMP_TIME) + value->year= value->month= value->day = 0; + DBUG_ASSERT(value->year <= 9999); + DBUG_ASSERT(value->month <= 12); + DBUG_ASSERT(value->day <= 31); + /* + 0<----year----><mn><day> + 012345678901234123412345 + <123456><123456><123456> + */ + buf[0]= (value->day | + ((value->month & 0x7) << 5)); + buf[1]= ((value->month >> 3) | ((value->year & 0x7F) << 1)); + buf[2]= (value->year >> 7); + str->length+= 3; + return ER_DYNCOL_OK; +} + + + +/** + Read date value of given length from the packed string + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + store_it_here->x.time_value.neg= 0; + store_it_here->x.time_value.second_part= 0; + store_it_here->x.time_value.hour= 0; + store_it_here->x.time_value.minute= 0; + store_it_here->x.time_value.second= 0; + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_DATE; + return dynamic_column_date_read_internal(store_it_here, data, length); +} + +/** + Internal function for reading date part from the string. + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_read_internal(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, + size_t length) +{ + if (length != 3) + goto err; + /* + 0<----year----><mn><day> + 12345678901234123412345 + <123456><123456><123456> + */ + store_it_here->x.time_value.day= (data[0] & 0x1f); + store_it_here->x.time_value.month= (((data[1] & 0x1) << 3) | + (data[0] >> 5)); + store_it_here->x.time_value.year= ((((uint)data[2]) << 7) | + (data[1] >> 1)); + if (store_it_here->x.time_value.day > 31 || + store_it_here->x.time_value.month > 12 || + store_it_here->x.time_value.year > 9999) + goto err; + return ER_DYNCOL_OK; + +err: + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_ERROR; + return ER_DYNCOL_FORMAT; +} + + +/** + Append the string with given value. + + @param str the string where to put the value + @param value the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +data_store(DYNAMIC_COLUMN *str, DYNAMIC_COLUMN_VALUE *value) +{ + switch (value->type) { + case DYN_COL_INT: + return dynamic_column_sint_store(str, value->x.long_value); + case DYN_COL_UINT: + return dynamic_column_uint_store(str, value->x.ulong_value); + case DYN_COL_DOUBLE: + return dynamic_column_double_store(str, value->x.double_value); + case DYN_COL_STRING: + return dynamic_column_string_store(str, &value->x.string.value, + value->x.string.charset); + case DYN_COL_DECIMAL: + return dynamic_column_decimal_store(str, &value->x.decimal.value); + case DYN_COL_DATETIME: + /* date+time in bits: 14 + 4 + 5 + 5 + 6 + 6 40bits = 5 bytes */ + return dynamic_column_date_time_store(str, &value->x.time_value); + case DYN_COL_DATE: + /* date in dits: 14 + 4 + 5 = 23bits ~= 3bytes*/ + return dynamic_column_date_store(str, &value->x.time_value); + case DYN_COL_TIME: + /* time in bits: 5 + 6 + 6 = 17bits ~= 3bytes*/ + return dynamic_column_time_store(str, &value->x.time_value); + case DYN_COL_NULL: + break; /* Impossible */ + } + DBUG_ASSERT(0); + return ER_DYNCOL_OK; /* Impossible */ +} + + +/** + Calculate length of offset field for given data length + + @param data_length Length of the data segment + + @return number of bytes +*/ + +static size_t dynamic_column_offset_bytes(size_t data_length) +{ + if (data_length < 0x1f) /* all 1 value is reserved */ + return 1; + if (data_length < 0x1fff) /* all 1 value is reserved */ + return 2; + if (data_length < 0x1fffff) /* all 1 value is reserved */ + return 3; + if (data_length < 0x1fffffff) /* all 1 value is reserved */ + return 4; + return MAX_OFFSET_LENGTH; /* For future */ +} + +/** + Store offset and type information in the given place + + @param place Beginning of the index entry + @param offset_size Size of offset field in bytes + @param type Type to be written + @param offset Offset to be written +*/ + +static void type_and_offset_store(uchar *place, size_t offset_size, + DYNAMIC_COLUMN_TYPE type, + size_t offset) +{ + ulong val = (((ulong) offset) << 3) | (type - 1); + DBUG_ASSERT(type != DYN_COL_NULL); + DBUG_ASSERT(((type - 1) & (~7)) == 0); /* fit in 3 bits */ + + /* Index entry starts with column number; Jump over it */ + place+= COLUMN_NUMBER_SIZE; + + switch (offset_size) { + case 1: + DBUG_ASSERT(offset < 0x1f); /* all 1 value is reserved */ + place[0]= (uchar)val; + break; + case 2: + DBUG_ASSERT(offset < 0x1fff); /* all 1 value is reserved */ + int2store(place, val); + break; + case 3: + DBUG_ASSERT(offset < 0x1fffff); /* all 1 value is reserved */ + int3store(place, val); + break; + case 4: + DBUG_ASSERT(offset < 0x1fffffff); /* all 1 value is reserved */ + int4store(place, val); + break; + default: + DBUG_ASSERT(0); /* impossible */ + } +} + + +/** + Read offset and type information from index entry + + @param type Where to put type info + @param offset Where to put offset info + @param place Beginning of the index entry + @param offset_size Size of offset field in bytes +*/ + +static void type_and_offset_read(DYNAMIC_COLUMN_TYPE *type, + size_t *offset, + uchar *place, size_t offset_size) +{ + ulong UNINIT_VAR(val); + + place+= COLUMN_NUMBER_SIZE; /* skip column number */ + switch (offset_size) { + case 1: + val= (ulong)place[0]; + break; + case 2: + val= uint2korr(place); + break; + case 3: + val= uint3korr(place); + break; + case 4: + val= uint4korr(place); + break; + default: + DBUG_ASSERT(0); /* impossible */ + } + *type= (val & 0x7) + 1; + *offset= val >> 3; +} + + +/** + Comparator function for references on column numbers for qsort +*/ + +static int column_sort(const void *a, const void *b) +{ + return **((uint **)a) - **((uint **)b); +} + + +/** + Write information to the fixed header + + @param str String where to write the header + @param offset_size Size of offset field in bytes + @param column_count Number of columns +*/ + +static void set_fixed_header(DYNAMIC_COLUMN *str, + uint offset_size, + uint column_count) +{ + DBUG_ASSERT(column_count <= 0xffff); + DBUG_ASSERT(offset_size <= 4); + str->str[0]= ((str->str[0] & ~DYNCOL_FLG_OFFSET) | + (offset_size - 1)); /* size of offset */ + int2store(str->str + 1, column_count); /* columns number */ + DBUG_ASSERT((str->str[0] & (~DYNCOL_FLG_KNOWN)) == 0); +} + +/* + Calculate entry size (E) and header size (H) by offset size (O) and column + count (C). +*/ + +#define calc_param(E,H,O,C) do { \ + (*(E))= (O) + COLUMN_NUMBER_SIZE; \ + (*(H))= (*(E)) * (C); \ +}while(0); + + +/** + Adds columns into the empty string + + @param str String where to write the data + @param header_size Size of the header without fixed part + @param offset_size Size of offset field in bytes + @param column_count Number of columns in the arrays + @parem not_null_count Number of non-null columns in the arrays + @param data_size Size of the data segment + @param column_numbers Array of columns numbers + @param values Array of columns values + @param new_str True if we need to allocate new string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_new_column_store(DYNAMIC_COLUMN *str, + size_t header_size, + size_t offset_size, + uint column_count, + uint not_null_count, + size_t data_size, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values, + my_bool new_str) +{ + uchar *header_end; + uint **columns_order; + uint i; + uint entry_size= COLUMN_NUMBER_SIZE + offset_size; + enum enum_dyncol_func_result rc= ER_DYNCOL_RESOURCE; + + if (!(columns_order= malloc(sizeof(uint*)*column_count))) + return ER_DYNCOL_RESOURCE; + if (new_str) + { + if (dynamic_column_init_str(str, + data_size + header_size + DYNCOL_SYZERESERVE)) + goto err; + } + else + { + str->length= 0; + if (dynstr_realloc(str, data_size + header_size + DYNCOL_SYZERESERVE)) + goto err; + bzero(str->str, FIXED_HEADER_SIZE); + str->length= FIXED_HEADER_SIZE; + } + + /* sort columns for the header */ + for (i= 0; i < column_count; i++) + columns_order[i]= column_numbers + i; + qsort(columns_order, (size_t)column_count, sizeof(uint*), &column_sort); + + /* + For now we don't allow creating two columns with the same number + at the time of create. This can be fixed later to just use the later + by comparing the pointers. + */ + for (i= 0; i < column_count - 1; i++) + { + if (columns_order[i][0] > UINT_MAX16 || + columns_order[i][0] == columns_order[i + 1][0]) + { + rc= ER_DYNCOL_DATA; + goto err; + } + } + if (columns_order[i][0] > UINT_MAX16) + { + rc= ER_DYNCOL_DATA; + goto err; + } + + DBUG_ASSERT(str->max_length >= str->length + header_size); + set_fixed_header(str, offset_size, not_null_count); + str->length+= header_size; /* reserve place for header */ + header_end= (uchar *)str->str + FIXED_HEADER_SIZE; + for (i= 0; i < column_count; i++) + { + uint ord= columns_order[i] - column_numbers; + if (values[ord].type != DYN_COL_NULL) + { + /* Store header first in the str */ + int2store(header_end, column_numbers[ord]); + type_and_offset_store(header_end, offset_size, + values[ord].type, + str->length - header_size - FIXED_HEADER_SIZE); + + /* Store value in 'str + str->length' and increase str->length */ + if ((rc= data_store(str, values + ord))) + goto err; + header_end+= entry_size; + } + } + rc= ER_DYNCOL_OK; +err: + free(columns_order); + return rc; +} + +/** + Create packed string which contains given columns (internal) + + @param str String where to write the data + @param column_count Number of columns in the arrays + @param column_numbers Array of columns numbers + @param values Array of columns values + @param new_str True if we need allocate new string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_create_many_internal(DYNAMIC_COLUMN *str, + uint column_count, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values, + my_bool new_str) +{ + size_t data_size= 0; + size_t header_size, offset_size; + uint i; + int not_null_column_count= 0; + + if (new_str) + { + /* to make dynstr_free() working in case of errors */ + bzero(str, sizeof(DYNAMIC_COLUMN)); + } + + for (i= 0; i < column_count; i++) + { + if (values[i].type != DYN_COL_NULL) + { + size_t tmp; + not_null_column_count++; + data_size+= (tmp=dynamic_column_value_len(values + i)); + if (tmp == (size_t) ~0) + return ER_DYNCOL_DATA; + } + } + + /* We can handle data up to 1fffffff = 536870911 bytes now */ + if ((offset_size= dynamic_column_offset_bytes(data_size)) >= + MAX_OFFSET_LENGTH) + return ER_DYNCOL_LIMIT; + + /* header entry is column number + offset & type */ + header_size= not_null_column_count * (offset_size + 2); + + return dynamic_new_column_store(str, + header_size, offset_size, + column_count, + not_null_column_count, + data_size, + column_numbers, values, + new_str); +} + + +/** + Create packed string which contains given columns + + @param str String where to write the data + @param column_count Number of columns in the arrays + @param column_numbers Array of columns numbers + @param values Array of columns values + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +dynamic_column_create_many(DYNAMIC_COLUMN *str, + uint column_count, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values) +{ + DBUG_ENTER("dynamic_column_create_many"); + DBUG_RETURN(dynamic_column_create_many_internal(str, column_count, + column_numbers, values, + TRUE)); +} + + +/** + Create packed string which contains given column + + @param str String where to write the data + @param column_number Column number + @param value The columns value + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +dynamic_column_create(DYNAMIC_COLUMN *str, uint column_nr, + DYNAMIC_COLUMN_VALUE *value) +{ + DBUG_ENTER("dynamic_column_create"); + DBUG_RETURN(dynamic_column_create_many(str, 1, &column_nr, value)); +} + + +/** + Calculate length of data between given two header entries + + @param entry Pointer to the first entry + @param entry_next Pointer to the last entry + @param header_end Pointer to the header end + @param offset_size Size of offset field in bytes + @param last_offset Size of the data segment + @param error Set in case of error + + @return number of bytes +*/ + +static size_t get_length_interval(uchar *entry, uchar *entry_next, + uchar *header_end, size_t offset_size, + size_t last_offset, my_bool *error) +{ + size_t offset, offset_next; + DYNAMIC_COLUMN_TYPE type, type_next; + DBUG_ASSERT(entry < entry_next); + + type_and_offset_read(&type, &offset, entry, offset_size); + if (entry_next >= header_end) + { + *error= 0; + return (last_offset - offset); + } + type_and_offset_read(&type_next, &offset_next, entry_next, offset_size); + *error= (offset_next > last_offset); + return (offset_next - offset); +} + +/* + Calculate length of data of one column + + + @param entry Pointer to the first entry + @param header_end Pointer to the header end + @param offset_size Size of offset field in bytes + @param last_offset Size of the data segment + @param error Set in case of error + + @return number of bytes +*/ + +static size_t get_length(uchar *entry, uchar *header_end, + size_t offset_size, + size_t last_offset, my_bool *error) +{ + return get_length_interval(entry, + entry + offset_size + COLUMN_NUMBER_SIZE, + header_end, offset_size, last_offset, error); +} + + +/** + Comparator function for references to header entries for qsort +*/ + +static int header_compar(const void *a, const void *b) +{ + uint va= uint2korr((uchar*)a), vb= uint2korr((uchar*)b); + return (va > vb ? 1 : (va < vb ? -1 : 0)); +} + + +/** + Find column and fill information about it + + @param type Returns type of the column + @param data Returns a pointer to the data + @param length Returns length of the data + @param offset_size Size of offset field in bytes + @param column_count Number of column in the packed string + @param data_end Pointer to the data end + @param num Number of the column we want to fetch + @param entry_pos NULL or place where to put reference to the entry + + @return 0 ok + @return 1 error in data +*/ + +static my_bool +find_column(DYNAMIC_COLUMN_TYPE *type, uchar **data, size_t *length, + uchar *header, size_t offset_size, uint column_count, + uchar *data_end, uint num, uchar **entry_pos) +{ + uchar *entry; + size_t offset, total_data, header_size, entry_size; + uchar key[2+4]; + my_bool error; + + if (!entry_pos) + entry_pos= &entry; + + calc_param(&entry_size, &header_size, offset_size, column_count); + + if (header + header_size > data_end) + return 1; + + int2store(key, num); + entry= bsearch(key, header, (size_t)column_count, entry_size, + &header_compar); + if (!entry) + { + /* Column not found */ + *type= DYN_COL_NULL; + *entry_pos= NULL; + return 0; + } + type_and_offset_read(type, &offset, entry, offset_size); + total_data= data_end - (header + header_size); + if (offset > total_data) + return 1; + *data= header + header_size + offset; + *length= get_length(entry, header + header_size, offset_size, + total_data, &error); + /* + Check that the found data is withing the ranges. This can happen if + we get data with wrong offsets. + */ + if (error || (long) *length < 0 || offset + *length > total_data) + return 1; + + *entry_pos= entry; + return 0; +} + + +/** + Read and check the header of the dynamic string + + @param str Dynamic string + + @retval FALSE OK + @retval TRUE error + + Note + We don't check for str->length == 0 as all code that calls this + already have handled this case. +*/ + +static inline my_bool read_fixed_header(DYNAMIC_COLUMN *str, + size_t *offset_size, + uint *column_count) +{ + DBUG_ASSERT(str != NULL && str->length != 0); + if ((str->length < FIXED_HEADER_SIZE) || + (str->str[0] & (~DYNCOL_FLG_KNOWN))) + return 1; /* Wrong header */ + *offset_size= (str->str[0] & DYNCOL_FLG_OFFSET) + 1; + *column_count= uint2korr(str->str + 1); + return 0; +} + + +/** + Get dynamic column value + + @param str The packed string to extract the column + @param column_nr Number of column to fetch + @param store_it_here Where to store the extracted value + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +dynamic_column_get(DYNAMIC_COLUMN *str, uint column_nr, + DYNAMIC_COLUMN_VALUE *store_it_here) +{ + uchar *data; + size_t offset_size, length; + uint column_count; + enum enum_dyncol_func_result rc= ER_DYNCOL_FORMAT; + + if (str->length == 0) + goto null; + + if (read_fixed_header(str, &offset_size, &column_count)) + goto err; + + if (column_count == 0) + goto null; + + if (find_column(&store_it_here->type, &data, &length, + (uchar*)str->str + FIXED_HEADER_SIZE, + offset_size, column_count, (uchar*)str->str + str->length, + column_nr, NULL)) + goto err; + + switch (store_it_here->type) { + case DYN_COL_INT: + rc= dynamic_column_sint_read(store_it_here, data, length); + break; + case DYN_COL_UINT: + rc= dynamic_column_uint_read(store_it_here, data, length); + break; + case DYN_COL_DOUBLE: + rc= dynamic_column_double_read(store_it_here, data, length); + break; + case DYN_COL_STRING: + rc= dynamic_column_string_read(store_it_here, data, length); + break; + case DYN_COL_DECIMAL: + rc= dynamic_column_decimal_read(store_it_here, data, length); + break; + case DYN_COL_DATETIME: + rc= dynamic_column_date_time_read(store_it_here, data, length); + break; + case DYN_COL_DATE: + rc= dynamic_column_date_read(store_it_here, data, length); + break; + case DYN_COL_TIME: + rc= dynamic_column_time_read(store_it_here, data, length); + break; + case DYN_COL_NULL: + rc= ER_DYNCOL_OK; + break; + default: + goto err; + } + return rc; + +null: + rc= ER_DYNCOL_OK; +err: + store_it_here->type= DYN_COL_NULL; + return rc; +} + +/** + Delete column with given number from the packed string + + @param str The packed string to delete the column + @param column_nr Number of column to delete + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +dynamic_column_delete(DYNAMIC_COLUMN *str, uint column_nr) +{ + uchar *data, *header_entry, *read, *write; + size_t offset_size, new_offset_size, length, entry_size, new_entry_size, + header_size, new_header_size, data_size, new_data_size, + deleted_entry_offset; + uint column_count, i; + DYNAMIC_COLUMN_TYPE type; + + if (str->length == 0) + return ER_DYNCOL_OK; /* no columns */ + + if (read_fixed_header(str, &offset_size, &column_count)) + return ER_DYNCOL_FORMAT; + + if (column_count == 0) + { + str->length= 0; + return ER_DYNCOL_OK; /* no columns */ + } + + if (find_column(&type, &data, &length, (uchar*)str->str + FIXED_HEADER_SIZE, + offset_size, column_count, (uchar*)str->str + str->length, + column_nr, &header_entry)) + return ER_DYNCOL_FORMAT; + + if (type == DYN_COL_NULL) + return ER_DYNCOL_OK; /* no such column */ + + if (column_count == 1) + { + /* delete the only column; Return empty string */ + str->length= 0; + return ER_DYNCOL_OK; + } + + /* Calculate entry_size and header_size */ + calc_param(&entry_size, &header_size, offset_size, column_count); + data_size= str->length - FIXED_HEADER_SIZE - header_size; + + new_data_size= data_size - length; + if ((new_offset_size= dynamic_column_offset_bytes(new_data_size)) >= + MAX_OFFSET_LENGTH) + return ER_DYNCOL_LIMIT; + DBUG_ASSERT(new_offset_size <= offset_size); + + calc_param(&new_entry_size, &new_header_size, + new_offset_size, column_count - 1); + + deleted_entry_offset= ((data - (uchar*) str->str) - + header_size - FIXED_HEADER_SIZE); + + /* rewrite header*/ + set_fixed_header(str, new_offset_size, column_count - 1); + for (i= 0, write= read= (uchar *)str->str + FIXED_HEADER_SIZE; + i < column_count; + i++, read+= entry_size, write+= new_entry_size) + { + size_t offs; + uint nm; + DYNAMIC_COLUMN_TYPE tp; + if (read == header_entry) + { +#ifndef DBUG_OFF + nm= uint2korr(read); + type_and_offset_read(&tp, &offs, read, + offset_size); + DBUG_ASSERT(nm == column_nr); + DBUG_ASSERT(offs == deleted_entry_offset); +#endif + write-= new_entry_size; /* do not move writer */ + continue; /* skip removed field */ + } + + nm= uint2korr(read), + type_and_offset_read(&tp, &offs, read, + offset_size); + + if (offs > deleted_entry_offset) + offs-= length; /* data stored after removed data */ + + int2store(write, nm); + type_and_offset_store(write, new_offset_size, tp, offs); + } + + /* move data */ + { + size_t first_chunk_len= ((data - (uchar *)str->str) - + FIXED_HEADER_SIZE - header_size); + size_t second_chunk_len= new_data_size - first_chunk_len; + if (first_chunk_len) + memmove(str->str + FIXED_HEADER_SIZE + new_header_size, + str->str + FIXED_HEADER_SIZE + header_size, + first_chunk_len); + if (second_chunk_len) + memmove(str->str + + FIXED_HEADER_SIZE + new_header_size + first_chunk_len, + str->str + + FIXED_HEADER_SIZE + header_size + first_chunk_len + length, + second_chunk_len); + } + + /* fix str length */ + DBUG_ASSERT(str->length >= + FIXED_HEADER_SIZE + new_header_size + new_data_size); + str->length= FIXED_HEADER_SIZE + new_header_size + new_data_size; + + return ER_DYNCOL_OK; +} + + +/** + Check existence of the column in the packed string + + @param str The packed string to check the column + @param column_nr Number of column to check + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +dynamic_column_exists(DYNAMIC_COLUMN *str, uint column_nr) +{ + uchar *data; + size_t offset_size, length; + uint column_count; + DYNAMIC_COLUMN_TYPE type; + + if (str->length == 0) + return ER_DYNCOL_NO; /* no columns */ + + if (read_fixed_header(str, &offset_size, &column_count)) + return ER_DYNCOL_FORMAT; + + if (column_count == 0) + return ER_DYNCOL_NO; /* no columns */ + + if (find_column(&type, &data, &length, (uchar*)str->str + FIXED_HEADER_SIZE, + offset_size, column_count, (uchar*)str->str + str->length, + column_nr, NULL)) + return ER_DYNCOL_FORMAT; + + return (type != DYN_COL_NULL ? ER_DYNCOL_YES : ER_DYNCOL_NO); +} + + +/** + List not-null columns in the packed string + + @param str The packed string + @param array_of_uint Where to put reference on created array + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +dynamic_column_list(DYNAMIC_COLUMN *str, DYNAMIC_ARRAY *array_of_uint) +{ + uchar *read; + size_t offset_size, entry_size; + uint column_count, i; + + bzero(array_of_uint, sizeof(*array_of_uint)); /* In case of errors */ + if (str->length == 0) + return ER_DYNCOL_OK; /* no columns */ + + if (read_fixed_header(str, &offset_size, &column_count)) + return ER_DYNCOL_FORMAT; + + entry_size= COLUMN_NUMBER_SIZE + offset_size; + + if (entry_size * column_count + FIXED_HEADER_SIZE > str->length) + return ER_DYNCOL_FORMAT; + + if (init_dynamic_array(array_of_uint, sizeof(uint), column_count, 0)) + return ER_DYNCOL_RESOURCE; + + for (i= 0, read= (uchar *)str->str + FIXED_HEADER_SIZE; + i < column_count; + i++, read+= entry_size) + { + uint nm= uint2korr(read); + /* Insert can't never fail as it's pre-allocated above */ + (void) insert_dynamic(array_of_uint, (uchar *)&nm); + } + return ER_DYNCOL_OK; +} + + +/** + Find the place of the column in the header or place where it should be put + + @param num Number of the column + @param header Pointer to the header + @param entry_size Size of a header entry + @param column_count Number of columns in the packed string + @param entry Return pointer to the entry or next entry + + @retval TRUE found + @retval FALSE pointer set to the next row +*/ + +static my_bool +find_place(uint num, uchar *header, size_t entry_size, + uint column_count, uchar **entry) +{ + uint mid, start, end, val; + int flag; + LINT_INIT(flag); /* 100 % safe */ + + start= 0; + end= column_count -1; + mid= 1; + while (start != end) + { + uint val; + mid= (start + end) / 2; + val= uint2korr(header + mid * entry_size); + if ((flag= CMP_NUM(num, val)) <= 0) + end= mid; + else + start= mid + 1; + } + if (start != mid) + { + val= uint2korr(header + start * entry_size); + flag= CMP_NUM(num, val); + } + *entry= header + start * entry_size; + if (flag > 0) + *entry+= entry_size; /* Point at next bigger key */ + return flag == 0; +} + + +/* + Description of plan of adding/removing/updating a packed string +*/ + +typedef enum {PLAN_REPLACE, PLAN_ADD, PLAN_DELETE, PLAN_NOP} PLAN_ACT; + +struct st_plan { + DYNAMIC_COLUMN_VALUE *val; + uint *num; + uchar *place; + size_t length; + int hdelta, ddelta; + PLAN_ACT act; +}; +typedef struct st_plan PLAN; + + +static int plan_sort(const void *a, const void *b) +{ + return ((PLAN *)a)->num[0] - ((PLAN *)b)->num[0]; +} + +#define DELTA_CHECK(S, D, C) \ + if ((S) == 0) \ + (S)= (D); \ + else if (((S) > 0 && (D) < 0) || \ + ((S) < 0 && (D) > 0)) \ + { \ + (C)= TRUE; \ + break; \ + } \ + + +/** + Update the packed string with the given columns + + @param str String where to write the data + @param add_column_count Number of columns in the arrays + @param column_numbers Array of columns numbers + @param values Array of columns values + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +dynamic_column_update_many(DYNAMIC_COLUMN *str, + uint add_column_count, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values) +{ + PLAN *plan; + uchar *header_end; + long data_delta= 0; + uint i, j, k; + uint new_column_count, column_count, not_null; + enum enum_dyncol_func_result rc; + int header_delta; + size_t offset_size, entry_size, header_size, data_size; + size_t new_offset_size, new_entry_size, new_header_size, new_data_size; + size_t max_offset; + + if (add_column_count == 0) + return ER_DYNCOL_OK; + + /* + Get columns in column order. As the data in 'str' is already + in column order this allows to replace all columns in one loop. + */ + + if (!(plan= my_malloc(sizeof(PLAN) * (add_column_count + 1), MYF(0)))) + return ER_DYNCOL_RESOURCE; + + not_null= add_column_count; + for (i= 0; i < add_column_count; i++) + { + if (column_numbers[i] > UINT_MAX16) + { + rc= ER_DYNCOL_DATA; + goto end; + } + + plan[i].val= values + i; + plan[i].num= column_numbers + i; + if (values[i].type == DYN_COL_NULL) + not_null--; + + } + + if (str->length == 0) + { + /* + Just add new columns. If there was no columns to add we return + an empty string. + */ + goto create_new_string; + } + + /* Check that header is ok */ + if (read_fixed_header(str, &offset_size, &column_count)) + { + rc= ER_DYNCOL_FORMAT; + goto end; + } + if (column_count == 0) + goto create_new_string; + + qsort(plan, (size_t)add_column_count, sizeof(PLAN), &plan_sort); + + new_column_count= column_count; + calc_param(&entry_size, &header_size, offset_size, column_count); + max_offset= str->length - (FIXED_HEADER_SIZE + header_size); + header_end= (uchar*) str->str + FIXED_HEADER_SIZE + header_size; + + if (header_size + FIXED_HEADER_SIZE > str->length) + { + rc= ER_DYNCOL_FORMAT; + goto end; + } + + /* + Calculate how many columns and data is added/deleted and make a 'plan' + for each of them. + */ + header_delta= 0; + for (i= 0; i < add_column_count; i++) + { + uchar *entry; + + /* + For now we don't allow creating two columns with the same number + at the time of create. This can be fixed later to just use the later + by comparing the pointers. + */ + if (i < add_column_count - 1 && plan[i].num[0] == plan[i + 1].num[0]) + { + rc= ER_DYNCOL_DATA; + goto end; + } + + /* Set common variables for all plans */ + plan[i].ddelta= data_delta; + /* get header delta in entries */ + plan[i].hdelta= header_delta; + plan[i].length= 0; /* Length if NULL */ + + if (find_place(plan[i].num[0], + (uchar *)str->str + FIXED_HEADER_SIZE, + entry_size, column_count, &entry)) + { + size_t entry_data_size; + my_bool error; + + /* Data existed; We have to replace or delete it */ + + entry_data_size= get_length(entry, header_end, + offset_size, max_offset, &error); + if (error || (long) entry_data_size < 0) + { + rc= ER_DYNCOL_FORMAT; + goto end; + } + + if (plan[i].val->type == DYN_COL_NULL) + { + /* Inserting a NULL means delete the old data */ + + plan[i].act= PLAN_DELETE; /* Remove old value */ + header_delta--; /* One row less in header */ + data_delta-= entry_data_size; /* Less data to store */ + } + else + { + /* Replace the value */ + + plan[i].act= PLAN_REPLACE; + /* get data delta in bytes */ + if ((plan[i].length= dynamic_column_value_len(plan[i].val)) == + (size_t) ~0) + { + rc= ER_DYNCOL_DATA; + goto end; + } + data_delta+= plan[i].length - entry_data_size; + } + } + else + { + /* Data did not exists. Add if it it's not NULL */ + + if (plan[i].val->type == DYN_COL_NULL) + { + plan[i].act= PLAN_NOP; /* Mark entry to be skiped */ + } + else + { + /* Add new value */ + + plan[i].act= PLAN_ADD; + header_delta++; /* One more row in header */ + /* get data delta in bytes */ + if ((plan[i].length= dynamic_column_value_len(plan[i].val)) == + (size_t) ~0) + { + rc= ER_DYNCOL_DATA; + goto end; + } + data_delta+= plan[i].length; + } + } + plan[i].place= entry; + } + plan[add_column_count].hdelta= header_delta; + plan[add_column_count].ddelta= data_delta; + new_column_count= column_count + header_delta; + + /* + Check if it is only "increasing" or only "decreasing" plan for (header + and data separately). + */ + data_size= str->length - header_size - FIXED_HEADER_SIZE; + new_data_size= data_size + data_delta; + if ((new_offset_size= dynamic_column_offset_bytes(new_data_size)) >= + MAX_OFFSET_LENGTH) + { + rc= ER_DYNCOL_LIMIT; + goto end; + } + +#ifdef NOT_IMPLEMENTED + /* if (new_offset_size != offset_size) then we have to rewrite header */ + header_delta_sign= new_offset_size - offset_size; + data_delta_sign= 0; + for (i= 0; i < add_column_count; i++) + { + /* This is the check for increasing/decreasing */ + DELTA_CHECK(header_delta_sign, plan[i].hdelta, copy); + DELTA_CHECK(data_delta_sign, plan[i].ddelta, copy); + } +#endif + calc_param(&new_entry_size, &new_header_size, + new_offset_size, new_column_count); + + /* + The following code always make a copy. In future we can do a more + optimized version when data is only increasing / decreasing. + */ + + /*if (copy) */ + { + DYNAMIC_COLUMN tmp; + uchar *header_base= (uchar *)str->str + FIXED_HEADER_SIZE, + *write; + if (dynamic_column_init_str(&tmp, + (FIXED_HEADER_SIZE + new_header_size + + new_data_size + DYNCOL_SYZERESERVE))) + { + rc= ER_DYNCOL_RESOURCE; + goto end; + } + write= (uchar *)tmp.str + FIXED_HEADER_SIZE; + /* Adjust tmp to contain whole the future header */ + tmp.length= FIXED_HEADER_SIZE + new_header_size; + set_fixed_header(&tmp, new_offset_size, new_column_count); + data_delta= 0; + + /* + Copy data to the new string + i= index in array of changes + j= index in packed string header index + */ + + for (i= 0, j= 0; i < add_column_count || j < column_count; i++) + { + size_t first_offset; + uint start= j, end; + LINT_INIT(first_offset); + + /* + Search in i and j for the next column to add from i and where to + add. + */ + + while (i < add_column_count && plan[i].act == PLAN_NOP) + i++; /* skip NOP */ + if (i == add_column_count) + j= end= column_count; + else + { + /* + old data portion. We don't need to check that j < column_count + as plan[i].place is guaranteed to have a pointer inside the + data. + */ + while (header_base + j * entry_size < plan[i].place) + j++; + end= j; + if ((plan[i].act == PLAN_REPLACE || plan[i].act == PLAN_DELETE)) + j++; /* data at 'j' will be removed */ + } + + if (plan[i].ddelta == 0 && offset_size == new_offset_size) + { + uchar *read= header_base + start * entry_size; + DYNAMIC_COLUMN_TYPE tp; + /* + It's safe to copy the header unchanged. This is usually the + case for the first header block before any changed data. + */ + if (start < end) /* Avoid memcpy with 0 */ + { + size_t length= entry_size * (end - start); + memcpy(write, read, length); + write+= length; + } + /* Read first_offset */ + type_and_offset_read(&tp, &first_offset, read, offset_size); + } + else + { + /* + Adjust all headers since last loop. + We have to do this as the offset for data has moved + */ + for (k= start; k < end; k++) + { + uchar *read= header_base + k * entry_size; + size_t offs; + uint nm; + DYNAMIC_COLUMN_TYPE tp; + + nm= uint2korr(read); /* Column nummber */ + type_and_offset_read(&tp, &offs, read, offset_size); + if (k == start) + first_offset= offs; + else if (offs < first_offset) + { + dynamic_column_column_free(&tmp); + rc= ER_DYNCOL_FORMAT; + goto end; + } + + offs+= plan[i].ddelta; + int2store(write, nm); + /* write rest of data at write + COLUMN_NUMBER_SIZE */ + type_and_offset_store(write, new_offset_size, tp, offs); + write+= new_entry_size; + } + } + + /* copy first the data that was not replaced in original packed data */ + if (start < end) + { + my_bool error; + /* Add old data last in 'tmp' */ + size_t data_size= + get_length_interval(header_base + start * entry_size, + header_base + end * entry_size, + header_end, offset_size, max_offset, &error); + if (error || (long) data_size < 0 || + data_size > max_offset - first_offset) + { + dynamic_column_column_free(&tmp); + rc= ER_DYNCOL_FORMAT; + goto end; + } + + memcpy(tmp.str + tmp.length, (char *)header_end + first_offset, + data_size); + tmp.length+= data_size; + } + + /* new data adding */ + if (i < add_column_count) + { + if( plan[i].act == PLAN_ADD || plan[i].act == PLAN_REPLACE) + { + int2store(write, plan[i].num[0]); + type_and_offset_store(write, new_offset_size, + plan[i].val[0].type, + tmp.length - + (FIXED_HEADER_SIZE + new_header_size)); + write+= new_entry_size; + data_store(&tmp, plan[i].val); /* Append new data */ + } + data_delta= plan[i].ddelta; + } + } + dynamic_column_column_free(str); + *str= tmp; + } + + rc= ER_DYNCOL_OK; + +end: + my_free(plan); + return rc; + +create_new_string: + /* There is no columns from before, so let's just add the new ones */ + rc= ER_DYNCOL_OK; + if (not_null != 0) + rc= dynamic_column_create_many_internal(str, add_column_count, + column_numbers, values, + str->str == NULL); + goto end; +} + + +/** + Update the packed string with the given column + + @param str String where to write the data + @param column_number Array of columns number + @param values Array of columns values + + @return ER_DYNCOL_* return code +*/ + + +enum enum_dyncol_func_result +dynamic_column_update(DYNAMIC_COLUMN *str, uint column_nr, + DYNAMIC_COLUMN_VALUE *value) +{ + return dynamic_column_update_many(str, 1, &column_nr, value); +} diff --git a/mysys/mf_format.c b/mysys/mf_format.c index 2b2356c08df..d20ce882459 100644 --- a/mysys/mf_format.c +++ b/mysys/mf_format.c @@ -46,7 +46,7 @@ char * fn_format(char * to, const char *name, const char *dir, else if ((flag & MY_RELATIVE_PATH) && !test_if_hard_path(dev)) { /* Put 'dir' before the given path */ - strmake(buff,dev,sizeof(buff)-1); + strmake_buf(buff, dev); pos=convert_dirname(dev,dir,NullS); strmake(pos,buff,sizeof(buff)-1- (int) (pos-dev)); } diff --git a/mysys/mf_getdate.c b/mysys/mf_getdate.c index e3ac2e3ff13..ed8544a23fe 100644 --- a/mysys/mf_getdate.c +++ b/mysys/mf_getdate.c @@ -43,7 +43,7 @@ void get_date(register char * to, int flag, time_t date) struct tm tm_tmp; #endif - skr=date ? (time_t) date : my_time(0); + skr=date ? date : (time_t) my_time(0); #if defined(HAVE_LOCALTIME_R) && defined(_REENTRANT) if (flag & GETDATE_GMT) gmtime_r(&skr,&tm_tmp); diff --git a/mysys/mf_iocache.c b/mysys/mf_iocache.c index 7aa0934096f..02e5c5373ae 100644 --- a/mysys/mf_iocache.c +++ b/mysys/mf_iocache.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2000, 2011, Oracle and/or its affiliates This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -168,11 +169,9 @@ int init_io_cache(IO_CACHE *info, File file, size_t cachesize, if ((pos == (my_off_t) -1) && (my_errno == ESPIPE)) { /* - This kind of object doesn't support seek() or tell(). Don't set a - flag that will make us again try to seek() later and fail. - */ - info->seek_not_done= 0; - /* + This kind of object doesn't support seek() or tell(). Don't set a + seek_not_done that will make us again try to seek() later and fail. + Additionally, if we're supposed to start somewhere other than the the beginning of whatever this file is, then somebody made a bad assumption. @@ -554,7 +553,7 @@ int _my_b_read(register IO_CACHE *info, uchar *Buffer, size_t Count) if (Count) { /* We couldn't fulfil the request. Return, how much we got. */ - info->error= left_length; + info->error= (int) left_length; DBUG_RETURN(1); } length=0; /* Didn't read any chars */ @@ -1291,7 +1290,7 @@ read_append_buffer: info->append_read_pos += copy_len; Count -= copy_len; if (Count) - info->error = save_count - Count; + info->error= (int) (save_count - Count); /* Fill read buffer with data from write buffer */ memcpy(info->buffer, info->append_read_pos, @@ -1679,8 +1678,8 @@ int my_block_write(register IO_CACHE *info, const uchar *Buffer, size_t Count, { /* Of no overlap, write everything without buffering */ if (pos + Count <= info->pos_in_file) - return mysql_file_pwrite(info->file, Buffer, Count, pos, - info->myflags | MY_NABP); + return (int)mysql_file_pwrite(info->file, Buffer, Count, pos, + info->myflags | MY_NABP); /* Write the part of the block that is before buffer */ length= (uint) (info->pos_in_file - pos); if (mysql_file_pwrite(info->file, Buffer, length, pos, info->myflags | MY_NABP)) @@ -1688,9 +1687,6 @@ int my_block_write(register IO_CACHE *info, const uchar *Buffer, size_t Count, Buffer+=length; pos+= length; Count-= length; -#ifndef HAVE_PREAD - info->seek_not_done=1; -#endif } /* Check if we want to write inside the used part of the buffer.*/ @@ -1763,7 +1759,7 @@ int my_b_flush_io_cache(IO_CACHE *info, */ if (!append_cache && info->seek_not_done) { /* File touched, do seek */ - if (mysql_file_seek(info->file, pos_in_file, MY_SEEK_SET, MYF(0)) == + if (mysql_file_seek(info->file, pos_in_file, MY_SEEK_SET, MYF(info->myflags & MY_WME)) == MY_FILEPOS_ERROR) { UNLOCK_APPEND_BUFFER; @@ -1858,6 +1854,7 @@ int end_io_cache(IO_CACHE *info) info->type= TYPE_NOT_SET; mysql_mutex_destroy(&info->append_buffer_lock); } + info->share= 0; DBUG_RETURN(error); } /* end_io_cache */ diff --git a/mysys/mf_iocache2.c b/mysys/mf_iocache2.c index 04d2bc34ae4..c447c4b44a1 100644 --- a/mysys/mf_iocache2.c +++ b/mysys/mf_iocache2.c @@ -291,6 +291,40 @@ my_off_t my_b_filelength(IO_CACHE *info) } +size_t +my_b_write_backtick_quote(IO_CACHE *info, const char *str, size_t len) +{ + const uchar *start; + const uchar *p= (const uchar *)str; + const uchar *end= p + len; + size_t count; + size_t total= 0; + + if (my_b_write(info, (uchar *)"`", 1)) + return (size_t)-1; + ++total; + for (;;) + { + start= p; + while (p < end && *p != '`') + ++p; + count= p - start; + if (count && my_b_write(info, start, count)) + return (size_t)-1; + total+= count; + if (p >= end) + break; + if (my_b_write(info, (uchar *)"``", 2)) + return (size_t)-1; + total+= 2; + ++p; + } + if (my_b_write(info, (uchar *)"`", 1)) + return (size_t)-1; + ++total; + return total; +} + /* Simple printf version. Supports '%s', '%d', '%u', "%ld" and "%lu" Used for logging in MySQL @@ -315,6 +349,7 @@ size_t my_b_vprintf(IO_CACHE *info, const char* fmt, va_list args) uint minimum_width_sign; uint precision; /* as yet unimplemented for anything but %b */ my_bool is_zero_padded; + my_bool backtick_quoting; /* Store the location of the beginning of a format directive, for the @@ -349,6 +384,7 @@ size_t my_b_vprintf(IO_CACHE *info, const char* fmt, va_list args) fmt++; is_zero_padded= FALSE; + backtick_quoting= FALSE; minimum_width_sign= 1; minimum_width= 0; precision= 0; @@ -361,6 +397,8 @@ process_flags: minimum_width_sign= -1; fmt++; goto process_flags; case '0': is_zero_padded= TRUE; fmt++; goto process_flags; + case '`': + backtick_quoting= TRUE; fmt++; goto process_flags; case '#': /** @todo Implement "#" conversion flag. */ fmt++; goto process_flags; case ' ': @@ -404,9 +442,19 @@ process_flags: reg2 char *par = va_arg(args, char *); size_t length2 = strlen(par); /* TODO: implement precision */ - out_length+= length2; - if (my_b_write(info, (uchar*) par, length2)) - goto err; + if (backtick_quoting) + { + size_t total= my_b_write_backtick_quote(info, par, length2); + if (total == (size_t)-1) + goto err; + out_length+= total; + } + else + { + out_length+= length2; + if (my_b_write(info, (uchar*) par, length2)) + goto err; + } } else if (*fmt == 'b') /* Sized buffer parameter, only precision makes sense */ { @@ -430,9 +478,9 @@ process_flags: /* minimum width padding */ if (minimum_width > length2) { - char *buffz; + uchar *buffz; - buffz= my_alloca(minimum_width - length2); + buffz= (uchar*) my_alloca(minimum_width - length2); if (is_zero_padded) memset(buffz, '0', minimum_width - length2); else @@ -478,3 +526,4 @@ process_flags: err: return (size_t) -1; } + diff --git a/mysys/mf_keycache.c b/mysys/mf_keycache.c index 620fe1f5795..8191e92bb27 100644 --- a/mysys/mf_keycache.c +++ b/mysys/mf_keycache.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -13,12 +13,44 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + + /** - @file + @file + The file contains the following modules: + + Simple Key Cache Module + + Partitioned Key Cache Module + + Key Cache Interface Module + +*/ + +#include "mysys_priv.h" +#include "mysys_err.h" +#include <keycache.h> +#include "my_static.h" +#include <m_string.h> +#include <my_bit.h> +#include <errno.h> +#include <stdarg.h> +#include "probes_mysql.h" + +/****************************************************************************** + Simple Key Cache Module + + The module contains implementations of all key cache interface functions + employed by partitioned key caches. + +******************************************************************************/ + +/* These functions handle keyblock cacheing for ISAM and MyISAM tables. One cache can handle many files. It must contain buffers of the same blocksize. + init_key_cache() should be used to init cache handler. The free list (free_block_list) is a stack like structure. @@ -37,9 +69,7 @@ blocks_unused is the sum of never used blocks in the pool and of currently free blocks. blocks_used is the number of blocks fetched from the pool and as such gives the maximum number of in-use blocks at any time. -*/ -/* Key Cache Locking ================= @@ -104,15 +134,77 @@ I/O finished. */ -#include "mysys_priv.h" -#include "mysys_err.h" -#include <keycache.h> -#include "my_static.h" -#include <m_string.h> -#include <my_bit.h> -#include <errno.h> -#include <stdarg.h> -#include "probes_mysql.h" +/* declare structures that is used by st_key_cache */ + +struct st_block_link; +typedef struct st_block_link BLOCK_LINK; +struct st_keycache_page; +typedef struct st_keycache_page KEYCACHE_PAGE; +struct st_hash_link; +typedef struct st_hash_link HASH_LINK; + +/* info about requests in a waiting queue */ +typedef struct st_keycache_wqueue +{ + struct st_my_thread_var *last_thread; /* circular list of waiting threads */ +} KEYCACHE_WQUEUE; + +#define CHANGED_BLOCKS_HASH 128 /* must be power of 2 */ + +/* Control block for a simple (non-partitioned) key cache */ + +typedef struct st_simple_key_cache_cb +{ + my_bool key_cache_inited; /* <=> control block is allocated */ + my_bool in_resize; /* true during resize operation */ + my_bool resize_in_flush; /* true during flush of resize operation */ + my_bool can_be_used; /* usage of cache for read/write is allowed */ + size_t key_cache_mem_size; /* specified size of the cache memory */ + uint key_cache_block_size; /* size of the page buffer of a cache block */ + ulong min_warm_blocks; /* min number of warm blocks; */ + ulong age_threshold; /* age threshold for hot blocks */ + ulonglong keycache_time; /* total number of block link operations */ + uint hash_entries; /* max number of entries in the hash table */ + int hash_links; /* max number of hash links */ + int hash_links_used; /* number of hash links currently used */ + int disk_blocks; /* max number of blocks in the cache */ + ulong blocks_used; /* maximum number of concurrently used blocks */ + ulong blocks_unused; /* number of currently unused blocks */ + ulong blocks_changed; /* number of currently dirty blocks */ + ulong warm_blocks; /* number of blocks in warm sub-chain */ + ulong cnt_for_resize_op; /* counter to block resize operation */ + long blocks_available; /* number of blocks available in the LRU chain */ + HASH_LINK **hash_root; /* arr. of entries into hash table buckets */ + HASH_LINK *hash_link_root; /* memory for hash table links */ + HASH_LINK *free_hash_list; /* list of free hash links */ + BLOCK_LINK *free_block_list; /* list of free blocks */ + BLOCK_LINK *block_root; /* memory for block links */ + uchar *block_mem; /* memory for block buffers */ + BLOCK_LINK *used_last; /* ptr to the last block of the LRU chain */ + BLOCK_LINK *used_ins; /* ptr to the insertion block in LRU chain */ + mysql_mutex_t cache_lock; /* to lock access to the cache structure */ + KEYCACHE_WQUEUE resize_queue; /* threads waiting during resize operation */ + /* + Waiting for a zero resize count. Using a queue for symmetry though + only one thread can wait here. + */ + KEYCACHE_WQUEUE waiting_for_resize_cnt; + KEYCACHE_WQUEUE waiting_for_hash_link; /* waiting for a free hash link */ + KEYCACHE_WQUEUE waiting_for_block; /* requests waiting for a free block */ + BLOCK_LINK *changed_blocks[CHANGED_BLOCKS_HASH]; /* hash for dirty file bl.*/ + BLOCK_LINK *file_blocks[CHANGED_BLOCKS_HASH]; /* hash for other file bl.*/ + + /* Statistics variables. These are reset in reset_key_cache_counters(). */ + ulong global_blocks_changed; /* number of currently dirty blocks */ + ulonglong global_cache_w_requests;/* number of write requests (write hits) */ + ulonglong global_cache_write; /* number of writes from cache to files */ + ulonglong global_cache_r_requests;/* number of read requests (read hits) */ + ulonglong global_cache_read; /* number of reads from files to cache */ + + int blocks; /* max number of blocks in the cache */ + uint hash_factor; /* factor used to calculate hash function */ + my_bool in_init; /* Set to 1 in MySQL during init/resize */ +} SIMPLE_KEY_CACHE_CB; /* Some compilation flags have been added specifically for this module @@ -224,21 +316,21 @@ KEY_CACHE *dflt_key_cache= &dflt_key_cache_var; #define FLUSH_CACHE 2000 /* sort this many blocks at once */ -static int flush_all_key_blocks(KEY_CACHE *keycache); - +static int flush_all_key_blocks(SIMPLE_KEY_CACHE_CB *keycache); +static void end_simple_key_cache(SIMPLE_KEY_CACHE_CB *keycache, my_bool cleanup); static void wait_on_queue(KEYCACHE_WQUEUE *wqueue, mysql_mutex_t *mutex); static void release_whole_queue(KEYCACHE_WQUEUE *wqueue); - -static void free_block(KEY_CACHE *keycache, BLOCK_LINK *block); -#if !defined(DBUG_OFF) -static void test_key_cache(KEY_CACHE *keycache, +static void free_block(SIMPLE_KEY_CACHE_CB *keycache, BLOCK_LINK *block); +#ifndef DBUG_OFF +static void test_key_cache(SIMPLE_KEY_CACHE_CB *keycache, const char *where, my_bool lock); #endif - +#define KEYCACHE_BASE_EXPR(f, pos) \ + ((ulong) ((pos) / keycache->key_cache_block_size) + (ulong) (f)) #define KEYCACHE_HASH(f, pos) \ -(((ulong) ((pos) / keycache->key_cache_block_size) + \ - (ulong) (f)) & (keycache->hash_entries-1)) + ((KEYCACHE_BASE_EXPR(f, pos) / keycache->hash_factor) & \ + (keycache->hash_entries-1)) #define FILE_HASH(f) ((uint) (f) & (CHANGED_BLOCKS_HASH-1)) #define DEFAULT_KEYCACHE_DEBUG_LOG "keycache_debug.log" @@ -282,7 +374,6 @@ static void keycache_debug_print(const char *fmt,...); #endif /* defined(KEYCACHE_DEBUG_LOG) && defined(KEYCACHE_DEBUG) */ #if defined(KEYCACHE_DEBUG) || !defined(DBUG_OFF) - static long keycache_thread_id; #define KEYCACHE_THREAD_TRACE(l) \ KEYCACHE_DBUG_PRINT(l,("|thread %ld",keycache_thread_id)) @@ -329,9 +420,10 @@ static int keycache_pthread_cond_signal(mysql_cond_t *cond); #define inline /* disabled inline for easier debugging */ static int fail_block(BLOCK_LINK *block); static int fail_hlink(HASH_LINK *hlink); -static int cache_empty(KEY_CACHE *keycache); +static int cache_empty(SIMPLE_KEY_CACHE_CB *keycache); #endif + static inline uint next_power(uint value) { return (uint) my_round_up_to_next_power((uint32) value) << 1; @@ -339,19 +431,32 @@ static inline uint next_power(uint value) /* - Initialize a key cache + Initialize a simple key cache SYNOPSIS - init_key_cache() - keycache pointer to a key cache data structure - key_cache_block_size size of blocks to keep cached data - use_mem total memory to use for the key cache - division_limit division limit (may be zero) - age_threshold age threshold (may be zero) + init_simple_key_cache() + keycache pointer to the control block of a simple key cache + key_cache_block_size size of blocks to keep cached data + use_mem memory to use for the key cache buferrs/structures + division_limit division limit (may be zero) + age_threshold age threshold (may be zero) + + DESCRIPTION + This function is the implementation of the init_key_cache interface + function that is employed by simple (non-partitioned) key caches. + The function builds a simple key cache and initializes the control block + structure of the type SIMPLE_KEY_CACHE_CB that is used for this key cache. + The parameter keycache is supposed to point to this structure. + The parameter key_cache_block_size specifies the size of the blocks in + the key cache to be built. The parameters division_limit and age_threshhold + determine the initial values of those characteristics of the key cache + that are used for midpoint insertion strategy. The parameter use_mem + specifies the total amount of memory to be allocated for key cache blocks + and auxiliary structures. RETURN VALUE number of blocks in the key cache, if successful, - 0 - otherwise. + <= 0 - otherwise. NOTES. if keycache->key_cache_inited != 0 we assume that the key cache @@ -360,17 +465,17 @@ static inline uint next_power(uint value) It's assumed that no two threads call this function simultaneously referring to the same key cache handle. - */ -int init_key_cache(KEY_CACHE *keycache, uint key_cache_block_size, - size_t use_mem, uint division_limit, - uint age_threshold) +static +int init_simple_key_cache(SIMPLE_KEY_CACHE_CB *keycache, uint key_cache_block_size, + size_t use_mem, uint division_limit, + uint age_threshold) { ulong blocks, hash_links; size_t length; int error; - DBUG_ENTER("init_key_cache"); + DBUG_ENTER("init_simple_key_cache"); DBUG_ASSERT(key_cache_block_size >= 512); KEYCACHE_DEBUG_OPEN; @@ -380,12 +485,15 @@ int init_key_cache(KEY_CACHE *keycache, uint key_cache_block_size, DBUG_RETURN(0); } + keycache->blocks_used= keycache->blocks_unused= 0; + keycache->global_blocks_changed= 0; keycache->global_cache_w_requests= keycache->global_cache_r_requests= 0; keycache->global_cache_read= keycache->global_cache_write= 0; keycache->disk_blocks= -1; if (! keycache->key_cache_inited) { keycache->key_cache_inited= 1; + keycache->hash_factor= 1; /* Initialize these variables once only. Their value must survive re-initialization during resizing. @@ -529,51 +637,38 @@ err: /* - Resize a key cache + Prepare for resizing a simple key cache SYNOPSIS - resize_key_cache() - keycache pointer to a key cache data structure - key_cache_block_size size of blocks to keep cached data - use_mem total memory to use for the new key cache - division_limit new division limit (if not zero) - age_threshold new age threshold (if not zero) + prepare_resize_simple_key_cache() + keycache pointer to the control block of a simple key cache + release_lock <=> release the key cache lock before return - RETURN VALUE - number of blocks in the key cache, if successful, - 0 - otherwise. - - NOTES. - The function first compares the memory size and the block size parameters - with the key cache values. + DESCRIPTION + This function flushes all dirty pages from a simple key cache and after + this it destroys the key cache calling end_simple_key_cache. The function + takes the parameter keycache as a pointer to the control block + structure of the type SIMPLE_KEY_CACHE_CB for this key cache. + The parameter release_lock says whether the key cache lock must be + released before return from the function. - If they differ the function free the the memory allocated for the - old key cache blocks by calling the end_key_cache function and - then rebuilds the key cache with new blocks by calling - init_key_cache. + RETURN VALUE + 0 - on success, + 1 - otherwise. - The function starts the operation only when all other threads - performing operations with the key cache let her to proceed - (when cnt_for_resize=0). + NOTES + This function is the called by resize_simple_key_cache and + resize_partitioned_key_cache that resize simple and partitioned key caches + respectively. */ -int resize_key_cache(KEY_CACHE *keycache, uint key_cache_block_size, - size_t use_mem, uint division_limit, - uint age_threshold) +static +int prepare_resize_simple_key_cache(SIMPLE_KEY_CACHE_CB *keycache, + my_bool release_lock) { - int blocks; - DBUG_ENTER("resize_key_cache"); - - if (!keycache->key_cache_inited) - DBUG_RETURN(keycache->disk_blocks); - - if(key_cache_block_size == keycache->key_cache_block_size && - use_mem == keycache->key_cache_mem_size) - { - change_key_cache_param(keycache, division_limit, age_threshold); - DBUG_RETURN(keycache->disk_blocks); - } - + int res= 0; + DBUG_ENTER("prepare_resize_simple_key_cache"); + keycache_pthread_mutex_lock(&keycache->cache_lock); /* @@ -606,8 +701,8 @@ int resize_key_cache(KEY_CACHE *keycache, uint key_cache_block_size, { /* TODO: if this happens, we should write a warning in the log file ! */ keycache->resize_in_flush= 0; - blocks= 0; keycache->can_be_used= 0; + res= 1; goto finish; } DBUG_ASSERT(cache_empty(keycache)); @@ -629,29 +724,139 @@ int resize_key_cache(KEY_CACHE *keycache, uint key_cache_block_size, */ while (keycache->cnt_for_resize_op) wait_on_queue(&keycache->waiting_for_resize_cnt, &keycache->cache_lock); - - /* - Free old cache structures, allocate new structures, and initialize - them. Note that the cache_lock mutex and the resize_queue are left - untouched. We do not lose the cache_lock and will release it only at - the end of this function. - */ - end_key_cache(keycache, 0); /* Don't free mutex */ - /* The following will work even if use_mem is 0 */ - blocks= init_key_cache(keycache, key_cache_block_size, use_mem, - division_limit, age_threshold); + + end_simple_key_cache(keycache, 0); finish: + if (release_lock) + keycache_pthread_mutex_unlock(&keycache->cache_lock); + DBUG_RETURN(res); +} + + +/* + Finalize resizing a simple key cache + + SYNOPSIS + finish_resize_simple_key_cache() + keycache pointer to the control block of a simple key cache + acquire_lock <=> acquire the key cache lock at start + + DESCRIPTION + This function performs finalizing actions for the operation of + resizing a simple key cache. The function takes the parameter + keycache as a pointer to the control block structure of the type + SIMPLE_KEY_CACHE_CB for this key cache. The function sets the flag + in_resize in this structure to FALSE. + The parameter acquire_lock says whether the key cache lock must be + acquired at the start of the function. + + RETURN VALUE + none + + NOTES + This function is the called by resize_simple_key_cache and + resize_partitioned_key_cache that resize simple and partitioned key caches + respectively. +*/ + +static +void finish_resize_simple_key_cache(SIMPLE_KEY_CACHE_CB *keycache, + my_bool acquire_lock) +{ + DBUG_ENTER("finish_resize_simple_key_cache"); + + if (acquire_lock) + keycache_pthread_mutex_lock(&keycache->cache_lock); + + mysql_mutex_assert_owner(&keycache->cache_lock); + /* Mark the resize finished. This allows other threads to start a resize or to request new cache blocks. */ keycache->in_resize= 0; + /* Signal waiting threads. */ release_whole_queue(&keycache->resize_queue); + keycache_pthread_mutex_unlock(&keycache->cache_lock); + + DBUG_VOID_RETURN; +} + + +/* + Resize a simple key cache + + SYNOPSIS + resize_simple_key_cache() + keycache pointer to the control block of a simple key cache + key_cache_block_size size of blocks to keep cached data + use_mem memory to use for the key cache buffers/structures + division_limit new division limit (if not zero) + age_threshold new age threshold (if not zero) + + DESCRIPTION + This function is the implementation of the resize_key_cache interface + function that is employed by simple (non-partitioned) key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type SIMPLE_KEY_CACHE_CB for the simple key + cache to be resized. + The parameter key_cache_block_size specifies the new size of the blocks in + the key cache. The parameters division_limit and age_threshold + determine the new initial values of those characteristics of the key cache + that are used for midpoint insertion strategy. The parameter use_mem + specifies the total amount of memory to be allocated for key cache blocks + and auxiliary structures in the new key cache. + + RETURN VALUE + number of blocks in the key cache, if successful, + 0 - otherwise. + + NOTES. + The function first calls the function prepare_resize_simple_key_cache + to flush all dirty blocks from key cache, to free memory used + for key cache blocks and auxiliary structures. After this the + function builds a new key cache with new parameters. + + This implementation doesn't block the calls and executions of other + functions from the key cache interface. However it assumes that the + calls of resize_simple_key_cache itself are serialized. + + The function starts the operation only when all other threads + performing operations with the key cache let her to proceed + (when cnt_for_resize=0). +*/ + +static +int resize_simple_key_cache(SIMPLE_KEY_CACHE_CB *keycache, uint key_cache_block_size, + size_t use_mem, uint division_limit, + uint age_threshold) +{ + int blocks= 0; + DBUG_ENTER("resize_simple_key_cache"); + + if (!keycache->key_cache_inited) + DBUG_RETURN(blocks); + + /* + Note that the cache_lock mutex and the resize_queue are left untouched. + We do not lose the cache_lock and will release it only at the end of + this function. + */ + if (prepare_resize_simple_key_cache(keycache, 0)) + goto finish; + + /* The following will work even if use_mem is 0 */ + blocks= init_simple_key_cache(keycache, key_cache_block_size, use_mem, + division_limit, age_threshold); + +finish: + finish_resize_simple_key_cache(keycache, 0); + DBUG_RETURN(blocks); } @@ -659,7 +864,7 @@ finish: /* Increment counter blocking resize key cache operation */ -static inline void inc_counter_for_resize_op(KEY_CACHE *keycache) +static inline void inc_counter_for_resize_op(SIMPLE_KEY_CACHE_CB *keycache) { keycache->cnt_for_resize_op++; } @@ -669,35 +874,47 @@ static inline void inc_counter_for_resize_op(KEY_CACHE *keycache) Decrement counter blocking resize key cache operation; Signal the operation to proceed when counter becomes equal zero */ -static inline void dec_counter_for_resize_op(KEY_CACHE *keycache) +static inline void dec_counter_for_resize_op(SIMPLE_KEY_CACHE_CB *keycache) { if (!--keycache->cnt_for_resize_op) release_whole_queue(&keycache->waiting_for_resize_cnt); } + /* - Change the key cache parameters + Change key cache parameters of a simple key cache SYNOPSIS - change_key_cache_param() - keycache pointer to a key cache data structure - division_limit new division limit (if not zero) - age_threshold new age threshold (if not zero) + change_simple_key_cache_param() + keycache pointer to the control block of a simple key cache + division_limit new division limit (if not zero) + age_threshold new age threshold (if not zero) + + DESCRIPTION + This function is the implementation of the change_key_cache_param interface + function that is employed by simple (non-partitioned) key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type SIMPLE_KEY_CACHE_CB for the simple key + cache where new values of the division limit and the age threshold used + for midpoint insertion strategy are to be set. The parameters + division_limit and age_threshold provide these new values. RETURN VALUE none NOTES. - Presently the function resets the key cache parameters - concerning midpoint insertion strategy - division_limit and - age_threshold. + Presently the function resets the key cache parameters concerning + midpoint insertion strategy - division_limit and age_threshold. + This function changes some parameters of a given key cache without + reformatting it. The function does not touch the contents the key + cache blocks. */ -void change_key_cache_param(KEY_CACHE *keycache, uint division_limit, - uint age_threshold) +static +void change_simple_key_cache_param(SIMPLE_KEY_CACHE_CB *keycache, uint division_limit, + uint age_threshold) { - DBUG_ENTER("change_key_cache_param"); - + DBUG_ENTER("change_simple_key_cache_param"); keycache_pthread_mutex_lock(&keycache->cache_lock); if (division_limit) keycache->min_warm_blocks= (keycache->disk_blocks * @@ -711,20 +928,31 @@ void change_key_cache_param(KEY_CACHE *keycache, uint division_limit, /* - Remove key_cache from memory + Destroy a simple key cache SYNOPSIS - end_key_cache() - keycache key cache handle - cleanup Complete free (Free also mutex for key cache) + end_simple_key_cache() + keycache pointer to the control block of a simple key cache + cleanup <=> complete free (free also mutex for key cache) + + DESCRIPTION + This function is the implementation of the end_key_cache interface + function that is employed by simple (non-partitioned) key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type SIMPLE_KEY_CACHE_CB for the simple key + cache to be destroyed. + The function frees the memory allocated for the key cache blocks and + auxiliary structures. If the value of the parameter cleanup is TRUE + then even the key cache mutex is freed. RETURN VALUE none */ -void end_key_cache(KEY_CACHE *keycache, my_bool cleanup) +static +void end_simple_key_cache(SIMPLE_KEY_CACHE_CB *keycache, my_bool cleanup) { - DBUG_ENTER("end_key_cache"); + DBUG_ENTER("end_simple_key_cache"); DBUG_PRINT("enter", ("key_cache: 0x%lx", (long) keycache)); if (!keycache->key_cache_inited) @@ -1011,7 +1239,7 @@ static inline void link_changed(BLOCK_LINK *block, BLOCK_LINK **phead) void */ -static void link_to_file_list(KEY_CACHE *keycache, +static void link_to_file_list(SIMPLE_KEY_CACHE_CB *keycache, BLOCK_LINK *block, int file, my_bool unlink_block) { @@ -1052,7 +1280,7 @@ static void link_to_file_list(KEY_CACHE *keycache, void */ -static void link_to_changed_list(KEY_CACHE *keycache, +static void link_to_changed_list(SIMPLE_KEY_CACHE_CB *keycache, BLOCK_LINK *block) { DBUG_ASSERT(block->status & BLOCK_IN_USE); @@ -1107,8 +1335,8 @@ static void link_to_changed_list(KEY_CACHE *keycache, not linked in the LRU ring. */ -static void link_block(KEY_CACHE *keycache, BLOCK_LINK *block, my_bool hot, - my_bool at_end) +static void link_block(SIMPLE_KEY_CACHE_CB *keycache, BLOCK_LINK *block, + my_bool hot, my_bool at_end) { BLOCK_LINK *ins; BLOCK_LINK **pins; @@ -1119,7 +1347,6 @@ static void link_block(KEY_CACHE *keycache, BLOCK_LINK *block, my_bool hot, DBUG_ASSERT(block->prev_changed && *block->prev_changed == block); DBUG_ASSERT(!block->next_used); DBUG_ASSERT(!block->prev_used); - if (!hot && keycache->waiting_for_block.last_thread) { /* Signal that in the LRU warm sub-chain an available block has appeared */ @@ -1179,7 +1406,6 @@ static void link_block(KEY_CACHE *keycache, BLOCK_LINK *block, my_bool hot, #endif return; } - pins= hot ? &keycache->used_ins : &keycache->used_last; ins= *pins; if (ins) @@ -1225,7 +1451,7 @@ static void link_block(KEY_CACHE *keycache, BLOCK_LINK *block, my_bool hot, See NOTES for link_block */ -static void unlink_block(KEY_CACHE *keycache, BLOCK_LINK *block) +static void unlink_block(SIMPLE_KEY_CACHE_CB *keycache, BLOCK_LINK *block) { DBUG_ASSERT((block->status & ~BLOCK_CHANGED) == (BLOCK_READ | BLOCK_IN_USE)); DBUG_ASSERT(block->hash_link); /*backptr to block NULL from free_block()*/ @@ -1283,7 +1509,8 @@ static void unlink_block(KEY_CACHE *keycache, BLOCK_LINK *block) RETURN void */ -static void reg_requests(KEY_CACHE *keycache, BLOCK_LINK *block, int count) +static void reg_requests(SIMPLE_KEY_CACHE_CB *keycache, + BLOCK_LINK *block, int count) { DBUG_ASSERT(block->status & BLOCK_IN_USE); DBUG_ASSERT(block->hash_link); @@ -1326,7 +1553,7 @@ static void reg_requests(KEY_CACHE *keycache, BLOCK_LINK *block, int count) not linked in the LRU ring. */ -static void unreg_request(KEY_CACHE *keycache, +static void unreg_request(SIMPLE_KEY_CACHE_CB *keycache, BLOCK_LINK *block, int at_end) { DBUG_ASSERT(block->status & (BLOCK_READ | BLOCK_IN_USE)); @@ -1401,7 +1628,6 @@ static void remove_reader(BLOCK_LINK *block) DBUG_ASSERT(!block->next_used); DBUG_ASSERT(!block->prev_used); DBUG_ASSERT(block->hash_link->requests); - if (! --block->hash_link->requests && block->condvar) keycache_pthread_cond_signal(block->condvar); } @@ -1412,7 +1638,7 @@ static void remove_reader(BLOCK_LINK *block) signals on its termination */ -static void wait_for_readers(KEY_CACHE *keycache, +static void wait_for_readers(SIMPLE_KEY_CACHE_CB *keycache, BLOCK_LINK *block) { struct st_my_thread_var *thread= my_thread_var; @@ -1457,7 +1683,7 @@ static inline void link_hash(HASH_LINK **start, HASH_LINK *hash_link) Remove a hash link from the hash table */ -static void unlink_hash(KEY_CACHE *keycache, HASH_LINK *hash_link) +static void unlink_hash(SIMPLE_KEY_CACHE_CB *keycache, HASH_LINK *hash_link) { KEYCACHE_DBUG_PRINT("unlink_hash", ("fd: %u pos_ %lu #requests=%u", (uint) hash_link->file,(ulong) hash_link->diskpos, hash_link->requests)); @@ -1465,7 +1691,6 @@ static void unlink_hash(KEY_CACHE *keycache, HASH_LINK *hash_link) if ((*hash_link->prev= hash_link->next)) hash_link->next->prev= hash_link->prev; hash_link->block= NULL; - if (keycache->waiting_for_hash_link.last_thread) { /* Signal that a free hash link has appeared */ @@ -1510,7 +1735,7 @@ static void unlink_hash(KEY_CACHE *keycache, HASH_LINK *hash_link) Get the hash link for a page */ -static HASH_LINK *get_hash_link(KEY_CACHE *keycache, +static HASH_LINK *get_hash_link(SIMPLE_KEY_CACHE_CB *keycache, int file, my_off_t filepos) { reg1 HASH_LINK *hash_link, **start; @@ -1627,7 +1852,7 @@ restart: waits until first of this operations links any block back. */ -static BLOCK_LINK *find_key_block(KEY_CACHE *keycache, +static BLOCK_LINK *find_key_block(SIMPLE_KEY_CACHE_CB *keycache, File file, my_off_t filepos, int init_hits_left, int wrmode, int *page_st) @@ -2099,7 +2324,6 @@ restart: DBUG_ASSERT(!hash_link->block->next_used); DBUG_ASSERT(!hash_link->block->prev_used); } - /* If we waited above, hash_link->block has been assigned by link_block(). Otherwise it is still NULL. In the latter case @@ -2380,7 +2604,7 @@ restart: portion is less than read_length, but not less than min_length. */ -static void read_block(KEY_CACHE *keycache, +static void read_block(SIMPLE_KEY_CACHE_CB *keycache, BLOCK_LINK *block, uint read_length, uint min_length, my_bool primary) { @@ -2468,43 +2692,60 @@ static void read_block(KEY_CACHE *keycache, /* - Read a block of data from a cached file into a buffer; + Read a block of data from a simple key cache into a buffer SYNOPSIS - key_cache_read() - keycache pointer to a key cache data structure - file handler for the file for the block of data to be read - filepos position of the block of data in the file - level determines the weight of the data - buff buffer to where the data must be placed - length length of the buffer - block_length length of the block in the key cache buffer - return_buffer return pointer to the key cache buffer with the data + simple_key_cache_read() + keycache pointer to the control block of a simple key cache + file handler for the file for the block of data to be read + filepos position of the block of data in the file + level determines the weight of the data + buff buffer to where the data must be placed + length length of the buffer + block_length length of the read data from a key cache block + return_buffer return pointer to the key cache buffer with the data + DESCRIPTION + This function is the implementation of the key_cache_read interface + function that is employed by simple (non-partitioned) key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type SIMPLE_KEY_CACHE_CB for a simple key + cache. + In a general case the function reads a block of data from the key cache + into the buffer buff of the size specified by the parameter length. The + beginning of the block of data to be read is specified by the parameters + file and filepos. The length of the read data is the same as the length + of the buffer. The data is read into the buffer in key_cache_block_size + increments. If the next portion of the data is not found in any key cache + block, first it is read from file into the key cache. + If the parameter return_buffer is not ignored and its value is TRUE, and + the data to be read of the specified size block_length can be read from one + key cache buffer, then the function returns a pointer to the data in the + key cache buffer. + The function takse into account parameters block_length and return buffer + only in a single-threaded environment. + The parameter 'level' is used only by the midpoint insertion strategy + when the data or its portion cannot be found in the key cache. + RETURN VALUE - Returns address from where the data is placed if sucessful, 0 - otherwise. + Returns address from where the data is placed if successful, 0 - otherwise. - NOTES. - The function ensures that a block of data of size length from file - positioned at filepos is in the buffers for some key cache blocks. - Then the function either copies the data into the buffer buff, or, - if return_buffer is TRUE, it just returns the pointer to the key cache - buffer with the data. + NOTES Filepos must be a multiple of 'block_length', but it doesn't have to be a multiple of key_cache_block_size; */ -uchar *key_cache_read(KEY_CACHE *keycache, - File file, my_off_t filepos, int level, - uchar *buff, uint length, - uint block_length __attribute__((unused)), - int return_buffer __attribute__((unused))) +uchar *simple_key_cache_read(SIMPLE_KEY_CACHE_CB *keycache, + File file, my_off_t filepos, int level, + uchar *buff, uint length, + uint block_length __attribute__((unused)), + int return_buffer __attribute__((unused))) { my_bool locked_and_incremented= FALSE; int error=0; uchar *start= buff; - DBUG_ENTER("key_cache_read"); + DBUG_ENTER("simple_key_cache_read"); DBUG_PRINT("enter", ("fd: %u pos: %lu length: %u", (uint) file, (ulong) filepos, length)); @@ -2569,9 +2810,6 @@ uchar *key_cache_read(KEY_CACHE *keycache, set_if_smaller(read_length, keycache->key_cache_block_size-offset); KEYCACHE_DBUG_ASSERT(read_length > 0); - if (block_length > keycache->key_cache_block_size || offset) - return_buffer=0; - /* Request the cache block that matches file/pos. */ keycache->global_cache_r_requests++; @@ -2710,28 +2948,47 @@ end: /* - Insert a block of file data from a buffer into key cache + Insert a block of file data from a buffer into a simple key cache SYNOPSIS - key_cache_insert() - keycache pointer to a key cache data structure + simple_key_cache_insert() + keycache pointer to the control block of a simple key cache file handler for the file to insert data from filepos position of the block of data in the file to insert level determines the weight of the data buff buffer to read data from length length of the data in the buffer - NOTES - This is used by MyISAM to move all blocks from a index file to the key - cache - + DESCRIPTION + This function is the implementation of the key_cache_insert interface + function that is employed by simple (non-partitioned) key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type SIMPLE_KEY_CACHE_CB for a simple key + cache. + The function writes a block of file data from a buffer into the key cache. + The buffer is specified with the parameters buff and length - the pointer + to the beginning of the buffer and its size respectively. It's assumed + the buffer contains the data from 'file' allocated from the position + filepos. The data is copied from the buffer in key_cache_block_size + increments. + The parameter level is used to set one characteristic for the key buffers + loaded with the data from buff. The characteristic is used only by the + midpoint insertion strategy. + RETURN VALUE 0 if a success, 1 - otherwise. + + NOTES + The function is used by MyISAM to move all blocks from a index file to + the key cache. It can be performed in parallel with reading the file data + from the key buffers by other threads. + */ -int key_cache_insert(KEY_CACHE *keycache, - File file, my_off_t filepos, int level, - uchar *buff, uint length) +static +int simple_key_cache_insert(SIMPLE_KEY_CACHE_CB *keycache, + File file, my_off_t filepos, int level, + uchar *buff, uint length) { int error= 0; DBUG_ENTER("key_cache_insert"); @@ -2948,43 +3205,64 @@ int key_cache_insert(KEY_CACHE *keycache, /* - Write a buffer into a cached file. + Write a buffer into a simple key cache SYNOPSIS - key_cache_write() - keycache pointer to a key cache data structure - file handler for the file to write data to - filepos position in the file to write data to - level determines the weight of the data - buff buffer with the data - length length of the buffer - dont_write if is 0 then all dirty pages involved in writing - should have been flushed from key cache + simple_key_cache_write() + keycache pointer to the control block of a simple key cache + file handler for the file to write data to + file_extra maps of key cache partitions containing + dirty pages from file + filepos position in the file to write data to + level determines the weight of the data + buff buffer with the data + length length of the buffer + dont_write if is 0 then all dirty pages involved in writing + should have been flushed from key cache + DESCRIPTION + This function is the implementation of the key_cache_write interface + function that is employed by simple (non-partitioned) key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type SIMPLE_KEY_CACHE_CB for a simple key + cache. + In a general case the function copies data from a buffer into the key + cache. The buffer is specified with the parameters buff and length - + the pointer to the beginning of the buffer and its size respectively. + It's assumed the buffer contains the data to be written into 'file' + starting from the position filepos. The data is copied from the buffer + in key_cache_block_size increments. + If the value of the parameter dont_write is FALSE then the function + also writes the data into file. + The parameter level is used to set one characteristic for the key buffers + filled with the data from buff. The characteristic is employed only by + the midpoint insertion strategy. + The parameter file_extra currently makes sense only for simple key caches + that are elements of a partitioned key cache. It provides a pointer to the + shared bitmap of the partitions that may contains dirty pages for the file. + This bitmap is used to optimize the function + flush_partitioned_key_cache_blocks. + RETURN VALUE 0 if a success, 1 - otherwise. - NOTES. - The function copies the data of size length from buff into buffers - for key cache blocks that are assigned to contain the portion of - the file starting with position filepos. - It ensures that this data is flushed to the file if dont_write is FALSE. - Filepos must be a multiple of 'block_length', but it doesn't - have to be a multiple of key_cache_block_size; - - dont_write is always TRUE in the server (info->lock_type is never F_UNLCK). + NOTES + This implementation exploits the fact that the function is called only + when a thread has got an exclusive lock for the key file. */ -int key_cache_write(KEY_CACHE *keycache, - File file, my_off_t filepos, int level, - uchar *buff, uint length, - uint block_length __attribute__((unused)), - int dont_write) +static +int simple_key_cache_write(SIMPLE_KEY_CACHE_CB *keycache, + File file, void *file_extra __attribute__((unused)), + my_off_t filepos, int level, + uchar *buff, uint length, + uint block_length __attribute__((unused)), + int dont_write) { my_bool locked_and_incremented= FALSE; int error=0; - DBUG_ENTER("key_cache_write"); + DBUG_ENTER("simple_key_cache_write"); DBUG_PRINT("enter", ("fd: %u pos: %lu length: %u block_length: %u" " key_block_length: %u", @@ -3316,7 +3594,7 @@ end: Block must have a request registered on it. */ -static void free_block(KEY_CACHE *keycache, BLOCK_LINK *block) +static void free_block(SIMPLE_KEY_CACHE_CB *keycache, BLOCK_LINK *block) { KEYCACHE_THREAD_TRACE("free block"); KEYCACHE_DBUG_PRINT("free_block", @@ -3456,7 +3734,7 @@ static int cmp_sec_link(BLOCK_LINK **a, BLOCK_LINK **b) free used blocks if requested */ -static int flush_cached_blocks(KEY_CACHE *keycache, +static int flush_cached_blocks(SIMPLE_KEY_CACHE_CB *keycache, File file, BLOCK_LINK **cache, BLOCK_LINK **end, enum flush_type type) @@ -3500,9 +3778,9 @@ static int flush_cached_blocks(KEY_CACHE *keycache, (BLOCK_READ | BLOCK_IN_FLUSH | BLOCK_CHANGED | BLOCK_IN_USE)); block->status|= BLOCK_IN_FLUSHWRITE; keycache_pthread_mutex_unlock(&keycache->cache_lock); - error= my_pwrite(file, block->buffer+block->offset, + error= my_pwrite(file, block->buffer + block->offset, block->length - block->offset, - block->hash_link->diskpos+ block->offset, + block->hash_link->diskpos + block->offset, MYF(MY_NABP | MY_WAIT_IF_FULL)); keycache_pthread_mutex_lock(&keycache->cache_lock); keycache->global_cache_write++; @@ -3562,7 +3840,7 @@ static int flush_cached_blocks(KEY_CACHE *keycache, /* - Flush all key blocks for a file to disk, but don't do any mutex locks. + Flush all key blocks for a file to disk, but don't do any mutex locks SYNOPSIS flush_key_blocks_int() @@ -3584,7 +3862,7 @@ static int flush_cached_blocks(KEY_CACHE *keycache, 1 error */ -static int flush_key_blocks_int(KEY_CACHE *keycache, +static int flush_key_blocks_int(SIMPLE_KEY_CACHE_CB *keycache, File file, enum flush_type type) { BLOCK_LINK *cache_buff[FLUSH_CACHE],**cache; @@ -3599,6 +3877,7 @@ static int flush_key_blocks_int(KEY_CACHE *keycache, test_key_cache(keycache, "start of flush_key_blocks", 0);); #endif + DBUG_ASSERT(type != FLUSH_KEEP_LAZY); cache= cache_buff; if (keycache->disk_blocks > 0 && (!my_disable_flush_key_blocks || type != FLUSH_KEEP)) @@ -4019,22 +4298,46 @@ err: /* - Flush all blocks for a file to disk + Flush all blocks for a file from key buffers of a simple key cache SYNOPSIS - flush_key_blocks() - keycache pointer to a key cache data structure - file handler for the file to flush to - flush_type type of the flush + flush_simple_key_blocks() + keycache pointer to the control block of a simple key cache + file handler for the file to flush to + file_extra maps of key cache partitions containing + dirty pages from file (not used) + flush_type type of the flush operation + DESCRIPTION + This function is the implementation of the flush_key_blocks interface + function that is employed by simple (non-partitioned) key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type S_KEY_CACHE_CB for a simple key + cache. + In a general case the function flushes the data from all dirty key + buffers related to the file 'file' into this file. The function does + exactly this if the value of the parameter type is FLUSH_KEEP. If the + value of this parameter is FLUSH_RELEASE, the function additionally + releases the key buffers containing data from 'file' for new usage. + If the value of the parameter type is FLUSH_IGNORE_CHANGED the function + just releases the key buffers containing data from 'file'. + The parameter file_extra currently is not used by this function. + RETURN 0 ok 1 error + + NOTES + This implementation exploits the fact that the function is called only + when a thread has got an exclusive lock for the key file. */ -int flush_key_blocks(KEY_CACHE *keycache, - File file, enum flush_type type) +static +int flush_simple_key_cache_blocks(SIMPLE_KEY_CACHE_CB *keycache, + File file, + void *file_extra __attribute__((unused)), + enum flush_type type) { int res= 0; DBUG_ENTER("flush_key_blocks"); @@ -4088,7 +4391,7 @@ int flush_key_blocks(KEY_CACHE *keycache, != 0 Error */ -static int flush_all_key_blocks(KEY_CACHE *keycache) +static int flush_all_key_blocks(SIMPLE_KEY_CACHE_CB *keycache) { BLOCK_LINK *block; uint total_found; @@ -4191,37 +4494,43 @@ static int flush_all_key_blocks(KEY_CACHE *keycache) /* - Reset the counters of a key cache. + Reset the counters of a simple key cache SYNOPSIS - reset_key_cache_counters() - name the name of a key cache - key_cache pointer to the key kache to be reset + reset_simple_key_cache_counters() + name the name of a key cache + keycache pointer to the control block of a simple key cache DESCRIPTION - This procedure is used by process_key_caches() to reset the counters of all - currently used key caches, both the default one and the named ones. + This function is the implementation of the reset_key_cache_counters + interface function that is employed by simple (non-partitioned) key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type S_KEY_CACHE_CB for a simple key cache. + This function resets the values of all statistical counters for the key + cache to 0. + The parameter name is currently not used. RETURN 0 on success (always because it can't fail) */ -int reset_key_cache_counters(const char *name __attribute__((unused)), - KEY_CACHE *key_cache) +static +int reset_simple_key_cache_counters(const char *name __attribute__((unused)), + SIMPLE_KEY_CACHE_CB *keycache) { - DBUG_ENTER("reset_key_cache_counters"); - if (!key_cache->key_cache_inited) + DBUG_ENTER("reset_simple_key_cache_counters"); + if (!keycache->key_cache_inited) { DBUG_PRINT("info", ("Key cache %s not initialized.", name)); DBUG_RETURN(0); } DBUG_PRINT("info", ("Resetting counters for key cache %s.", name)); - key_cache->global_blocks_changed= 0; /* Key_blocks_not_flushed */ - key_cache->global_cache_r_requests= 0; /* Key_read_requests */ - key_cache->global_cache_read= 0; /* Key_reads */ - key_cache->global_cache_w_requests= 0; /* Key_write_requests */ - key_cache->global_cache_write= 0; /* Key_writes */ + keycache->global_blocks_changed= 0; /* Key_blocks_not_flushed */ + keycache->global_cache_r_requests= 0; /* Key_read_requests */ + keycache->global_cache_read= 0; /* Key_reads */ + keycache->global_cache_w_requests= 0; /* Key_write_requests */ + keycache->global_cache_write= 0; /* Key_writes */ DBUG_RETURN(0); } @@ -4230,9 +4539,10 @@ int reset_key_cache_counters(const char *name __attribute__((unused)), /* Test if disk-cache is ok */ -static void test_key_cache(KEY_CACHE *keycache __attribute__((unused)), - const char *where __attribute__((unused)), - my_bool lock __attribute__((unused))) +static +void test_key_cache(SIMPLE_KEY_CACHE_CB *keycache __attribute__((unused)), + const char *where __attribute__((unused)), + my_bool lock __attribute__((unused))) { /* TODO */ } @@ -4244,7 +4554,7 @@ static void test_key_cache(KEY_CACHE *keycache __attribute__((unused)), #define MAX_QUEUE_LEN 100 -static void keycache_dump(KEY_CACHE *keycache) +static void keycache_dump(SIMPLE_KEY_CACHE_CB *keycache) { FILE *keycache_dump_file=fopen(KEYCACHE_DUMP_FILE, "w"); struct st_my_thread_var *last; @@ -4484,7 +4794,7 @@ static int fail_hlink(HASH_LINK *hlink) return 0; /* Let the assert fail. */ } -static int cache_empty(KEY_CACHE *keycache) +static int cache_empty(SIMPLE_KEY_CACHE_CB *keycache) { int errcnt= 0; int idx; @@ -4522,3 +4832,1688 @@ static int cache_empty(KEY_CACHE *keycache) } #endif + +/* + Get statistics for a simple key cache + + SYNOPSIS + get_simple_key_cache_statistics() + keycache pointer to the control block of a simple key cache + partition_no partition number (not used) + key_cache_stats OUT pointer to the structure for the returned statistics + + DESCRIPTION + This function is the implementation of the get_key_cache_statistics + interface function that is employed by simple (non-partitioned) key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type SIMPLE_KEY_CACHE_CB for a simple key + cache. This function returns the statistical data for the key cache. + The parameter partition_no is not used by this function. + + RETURN + none +*/ + +static +void get_simple_key_cache_statistics(SIMPLE_KEY_CACHE_CB *keycache, + uint partition_no __attribute__((unused)), + KEY_CACHE_STATISTICS *keycache_stats) +{ + DBUG_ENTER("simple_get_key_cache_statistics"); + + keycache_stats->mem_size= (longlong) keycache->key_cache_mem_size; + keycache_stats->block_size= (longlong) keycache->key_cache_block_size; + keycache_stats->blocks_used= keycache->blocks_used; + keycache_stats->blocks_unused= keycache->blocks_unused; + keycache_stats->blocks_changed= keycache->global_blocks_changed; + keycache_stats->blocks_warm= keycache->warm_blocks; + keycache_stats->read_requests= keycache->global_cache_r_requests; + keycache_stats->reads= keycache->global_cache_read; + keycache_stats->write_requests= keycache->global_cache_w_requests; + keycache_stats->writes= keycache->global_cache_write; + DBUG_VOID_RETURN; +} + + +/* + The array of pointer to the key cache interface functions used for simple + key caches. Any simple key cache objects including those incorporated into + partitioned keys caches exploit this array. + + The current implementation of these functions allows to call them from + the MySQL server code directly. We don't do it though. +*/ + +static KEY_CACHE_FUNCS simple_key_cache_funcs = +{ + (INIT_KEY_CACHE) init_simple_key_cache, + (RESIZE_KEY_CACHE) resize_simple_key_cache, + (CHANGE_KEY_CACHE_PARAM) change_simple_key_cache_param, + (KEY_CACHE_READ) simple_key_cache_read, + (KEY_CACHE_INSERT) simple_key_cache_insert, + (KEY_CACHE_WRITE) simple_key_cache_write, + (FLUSH_KEY_BLOCKS) flush_simple_key_cache_blocks, + (RESET_KEY_CACHE_COUNTERS) reset_simple_key_cache_counters, + (END_KEY_CACHE) end_simple_key_cache, + (GET_KEY_CACHE_STATISTICS) get_simple_key_cache_statistics, +}; + + +/****************************************************************************** + Partitioned Key Cache Module + + The module contains implementations of all key cache interface functions + employed by partitioned key caches. + + A partitioned key cache is a collection of structures for simple key caches + called key cache partitions. Any page from a file can be placed into a buffer + of only one partition. The number of the partition is calculated from + the file number and the position of the page in the file, and it's always the + same for the page. The function that maps pages into partitions takes care + of even distribution of pages among partitions. + + Partition key cache mitigate one of the major problem of simple key cache: + thread contention for key cache lock (mutex). Every call of a key cache + interface function must acquire this lock. So threads compete for this lock + even in the case when they have acquired shared locks for the file and + pages they want read from are in the key cache buffers. + When working with a partitioned key cache any key cache interface function + that needs only one page has to acquire the key cache lock only for the + partition the page is ascribed to. This makes the chances for threads not + compete for the same key cache lock better. Unfortunately if we use a + partitioned key cache with N partitions for B-tree indexes we can't say + that the chances becomes N times less. The fact is that any index lookup + operation requires reading from the root page that, for any index, is always + ascribed to the same partition. To resolve this problem we should have + employed more sophisticated mechanisms of working with root pages. + + Currently the number of partitions in a partitioned key cache is limited + by 64. We could increase this limit. Simultaneously we would have to increase + accordingly the size of the bitmap dirty_part_map from the MYISAM_SHARE + structure. + +******************************************************************************/ + +/* Control block for a partitioned key cache */ + +typedef struct st_partitioned_key_cache_cb +{ + my_bool key_cache_inited; /*<=> control block is allocated */ + SIMPLE_KEY_CACHE_CB **partition_array; /* the key cache partitions */ + size_t key_cache_mem_size; /* specified size of the cache memory */ + uint key_cache_block_size; /* size of the page buffer of a cache block */ + uint partitions; /* number of partitions in the key cache */ +} PARTITIONED_KEY_CACHE_CB; + +static +void end_partitioned_key_cache(PARTITIONED_KEY_CACHE_CB *keycache, + my_bool cleanup); + +static int +reset_partitioned_key_cache_counters(const char *name, + PARTITIONED_KEY_CACHE_CB *keycache); + +/* + Determine the partition to which the index block to read is ascribed + + SYNOPSIS + get_key_cache_partition() + keycache pointer to the control block of a partitioned key cache + file handler for the file for the block of data to be read + filepos position of the block of data in the file + + DESCRIPTION + The function determines the number of the partition in whose buffer the + block from 'file' at the position filepos has to be placed for reading. + The function returns the control block of the simple key cache for this + partition to the caller. + + RETURN VALUE + The pointer to the control block of the partition to which the specified + file block is ascribed. +*/ + +static +SIMPLE_KEY_CACHE_CB * +get_key_cache_partition(PARTITIONED_KEY_CACHE_CB *keycache, + File file, my_off_t filepos) +{ + uint i= KEYCACHE_BASE_EXPR(file, filepos) % keycache->partitions; + return keycache->partition_array[i]; +} + + +/* + Determine the partition to which the index block to write is ascribed + + SYNOPSIS + get_key_cache_partition() + keycache pointer to the control block of a partitioned key cache + file handler for the file for the block of data to be read + filepos position of the block of data in the file + dirty_part_map pointer to the bitmap of dirty partitions for the file + + DESCRIPTION + The function determines the number of the partition in whose buffer the + block from 'file' at the position filepos has to be placed for writing and + marks the partition as dirty in the dirty_part_map bitmap. + The function returns the control block of the simple key cache for this + partition to the caller. + + RETURN VALUE + The pointer to the control block of the partition to which the specified + file block is ascribed. +*/ + +static SIMPLE_KEY_CACHE_CB +*get_key_cache_partition_for_write(PARTITIONED_KEY_CACHE_CB *keycache, + File file, my_off_t filepos, + ulonglong* dirty_part_map) +{ + uint i= KEYCACHE_BASE_EXPR( file, filepos) % keycache->partitions; + *dirty_part_map|= 1ULL << i; + return keycache->partition_array[i]; +} + + +/* + Initialize a partitioned key cache + + SYNOPSIS + init_partitioned_key_cache() + keycache pointer to the control block of a partitioned key cache + key_cache_block_size size of blocks to keep cached data + use_mem total memory to use for all key cache partitions + division_limit division limit (may be zero) + age_threshold age threshold (may be zero) + + DESCRIPTION + This function is the implementation of the init_key_cache interface function + that is employed by partitioned key caches. + The function builds and initializes an array of simple key caches, and then + initializes the control block structure of the type PARTITIONED_KEY_CACHE_CB + that is used for a partitioned key cache. The parameter keycache is + supposed to point to this structure. The number of partitions in the + partitioned key cache to be built must be passed through the field + 'partitions' of this structure. The parameter key_cache_block_size specifies + the size of the blocks in the the simple key caches to be built. + The parameters division_limit and age_threshold determine the initial + values of those characteristics of the simple key caches that are used for + midpoint insertion strategy. The parameter use_mem specifies the total + amount of memory to be allocated for the key cache blocks in all simple key + caches and for all auxiliary structures. + + RETURN VALUE + total number of blocks in key cache partitions, if successful, + <= 0 - otherwise. + + NOTES + If keycache->key_cache_inited != 0 then we assume that the memory for + the array of partitions has been already allocated. + + It's assumed that no two threads call this function simultaneously + referring to the same key cache handle. +*/ + +static +int init_partitioned_key_cache(PARTITIONED_KEY_CACHE_CB *keycache, + uint key_cache_block_size, + size_t use_mem, uint division_limit, + uint age_threshold) +{ + int i; + size_t mem_per_cache; + size_t mem_decr; + int cnt; + SIMPLE_KEY_CACHE_CB *partition; + SIMPLE_KEY_CACHE_CB **partition_ptr; + uint partitions= keycache->partitions; + int blocks= 0; + DBUG_ENTER("partitioned_init_key_cache"); + + keycache->key_cache_block_size = key_cache_block_size; + + if (keycache->key_cache_inited) + partition_ptr= keycache->partition_array; + else + { + if(!(partition_ptr= + (SIMPLE_KEY_CACHE_CB **) my_malloc(sizeof(SIMPLE_KEY_CACHE_CB *) * + partitions, MYF(MY_WME)))) + DBUG_RETURN(-1); + bzero(partition_ptr, sizeof(SIMPLE_KEY_CACHE_CB *) * partitions); + keycache->partition_array= partition_ptr; + } + + mem_per_cache = use_mem / partitions; + mem_decr= mem_per_cache / 5; + + for (i= 0; i < (int) partitions; i++) + { + my_bool key_cache_inited= keycache->key_cache_inited; + if (key_cache_inited) + partition= *partition_ptr; + else + { + if (!(partition= + (SIMPLE_KEY_CACHE_CB *) my_malloc(sizeof(SIMPLE_KEY_CACHE_CB), + MYF(MY_WME)))) + continue; + partition->key_cache_inited= 0; + } + + cnt= init_simple_key_cache(partition, key_cache_block_size, mem_per_cache, + division_limit, age_threshold); + if (cnt <= 0) + { + end_simple_key_cache(partition, 1); + if (!key_cache_inited) + { + my_free(partition); + partition= 0; + } + if ((i == 0 && cnt < 0) || i > 0) + { + /* + Here we have two cases: + 1. i == 0 and cnt < 0 + cnt < 0 => mem_per_cache is not big enough to allocate minimal + number of key blocks in the key cache of the partition. + Decrease the the number of the partitions by 1 and start again. + 2. i > 0 + There is not enough memory for one of the succeeding partitions. + Just skip this partition decreasing the number of partitions in + the key cache by one. + Do not change the value of mem_per_cache in both cases. + */ + if (key_cache_inited) + { + my_free(partition); + partition= 0; + if(key_cache_inited) + memmove(partition_ptr, partition_ptr+1, + sizeof(partition_ptr)*(partitions-i-1)); + } + if (!--partitions) + break; + } + else + { + /* + We come here when i == 0 && cnt == 0. + cnt == 0 => the memory allocator fails to allocate a block of + memory of the size mem_per_cache. Decrease the value of + mem_per_cache without changing the current number of partitions + and start again. Make sure that such a decrease may happen not + more than 5 times in total. + */ + if (use_mem <= mem_decr) + break; + use_mem-= mem_decr; + } + i--; + mem_per_cache= use_mem/partitions; + continue; + } + else + { + blocks+= cnt; + *partition_ptr++= partition; + } + } + + keycache->partitions= partitions= partition_ptr-keycache->partition_array; + keycache->key_cache_mem_size= mem_per_cache * partitions; + for (i= 0; i < (int) partitions; i++) + keycache->partition_array[i]->hash_factor= partitions; + + keycache->key_cache_inited= 1; + + if (!partitions) + blocks= -1; + + DBUG_RETURN(blocks); +} + + +/* + Resize a partitioned key cache + + SYNOPSIS + resize_partitioned_key_cache() + keycache pointer to the control block of a partitioned key cache + key_cache_block_size size of blocks to keep cached data + use_mem total memory to use for the new key cache + division_limit new division limit (if not zero) + age_threshold new age threshold (if not zero) + + DESCRIPTION + This function is the implementation of the resize_key_cache interface + function that is employed by partitioned key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type PARTITIONED_KEY_CACHE_CB for the + partitioned key cache to be resized. + The parameter key_cache_block_size specifies the new size of the blocks in + the simple key caches that comprise the partitioned key cache. + The parameters division_limit and age_threshold determine the new initial + values of those characteristics of the simple key cache that are used for + midpoint insertion strategy. The parameter use-mem specifies the total + amount of memory to be allocated for the key cache blocks in all new + simple key caches and for all auxiliary structures. + + RETURN VALUE + number of blocks in the key cache, if successful, + 0 - otherwise. + + NOTES. + The function first calls prepare_resize_simple_key_cache for each simple + key cache effectively flushing all dirty pages from it and destroying + the key cache. Then init_partitioned_key_cache is called. This call builds + a new array of simple key caches containing the same number of elements + as the old one. After this the function calls the function + finish_resize_simple_key_cache for each simple key cache from this array. + + This implementation doesn't block the calls and executions of other + functions from the key cache interface. However it assumes that the + calls of resize_partitioned_key_cache itself are serialized. +*/ + +static +int resize_partitioned_key_cache(PARTITIONED_KEY_CACHE_CB *keycache, + uint key_cache_block_size, + size_t use_mem, uint division_limit, + uint age_threshold) +{ + uint i; + uint partitions= keycache->partitions; + my_bool cleanup= use_mem == 0; + int blocks= -1; + int err= 0; + DBUG_ENTER("partitioned_resize_key_cache"); + if (cleanup) + { + end_partitioned_key_cache(keycache, 0); + DBUG_RETURN(-1); + } + for (i= 0; i < partitions; i++) + { + err|= prepare_resize_simple_key_cache(keycache->partition_array[i], 1); + } + if (!err) + blocks= init_partitioned_key_cache(keycache, key_cache_block_size, + use_mem, division_limit, age_threshold); + if (blocks > 0) + { + for (i= 0; i < partitions; i++) + { + finish_resize_simple_key_cache(keycache->partition_array[i], 1); + } + } + DBUG_RETURN(blocks); +} + + +/* + Change key cache parameters of a partitioned key cache + + SYNOPSIS + partitioned_change_key_cache_param() + keycache pointer to the control block of a partitioned key cache + division_limit new division limit (if not zero) + age_threshold new age threshold (if not zero) + + DESCRIPTION + This function is the implementation of the change_key_cache_param interface + function that is employed by partitioned key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type PARTITIONED_KEY_CACHE_CB for the simple + key cache where new values of the division limit and the age threshold used + for midpoint insertion strategy are to be set. The parameters + division_limit and age_threshold provide these new values. + + RETURN VALUE + none + + NOTES + The function just calls change_simple_key_cache_param for each element from + the array of simple caches that comprise the partitioned key cache. +*/ + +static +void change_partitioned_key_cache_param(PARTITIONED_KEY_CACHE_CB *keycache, + uint division_limit, + uint age_threshold) +{ + uint i; + uint partitions= keycache->partitions; + DBUG_ENTER("partitioned_change_key_cache_param"); + for (i= 0; i < partitions; i++) + { + change_simple_key_cache_param(keycache->partition_array[i], division_limit, + age_threshold); + } + DBUG_VOID_RETURN; +} + + +/* + Destroy a partitioned key cache + + SYNOPSIS + end_partitioned_key_cache() + keycache pointer to the control block of a partitioned key cache + cleanup <=> complete free (free also control block structures + for all simple key caches) + + DESCRIPTION + This function is the implementation of the end_key_cache interface + function that is employed by partitioned key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type PARTITIONED_KEY_CACHE_CB for the + partitioned key cache to be destroyed. + The function frees the memory allocated for the cache blocks and + auxiliary structures used by simple key caches that comprise the + partitioned key cache. If the value of the parameter cleanup is TRUE + then even the memory used for control blocks of the simple key caches + and the array of pointers to them are freed. + + RETURN VALUE + none +*/ + +static +void end_partitioned_key_cache(PARTITIONED_KEY_CACHE_CB *keycache, + my_bool cleanup) +{ + uint i; + uint partitions= keycache->partitions; + DBUG_ENTER("partitioned_end_key_cache"); + DBUG_PRINT("enter", ("key_cache: 0x%lx", (long) keycache)); + + for (i= 0; i < partitions; i++) + { + end_simple_key_cache(keycache->partition_array[i], cleanup); + } + if (cleanup) + { + for (i= 0; i < partitions; i++) + my_free(keycache->partition_array[i]); + my_free(keycache->partition_array); + keycache->key_cache_inited= 0; + } + DBUG_VOID_RETURN; +} + + +/* + Read a block of data from a partitioned key cache into a buffer + + SYNOPSIS + + partitioned_key_cache_read() + keycache pointer to the control block of a partitioned key cache + file handler for the file for the block of data to be read + filepos position of the block of data in the file + level determines the weight of the data + buff buffer to where the data must be placed + length length of the buffer + block_length length of the read data from a key cache block + return_buffer return pointer to the key cache buffer with the data + + DESCRIPTION + This function is the implementation of the key_cache_read interface + function that is employed by partitioned key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type PARTITIONED_KEY_CACHE_CB for a + partitioned key cache. + In a general case the function reads a block of data from the key cache + into the buffer buff of the size specified by the parameter length. The + beginning of the block of data to be read is specified by the parameters + file and filepos. The length of the read data is the same as the length + of the buffer. The data is read into the buffer in key_cache_block_size + increments. To read each portion the function first finds out in what + partition of the key cache this portion(page) is to be saved, and calls + simple_key_cache_read with the pointer to the corresponding simple key as + its first parameter. + If the parameter return_buffer is not ignored and its value is TRUE, and + the data to be read of the specified size block_length can be read from one + key cache buffer, then the function returns a pointer to the data in the + key cache buffer. + The function takes into account parameters block_length and return buffer + only in a single-threaded environment. + The parameter 'level' is used only by the midpoint insertion strategy + when the data or its portion cannot be found in the key cache. + + RETURN VALUE + Returns address from where the data is placed if successful, 0 - otherwise. +*/ + +static +uchar *partitioned_key_cache_read(PARTITIONED_KEY_CACHE_CB *keycache, + File file, my_off_t filepos, int level, + uchar *buff, uint length, + uint block_length __attribute__((unused)), + int return_buffer __attribute__((unused))) +{ + uint r_length; + uint offset= (uint) (filepos % keycache->key_cache_block_size); + uchar *start= buff; + DBUG_ENTER("partitioned_key_cache_read"); + DBUG_PRINT("enter", ("fd: %u pos: %lu length: %u", + (uint) file, (ulong) filepos, length)); + + + /* Read data in key_cache_block_size increments */ + do + { + SIMPLE_KEY_CACHE_CB *partition= get_key_cache_partition(keycache, + file, filepos); + uchar *ret_buff= 0; + r_length= length; + set_if_smaller(r_length, keycache->key_cache_block_size - offset); + ret_buff= simple_key_cache_read((void *) partition, + file, filepos, level, + buff, r_length, + block_length, return_buffer); + if (ret_buff == 0) + DBUG_RETURN(0); + filepos+= r_length; + buff+= r_length; + offset= 0; + } while ((length-= r_length)); + + DBUG_RETURN(start); +} + + +/* + Insert a block of file data from a buffer into a partitioned key cache + + SYNOPSIS + partitioned_key_cache_insert() + keycache pointer to the control block of a partitioned key cache + file handler for the file to insert data from + filepos position of the block of data in the file to insert + level determines the weight of the data + buff buffer to read data from + length length of the data in the buffer + + DESCRIPTION + This function is the implementation of the key_cache_insert interface + function that is employed by partitioned key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type PARTITIONED_KEY_CACHE_CB for a + partitioned key cache. + The function writes a block of file data from a buffer into the key cache. + The buffer is specified with the parameters buff and length - the pointer + to the beginning of the buffer and its size respectively. It's assumed + that the buffer contains the data from 'file' allocated from the position + filepos. The data is copied from the buffer in key_cache_block_size + increments. For every portion of data the function finds out in what simple + key cache from the array of partitions the data must be stored, and after + this calls simple_key_cache_insert to copy the data into a key buffer of + this simple key cache. + The parameter level is used to set one characteristic for the key buffers + loaded with the data from buff. The characteristic is used only by the + midpoint insertion strategy. + + RETURN VALUE + 0 if a success, 1 - otherwise. + + NOTES + The function is used by MyISAM to move all blocks from a index file to + the key cache. It can be performed in parallel with reading the file data + from the key buffers by other threads. +*/ + +static +int partitioned_key_cache_insert(PARTITIONED_KEY_CACHE_CB *keycache, + File file, my_off_t filepos, int level, + uchar *buff, uint length) +{ + uint w_length; + uint offset= (uint) (filepos % keycache->key_cache_block_size); + DBUG_ENTER("partitioned_key_cache_insert"); + DBUG_PRINT("enter", ("fd: %u pos: %lu length: %u", + (uint) file,(ulong) filepos, length)); + + + /* Write data in key_cache_block_size increments */ + do + { + SIMPLE_KEY_CACHE_CB *partition= get_key_cache_partition(keycache, + file, filepos); + w_length= length; + set_if_smaller(w_length, keycache->key_cache_block_size - offset); + if (simple_key_cache_insert((void *) partition, + file, filepos, level, + buff, w_length)) + DBUG_RETURN(1); + + filepos+= w_length; + buff+= w_length; + offset = 0; + } while ((length-= w_length)); + + DBUG_RETURN(0); +} + + +/* + Write data from a buffer into a partitioned key cache + + SYNOPSIS + + partitioned_key_cache_write() + keycache pointer to the control block of a partitioned key cache + file handler for the file to write data to + filepos position in the file to write data to + level determines the weight of the data + buff buffer with the data + length length of the buffer + dont_write if is 0 then all dirty pages involved in writing + should have been flushed from key cache + file_extra maps of key cache partitions containing + dirty pages from file + + DESCRIPTION + This function is the implementation of the key_cache_write interface + function that is employed by partitioned key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type PARTITIONED_KEY_CACHE_CB for a + partitioned key cache. + In a general case the function copies data from a buffer into the key + cache. The buffer is specified with the parameters buff and length - + the pointer to the beginning of the buffer and its size respectively. + It's assumed the buffer contains the data to be written into 'file' + starting from the position filepos. The data is copied from the buffer + in key_cache_block_size increments. For every portion of data the + function finds out in what simple key cache from the array of partitions + the data must be stored, and after this calls simple_key_cache_write to + copy the data into a key buffer of this simple key cache. + If the value of the parameter dont_write is FALSE then the function + also writes the data into file. + The parameter level is used to set one characteristic for the key buffers + filled with the data from buff. The characteristic is employed only by + the midpoint insertion strategy. + The parameter file_expra provides a pointer to the shared bitmap of + the partitions that may contains dirty pages for the file. This bitmap + is used to optimize the function flush_partitioned_key_cache_blocks. + + RETURN VALUE + 0 if a success, 1 - otherwise. + + NOTES + This implementation exploits the fact that the function is called only + when a thread has got an exclusive lock for the key file. +*/ + +static +int partitioned_key_cache_write(PARTITIONED_KEY_CACHE_CB *keycache, + File file, void *file_extra, + my_off_t filepos, int level, + uchar *buff, uint length, + uint block_length __attribute__((unused)), + int dont_write) +{ + uint w_length; + ulonglong *part_map= (ulonglong *) file_extra; + uint offset= (uint) (filepos % keycache->key_cache_block_size); + DBUG_ENTER("partitioned_key_cache_write"); + DBUG_PRINT("enter", + ("fd: %u pos: %lu length: %u block_length: %u" + " key_block_length: %u", + (uint) file, (ulong) filepos, length, block_length, + keycache ? keycache->key_cache_block_size : 0)); + + + /* Write data in key_cache_block_size increments */ + do + { + SIMPLE_KEY_CACHE_CB *partition= get_key_cache_partition_for_write(keycache, + file, + filepos, + part_map); + w_length = length; + set_if_smaller(w_length, keycache->key_cache_block_size - offset ); + if (simple_key_cache_write(partition, + file, 0, filepos, level, + buff, w_length, block_length, + dont_write)) + DBUG_RETURN(1); + + filepos+= w_length; + buff+= w_length; + offset= 0; + } while ((length-= w_length)); + + DBUG_RETURN(0); +} + + +/* + Flush all blocks for a file from key buffers of a partitioned key cache + + SYNOPSIS + + flush_partitioned_key_cache_blocks() + keycache pointer to the control block of a partitioned key cache + file handler for the file to flush to + file_extra maps of key cache partitions containing + dirty pages from file (not used) + flush_type type of the flush operation + + DESCRIPTION + This function is the implementation of the flush_key_blocks interface + function that is employed by partitioned key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type PARTITIONED_KEY_CACHE_CB for a + partitioned key cache. + In a general case the function flushes the data from all dirty key + buffers related to the file 'file' into this file. The function does + exactly this if the value of the parameter type is FLUSH_KEEP. If the + value of this parameter is FLUSH_RELEASE, the function additionally + releases the key buffers containing data from 'file' for new usage. + If the value of the parameter type is FLUSH_IGNORE_CHANGED the function + just releases the key buffers containing data from 'file'. + The function performs the operation by calling the function + flush_simple_key_cache_blocks for the elements of the array of the + simple key caches that comprise the partitioned key_cache. If the value + of the parameter type is FLUSH_KEEP s_flush_key_blocks is called only + for the partitions with possibly dirty pages marked in the bitmap + pointed to by the parameter file_extra. + + RETURN + 0 ok + 1 error + + NOTES + This implementation exploits the fact that the function is called only + when a thread has got an exclusive lock for the key file. +*/ + +static +int flush_partitioned_key_cache_blocks(PARTITIONED_KEY_CACHE_CB *keycache, + File file, void *file_extra, + enum flush_type type) +{ + uint i; + uint partitions= keycache->partitions; + int err= 0; + ulonglong *dirty_part_map= (ulonglong *) file_extra; + DBUG_ENTER("partitioned_flush_key_blocks"); + DBUG_PRINT("enter", ("keycache: 0x%lx", (long) keycache)); + + for (i= 0; i < partitions; i++) + { + SIMPLE_KEY_CACHE_CB *partition= keycache->partition_array[i]; + if ((type == FLUSH_KEEP || type == FLUSH_FORCE_WRITE) && + !((*dirty_part_map) & ((ulonglong) 1 << i))) + continue; + err|= test(flush_simple_key_cache_blocks(partition, file, 0, type)); + } + *dirty_part_map= 0; + + DBUG_RETURN(err); +} + + +/* + Reset the counters of a partitioned key cache + + SYNOPSIS + reset_partitioned_key_cache_counters() + name the name of a key cache + keycache pointer to the control block of a partitioned key cache + + DESCRIPTION + This function is the implementation of the reset_key_cache_counters + interface function that is employed by partitioned key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type PARTITIONED_KEY_CACHE_CB for a partitioned + key cache. + This function resets the values of the statistical counters of the simple + key caches comprising partitioned key cache to 0. It does it by calling + reset_simple_key_cache_counters for each key cache partition. + The parameter name is currently not used. + + RETURN + 0 on success (always because it can't fail) +*/ + +static int +reset_partitioned_key_cache_counters(const char *name __attribute__((unused)), + PARTITIONED_KEY_CACHE_CB *keycache) +{ + uint i; + uint partitions= keycache->partitions; + DBUG_ENTER("partitioned_reset_key_cache_counters"); + + for (i = 0; i < partitions; i++) + { + reset_simple_key_cache_counters(name, keycache->partition_array[i]); + } + DBUG_RETURN(0); +} + + +/* + Get statistics for a partition key cache + + SYNOPSIS + get_partitioned_key_cache_statistics() + keycache pointer to the control block of a partitioned key cache + partition_no partition number to get statistics for + key_cache_stats OUT pointer to the structure for the returned statistics + + DESCRIPTION + This function is the implementation of the get_key_cache_statistics + interface function that is employed by partitioned key caches. + The function takes the parameter keycache as a pointer to the + control block structure of the type PARTITIONED_KEY_CACHE_CB for + a partitioned key cache. + If the value of the parameter partition_no is equal to 0 then aggregated + statistics for all partitions is returned in the fields of the + structure key_cache_stat of the type KEY_CACHE_STATISTICS . Otherwise + the function returns data for the partition number partition_no of the + key cache in the structure key_cache_stat. (Here partitions are numbered + starting from 1.) + + RETURN + none +*/ + +static +void +get_partitioned_key_cache_statistics(PARTITIONED_KEY_CACHE_CB *keycache, + uint partition_no, + KEY_CACHE_STATISTICS *keycache_stats) +{ + uint i; + SIMPLE_KEY_CACHE_CB *partition; + uint partitions= keycache->partitions; + DBUG_ENTER("get_partitioned_key_cache_statistics"); + + if (partition_no != 0) + { + partition= keycache->partition_array[partition_no-1]; + get_simple_key_cache_statistics((void *) partition, 0, keycache_stats); + DBUG_VOID_RETURN; + } + bzero(keycache_stats, sizeof(KEY_CACHE_STATISTICS)); + keycache_stats->mem_size= (longlong) keycache->key_cache_mem_size; + keycache_stats->block_size= (longlong) keycache->key_cache_block_size; + for (i = 0; i < partitions; i++) + { + partition= keycache->partition_array[i]; + keycache_stats->blocks_used+= partition->blocks_used; + keycache_stats->blocks_unused+= partition->blocks_unused; + keycache_stats->blocks_changed+= partition->global_blocks_changed; + keycache_stats->blocks_warm+= partition->warm_blocks; + keycache_stats->read_requests+= partition->global_cache_r_requests; + keycache_stats->reads+= partition->global_cache_read; + keycache_stats->write_requests+= partition->global_cache_w_requests; + keycache_stats->writes+= partition->global_cache_write; + } + DBUG_VOID_RETURN; +} + +/* + The array of pointers to the key cache interface functions used by + partitioned key caches. Any partitioned key cache object caches exploits + this array. + + The current implementation of these functions does not allow to call + them from the MySQL server code directly. The key cache interface + wrappers must be used for this purpose. +*/ + +static KEY_CACHE_FUNCS partitioned_key_cache_funcs = +{ + (INIT_KEY_CACHE) init_partitioned_key_cache, + (RESIZE_KEY_CACHE) resize_partitioned_key_cache, + (CHANGE_KEY_CACHE_PARAM) change_partitioned_key_cache_param, + (KEY_CACHE_READ) partitioned_key_cache_read, + (KEY_CACHE_INSERT) partitioned_key_cache_insert, + (KEY_CACHE_WRITE) partitioned_key_cache_write, + (FLUSH_KEY_BLOCKS) flush_partitioned_key_cache_blocks, + (RESET_KEY_CACHE_COUNTERS) reset_partitioned_key_cache_counters, + (END_KEY_CACHE) end_partitioned_key_cache, + (GET_KEY_CACHE_STATISTICS) get_partitioned_key_cache_statistics, +}; + + +/****************************************************************************** + Key Cache Interface Module + + The module contains wrappers for all key cache interface functions. + + Currently there are key caches of two types: simple key caches and + partitioned key caches. Each type (class) has its own implementation of the + basic key cache operations used the MyISAM storage engine. The pointers + to the implementation functions are stored in two static structures of the + type KEY_CACHE_FUNC: simple_key_cache_funcs - for simple key caches, and + partitioned_key_cache_funcs - for partitioned key caches. When a key cache + object is created the constructor procedure init_key_cache places a pointer + to the corresponding table into one of its fields. The procedure also + initializes a control block for the key cache oject and saves the pointer + to this block in another field of the key cache object. + When a key cache wrapper function is invoked for a key cache object to + perform a basic key cache operation it looks into the interface table + associated with the key cache oject and calls the corresponding + implementation of the operation. It passes the saved key cache control + block to this implementation. If, for some reasons, the control block + has not been fully initialized yet, the wrapper function either does not + do anything or, in the case when it perform a read/write operation, the + function do it directly through the system i/o functions. + + As we can see the model with which the key cache interface is supported + as quite conventional for interfaces in general. + +******************************************************************************/ + +static +int repartition_key_cache_internal(KEY_CACHE *keycache, + uint key_cache_block_size, size_t use_mem, + uint division_limit, uint age_threshold, + uint partitions, my_bool use_op_lock); + +/* + Initialize a key cache : internal + + SYNOPSIS + init_key_cache_internal() + keycache pointer to the key cache to be initialized + key_cache_block_size size of blocks to keep cached data + use_mem total memory to use for cache buffers/structures + division_limit division limit (may be zero) + age_threshold age threshold (may be zero) + partitions number of partitions in the key cache + use_op_lock if TRUE use keycache->op_lock, otherwise - ignore it + + DESCRIPTION + The function performs the actions required from init_key_cache(). + It has an additional parameter: use_op_lock. When the parameter + is TRUE than the function initializes keycache->op_lock if needed, + then locks it, and unlocks it before the return. Otherwise the actions + with the lock are omitted. + + RETURN VALUE + total number of blocks in key cache partitions, if successful, + <= 0 - otherwise. + + NOTES + if keycache->key_cache_inited != 0 we assume that the memory + for the control block of the key cache has been already allocated. +*/ + +static +int init_key_cache_internal(KEY_CACHE *keycache, uint key_cache_block_size, + size_t use_mem, uint division_limit, + uint age_threshold, uint partitions, + my_bool use_op_lock) +{ + void *keycache_cb; + int blocks; + if (keycache->key_cache_inited) + { + if (use_op_lock) + pthread_mutex_lock(&keycache->op_lock); + keycache_cb= keycache->keycache_cb; + } + else + { + if (partitions == 0) + { + if (!(keycache_cb= (void *) my_malloc(sizeof(SIMPLE_KEY_CACHE_CB), + MYF(0)))) + return 0; + ((SIMPLE_KEY_CACHE_CB *) keycache_cb)->key_cache_inited= 0; + keycache->key_cache_type= SIMPLE_KEY_CACHE; + keycache->interface_funcs= &simple_key_cache_funcs; + } + else + { + if (!(keycache_cb= (void *) my_malloc(sizeof(PARTITIONED_KEY_CACHE_CB), + MYF(0)))) + return 0; + ((PARTITIONED_KEY_CACHE_CB *) keycache_cb)->key_cache_inited= 0; + keycache->key_cache_type= PARTITIONED_KEY_CACHE; + keycache->interface_funcs= &partitioned_key_cache_funcs; + } + /* + Initialize op_lock if it's not initialized before. + The mutex may have been initialized before if we are being called + from repartition_key_cache_internal(). + */ + if (use_op_lock) + pthread_mutex_init(&keycache->op_lock, MY_MUTEX_INIT_FAST); + keycache->keycache_cb= keycache_cb; + keycache->key_cache_inited= 1; + if (use_op_lock) + pthread_mutex_lock(&keycache->op_lock); + } + + if (partitions != 0) + { + ((PARTITIONED_KEY_CACHE_CB *) keycache_cb)->partitions= partitions; + } + keycache->can_be_used= 0; + blocks= keycache->interface_funcs->init(keycache_cb, key_cache_block_size, + use_mem, division_limit, + age_threshold); + keycache->partitions= partitions ? + ((PARTITIONED_KEY_CACHE_CB *) keycache_cb)->partitions : + 0; + DBUG_ASSERT(partitions <= MAX_KEY_CACHE_PARTITIONS); + keycache->key_cache_mem_size= + keycache->partitions ? + ((PARTITIONED_KEY_CACHE_CB *) keycache_cb)->key_cache_mem_size : + ((SIMPLE_KEY_CACHE_CB *) keycache_cb)->key_cache_mem_size; + if (blocks > 0) + keycache->can_be_used= 1; + if (use_op_lock) + pthread_mutex_unlock(&keycache->op_lock); + return blocks; +} + + +/* + Initialize a key cache + + SYNOPSIS + init_key_cache() + keycache pointer to the key cache to be initialized + key_cache_block_size size of blocks to keep cached data + use_mem total memory to use for cache buffers/structures + division_limit division limit (may be zero) + age_threshold age threshold (may be zero) + partitions number of partitions in the key cache + + DESCRIPTION + The function creates a control block structure for a key cache and + places the pointer to this block in the structure keycache. + If the value of the parameter 'partitions' is 0 then a simple key cache + is created. Otherwise a partitioned key cache with the specified number + of partitions is created. + The parameter key_cache_block_size specifies the size of the blocks in + the key cache to be created. The parameters division_limit and + age_threshold determine the initial values of those characteristics of + the key cache that are used for midpoint insertion strategy. The parameter + use_mem specifies the total amount of memory to be allocated for the + key cache buffers and for all auxiliary structures. + The function calls init_key_cache_internal() to perform all these actions + with the last parameter set to TRUE. + + RETURN VALUE + total number of blocks in key cache partitions, if successful, + <= 0 - otherwise. + + NOTES + It's assumed that no two threads call this function simultaneously + referring to the same key cache handle. +*/ + +int init_key_cache(KEY_CACHE *keycache, uint key_cache_block_size, + size_t use_mem, uint division_limit, + uint age_threshold, uint partitions) +{ + return init_key_cache_internal(keycache, key_cache_block_size, use_mem, + division_limit, age_threshold, partitions, 1); +} + + +/* + Resize a key cache + + SYNOPSIS + resize_key_cache() + keycache pointer to the key cache to be resized + key_cache_block_size size of blocks to keep cached data + use_mem total memory to use for the new key cache + division_limit new division limit (if not zero) + age_threshold new age threshold (if not zero) + + DESCRIPTION + The function operates over the key cache key cache. + The parameter key_cache_block_size specifies the new size of the block + buffers in the key cache. The parameters division_limit and age_threshold + determine the new initial values of those characteristics of the key cache + that are used for midpoint insertion strategy. The parameter use_mem + specifies the total amount of memory to be allocated for the key cache + buffers and for all auxiliary structures. + + RETURN VALUE + number of blocks in the key cache, if successful, + 0 - otherwise. + + NOTES + The function does not block the calls and executions of other functions + from the key cache interface. However it assumes that the calls of + resize_key_cache itself are serialized. + + Currently the function is called when the values of the variables + key_buffer_size and/or key_cache_block_size are being reset for + the key cache keycache. +*/ + +int resize_key_cache(KEY_CACHE *keycache, uint key_cache_block_size, + size_t use_mem, uint division_limit, uint age_threshold) +{ + int blocks= -1; + if (keycache->key_cache_inited) + { + pthread_mutex_lock(&keycache->op_lock); + if ((uint) keycache->param_partitions != keycache->partitions && use_mem) + blocks= repartition_key_cache_internal(keycache, + key_cache_block_size, use_mem, + division_limit, age_threshold, + (uint) keycache->param_partitions, + 0); + else + { + blocks= keycache->interface_funcs->resize(keycache->keycache_cb, + key_cache_block_size, + use_mem, division_limit, + age_threshold); + + if (keycache->partitions) + keycache->partitions= + ((PARTITIONED_KEY_CACHE_CB *)(keycache->keycache_cb))->partitions; + } + + keycache->key_cache_mem_size= + keycache->partitions ? + ((PARTITIONED_KEY_CACHE_CB *)(keycache->keycache_cb))->key_cache_mem_size : + ((SIMPLE_KEY_CACHE_CB *)(keycache->keycache_cb))->key_cache_mem_size; + + keycache->can_be_used= (blocks >= 0); + pthread_mutex_unlock(&keycache->op_lock); + } + return blocks; +} + + +/* + Change key cache parameters of a key cache + + SYNOPSIS + change_key_cache_param() + keycache pointer to the key cache to change parameters for + division_limit new division limit (if not zero) + age_threshold new age threshold (if not zero) + + DESCRIPTION + The function sets new values of the division limit and the age threshold + used when the key cache keycach employs midpoint insertion strategy. + The parameters division_limit and age_threshold provide these new values. + + RETURN VALUE + none + + NOTES + Currently the function is called when the values of the variables + key_cache_division_limit and/or key_cache_age_threshold are being reset + for the key cache keycache. +*/ + +void change_key_cache_param(KEY_CACHE *keycache, uint division_limit, + uint age_threshold) +{ + if (keycache->key_cache_inited) + { + pthread_mutex_lock(&keycache->op_lock); + keycache->interface_funcs->change_param(keycache->keycache_cb, + division_limit, + age_threshold); + pthread_mutex_unlock(&keycache->op_lock); + } +} + + +/* + Destroy a key cache : internal + + SYNOPSIS + end_key_cache_internal() + keycache pointer to the key cache to be destroyed + cleanup <=> complete free + use_op_lock if TRUE use keycache->op_lock, otherwise - ignore it + + DESCRIPTION + The function performs the actions required from end_key_cache(). + It has an additional parameter: use_op_lock. When the parameter + is TRUE than the function destroys keycache->op_lock if cleanup is true. + Otherwise the action with the lock is omitted. + + RETURN VALUE + none +*/ + +static +void end_key_cache_internal(KEY_CACHE *keycache, my_bool cleanup, + my_bool use_op_lock) +{ + if (keycache->key_cache_inited) + { + keycache->interface_funcs->end(keycache->keycache_cb, cleanup); + if (cleanup) + { + if (keycache->keycache_cb) + { + my_free(keycache->keycache_cb); + keycache->keycache_cb= 0; + } + /* + We do not destroy op_lock if we are going to reuse the same key cache. + This happens if we are called from repartition_key_cache_internal(). + */ + if (use_op_lock) + pthread_mutex_destroy(&keycache->op_lock); + keycache->key_cache_inited= 0; + } + keycache->can_be_used= 0; + } +} + + +/* + Destroy a key cache + + SYNOPSIS + end_key_cache() + keycache pointer to the key cache to be destroyed + cleanup <=> complete free + + DESCRIPTION + The function frees the memory allocated for the cache blocks and + auxiliary structures used by the key cache keycache. If the value + of the parameter cleanup is TRUE then all resources used by the key + cache are to be freed. + The function calls end_key_cache_internal() to perform all these actions + with the last parameter set to TRUE. + + RETURN VALUE + none +*/ + +void end_key_cache(KEY_CACHE *keycache, my_bool cleanup) +{ + end_key_cache_internal(keycache, cleanup, 1); +} + + +/* + Read a block of data from a key cache into a buffer + + SYNOPSIS + + key_cache_read() + keycache pointer to the key cache to read data from + file handler for the file for the block of data to be read + filepos position of the block of data in the file + level determines the weight of the data + buff buffer to where the data must be placed + length length of the buffer + block_length length of the data read from a key cache block + return_buffer return pointer to the key cache buffer with the data + + DESCRIPTION + The function operates over buffers of the key cache keycache. + In a general case the function reads a block of data from the key cache + into the buffer buff of the size specified by the parameter length. The + beginning of the block of data to be read is specified by the parameters + file and filepos. The length of the read data is the same as the length + of the buffer. + If the parameter return_buffer is not ignored and its value is TRUE, and + the data to be read of the specified size block_length can be read from one + key cache buffer, then the function returns a pointer to the data in the + key cache buffer. + The parameter 'level' is used only by the midpoint insertion strategy + when the data or its portion cannot be found in the key cache. + The function reads data into the buffer directly from file if the control + block of the key cache has not been initialized yet. + + RETURN VALUE + Returns address from where the data is placed if successful, 0 - otherwise. + + NOTES. + Filepos must be a multiple of 'block_length', but it doesn't + have to be a multiple of key_cache_block_size; +*/ + +uchar *key_cache_read(KEY_CACHE *keycache, + File file, my_off_t filepos, int level, + uchar *buff, uint length, + uint block_length, int return_buffer) +{ + if (keycache->can_be_used) + return keycache->interface_funcs->read(keycache->keycache_cb, + file, filepos, level, + buff, length, + block_length, return_buffer); + + /* We can't use mutex here as the key cache may not be initialized */ + + if (my_pread(file, (uchar*) buff, length, filepos, MYF(MY_NABP))) + return (uchar *) 0; + + return buff; +} + + +/* + Insert a block of file data from a buffer into a key cache + + SYNOPSIS + key_cache_insert() + keycache pointer to the key cache to insert data into + file handler for the file to insert data from + filepos position of the block of data in the file to insert + level determines the weight of the data + buff buffer to read data from + length length of the data in the buffer + + DESCRIPTION + The function operates over buffers of the key cache keycache. + The function writes a block of file data from a buffer into the key cache. + The buffer is specified with the parameters buff and length - the pointer + to the beginning of the buffer and its size respectively. It's assumed + that the buffer contains the data from 'file' allocated from the position + filepos. + The parameter level is used to set one characteristic for the key buffers + loaded with the data from buff. The characteristic is used only by the + midpoint insertion strategy. + + RETURN VALUE + 0 if a success, 1 - otherwise. + + NOTES + The function is used by MyISAM to move all blocks from a index file to + the key cache. + It is assumed that it may be performed in parallel with reading the file + data from the key buffers by other threads. +*/ + +int key_cache_insert(KEY_CACHE *keycache, + File file, my_off_t filepos, int level, + uchar *buff, uint length) +{ + if (keycache->can_be_used) + return keycache->interface_funcs->insert(keycache->keycache_cb, + file, filepos, level, + buff, length); + return 0; +} + + +/* + Write data from a buffer into a key cache + + SYNOPSIS + + key_cache_write() + keycache pointer to the key cache to write data to + file handler for the file to write data to + filepos position in the file to write data to + level determines the weight of the data + buff buffer with the data + length length of the buffer + dont_write if is 0 then all dirty pages involved in writing + should have been flushed from key cache + file_extra pointer to optional file attributes + + DESCRIPTION + The function operates over buffers of the key cache keycache. + In a general case the function writes data from a buffer into the key + cache. The buffer is specified with the parameters buff and length - + the pointer to the beginning of the buffer and its size respectively. + It's assumed the buffer contains the data to be written into 'file' + starting from the position filepos. + If the value of the parameter dont_write is FALSE then the function + also writes the data into file. + The parameter level is used to set one characteristic for the key buffers + filled with the data from buff. The characteristic is employed only by + the midpoint insertion strategy. + The parameter file_expra may point to additional file attributes used + for optimization or other purposes. + The function writes data from the buffer directly into file if the control + block of the key cache has not been initialized yet. + + RETURN VALUE + 0 if a success, 1 - otherwise. + + NOTES + This implementation may exploit the fact that the function is called only + when a thread has got an exclusive lock for the key file. +*/ + +int key_cache_write(KEY_CACHE *keycache, + File file, void *file_extra, + my_off_t filepos, int level, + uchar *buff, uint length, + uint block_length, int force_write) +{ + if (keycache->can_be_used) + return keycache->interface_funcs->write(keycache->keycache_cb, + file, file_extra, + filepos, level, + buff, length, + block_length, force_write); + + /* We can't use mutex here as the key cache may not be initialized */ + if (my_pwrite(file, buff, length, filepos, MYF(MY_NABP | MY_WAIT_IF_FULL))) + return 1; + + return 0; +} + + +/* + Flush all blocks for a file from key buffers of a key cache + + SYNOPSIS + + flush_key_blocks() + keycache pointer to the key cache whose blocks are to be flushed + file handler for the file to flush to + file_extra maps of key cache (used for partitioned key caches) + flush_type type of the flush operation + + DESCRIPTION + The function operates over buffers of the key cache keycache. + In a general case the function flushes the data from all dirty key + buffers related to the file 'file' into this file. The function does + exactly this if the value of the parameter type is FLUSH_KEEP. If the + value of this parameter is FLUSH_RELEASE, the function additionally + releases the key buffers containing data from 'file' for new usage. + If the value of the parameter type is FLUSH_IGNORE_CHANGED the function + just releases the key buffers containing data from 'file'. + If the value of the parameter type is FLUSH_KEEP the function may use + the value of the parameter file_extra pointing to possibly dirty + partitions to optimize the operation for partitioned key caches. + + RETURN + 0 ok + 1 error + + NOTES + Any implementation of the function may exploit the fact that the function + is called only when a thread has got an exclusive lock for the key file. +*/ + +int flush_key_blocks(KEY_CACHE *keycache, + int file, void *file_extra, + enum flush_type type) +{ + if (keycache->can_be_used) + return keycache->interface_funcs->flush(keycache->keycache_cb, + file, file_extra, type); + return 0; +} + + +/* + Reset the counters of a key cache + + SYNOPSIS + reset_key_cache_counters() + name the name of a key cache (unused) + keycache pointer to the key cache for which to reset counters + + DESCRIPTION + This function resets the values of the statistical counters for the key + cache keycache. + The parameter name is currently not used. + + RETURN + 0 on success (always because it can't fail) + + NOTES + This procedure is used by process_key_caches() to reset the counters of all + currently used key caches, both the default one and the named ones. +*/ + +int reset_key_cache_counters(const char *name __attribute__((unused)), + KEY_CACHE *keycache, + void *unused __attribute__((unused))) +{ + int rc= 0; + if (keycache->key_cache_inited) + { + pthread_mutex_lock(&keycache->op_lock); + rc= keycache->interface_funcs->reset_counters(name, + keycache->keycache_cb); + pthread_mutex_unlock(&keycache->op_lock); + } + return rc; +} + + +/* + Get statistics for a key cache + + SYNOPSIS + get_key_cache_statistics() + keycache pointer to the key cache to get statistics for + partition_no partition number to get statistics for + key_cache_stats OUT pointer to the structure for the returned statistics + + DESCRIPTION + If the value of the parameter partition_no is equal to 0 then statistics + for the whole key cache keycache (aggregated statistics) is returned in the + fields of the structure key_cache_stat of the type KEY_CACHE_STATISTICS. + Otherwise the value of the parameter partition_no makes sense only for + a partitioned key cache. In this case the function returns statistics + for the partition with the specified number partition_no. + + RETURN + none +*/ + +void get_key_cache_statistics(KEY_CACHE *keycache, uint partition_no, + KEY_CACHE_STATISTICS *key_cache_stats) +{ + if (keycache->key_cache_inited) + { + pthread_mutex_lock(&keycache->op_lock); + keycache->interface_funcs->get_stats(keycache->keycache_cb, + partition_no, key_cache_stats); + pthread_mutex_unlock(&keycache->op_lock); + } +} + + +/* + Repartition a key cache : internal + + SYNOPSIS + repartition_key_cache_internal() + keycache pointer to the key cache to be repartitioned + key_cache_block_size size of blocks to keep cached data + use_mem total memory to use for the new key cache + division_limit new division limit (if not zero) + age_threshold new age threshold (if not zero) + partitions new number of partitions in the key cache + use_op_lock if TRUE use keycache->op_lock, otherwise - ignore it + + DESCRIPTION + The function performs the actions required from repartition_key_cache(). + It has an additional parameter: use_op_lock. When the parameter + is TRUE then the function locks keycache->op_lock at start and + unlocks it before the return. Otherwise the actions with the lock + are omitted. + + RETURN VALUE + number of blocks in the key cache, if successful, + 0 - otherwise. +*/ + +static +int repartition_key_cache_internal(KEY_CACHE *keycache, + uint key_cache_block_size, size_t use_mem, + uint division_limit, uint age_threshold, + uint partitions, my_bool use_op_lock) +{ + uint blocks= -1; + if (keycache->key_cache_inited) + { + if (use_op_lock) + pthread_mutex_lock(&keycache->op_lock); + keycache->interface_funcs->resize(keycache->keycache_cb, + key_cache_block_size, 0, + division_limit, age_threshold); + end_key_cache_internal(keycache, 1, 0); + blocks= init_key_cache_internal(keycache, key_cache_block_size, use_mem, + division_limit, age_threshold, partitions, + 0); + if (use_op_lock) + pthread_mutex_unlock(&keycache->op_lock); + } + return blocks; +} + +/* + Repartition a key cache + + SYNOPSIS + repartition_key_cache() + keycache pointer to the key cache to be repartitioned + key_cache_block_size size of blocks to keep cached data + use_mem total memory to use for the new key cache + division_limit new division limit (if not zero) + age_threshold new age threshold (if not zero) + partitions new number of partitions in the key cache + + DESCRIPTION + The function operates over the key cache keycache. + The parameter partitions specifies the number of partitions in the key + cache after repartitioning. If the value of this parameter is 0 then + a simple key cache must be created instead of the old one. + The parameter key_cache_block_size specifies the new size of the block + buffers in the key cache. The parameters division_limit and age_threshold + determine the new initial values of those characteristics of the key cache + that are used for midpoint insertion strategy. The parameter use_mem + specifies the total amount of memory to be allocated for the new key + cache buffers and for all auxiliary structures. + The function calls repartition_key_cache_internal() to perform all these + actions with the last parameter set to TRUE. + + RETURN VALUE + number of blocks in the key cache, if successful, + 0 - otherwise. + + NOTES + Currently the function is called when the value of the variable + key_cache_partitions is being reset for the key cache keycache. +*/ + +int repartition_key_cache(KEY_CACHE *keycache, uint key_cache_block_size, + size_t use_mem, uint division_limit, + uint age_threshold, uint partitions) +{ + return repartition_key_cache_internal(keycache, key_cache_block_size, use_mem, + division_limit, age_threshold, + partitions, 1); +} + diff --git a/mysys/mf_keycaches.c b/mysys/mf_keycaches.c index b49a46f2e78..7dbfc26a365 100644 --- a/mysys/mf_keycaches.c +++ b/mysys/mf_keycaches.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2003, 2011, Oracle and/or its affiliates. + Copyright (c) 2010, 2011, Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,267 +26,7 @@ #include <keycache.h> #include <hash.h> #include <m_string.h> - -/***************************************************************************** - General functions to handle SAFE_HASH objects. - - A SAFE_HASH object is used to store the hash, the mutex and default value - needed by the rest of the key cache code. - This is a separate struct to make it easy to later reuse the code for other - purposes - - All entries are linked in a list to allow us to traverse all elements - and delete selected ones. (HASH doesn't allow any easy ways to do this). -*****************************************************************************/ - -/* - Struct to store a key and pointer to object -*/ - -typedef struct st_safe_hash_entry -{ - uchar *key; - uint length; - uchar *data; - struct st_safe_hash_entry *next, **prev; -} SAFE_HASH_ENTRY; - - -typedef struct st_safe_hash_with_default -{ - rw_lock_t mutex; - HASH hash; - uchar *default_value; - SAFE_HASH_ENTRY *root; -} SAFE_HASH; - - -/* - Free a SAFE_HASH_ENTRY - - This function is called by the hash object on delete -*/ - -static void safe_hash_entry_free(SAFE_HASH_ENTRY *entry) -{ - DBUG_ENTER("free_assign_entry"); - my_free(entry); - DBUG_VOID_RETURN; -} - - -/* Get key and length for a SAFE_HASH_ENTRY */ - -static uchar *safe_hash_entry_get(SAFE_HASH_ENTRY *entry, size_t *length, - my_bool not_used __attribute__((unused))) -{ - *length=entry->length; - return (uchar*) entry->key; -} - - -/* - Init a SAFE_HASH object - - SYNOPSIS - safe_hash_init() - hash safe_hash handler - elements Expected max number of elements - default_value default value - - NOTES - In case of error we set hash->default_value to 0 to allow one to call - safe_hash_free on an object that couldn't be initialized. - - RETURN - 0 ok - 1 error -*/ - -static my_bool safe_hash_init(SAFE_HASH *hash, uint elements, - uchar *default_value) -{ - DBUG_ENTER("safe_hash"); - if (my_hash_init(&hash->hash, &my_charset_bin, elements, - 0, 0, (my_hash_get_key) safe_hash_entry_get, - (void (*)(void*)) safe_hash_entry_free, 0)) - { - hash->default_value= 0; - DBUG_RETURN(1); - } - my_rwlock_init(&hash->mutex, 0); - hash->default_value= default_value; - hash->root= 0; - DBUG_RETURN(0); -} - - -/* - Free a SAFE_HASH object - - NOTES - This is safe to call on any object that has been sent to safe_hash_init() -*/ - -static void safe_hash_free(SAFE_HASH *hash) -{ - /* - Test if safe_hash_init succeeded. This will also guard us against multiple - free calls. - */ - if (hash->default_value) - { - my_hash_free(&hash->hash); - rwlock_destroy(&hash->mutex); - hash->default_value=0; - } -} - -/* - Return the value stored for a key or default value if no key -*/ - -static uchar *safe_hash_search(SAFE_HASH *hash, const uchar *key, uint length) -{ - uchar *result; - DBUG_ENTER("safe_hash_search"); - rw_rdlock(&hash->mutex); - result= my_hash_search(&hash->hash, key, length); - rw_unlock(&hash->mutex); - if (!result) - result= hash->default_value; - else - result= ((SAFE_HASH_ENTRY*) result)->data; - DBUG_PRINT("exit",("data: 0x%lx", (long) result)); - DBUG_RETURN(result); -} - - -/* - Associate a key with some data - - SYONOPSIS - safe_hash_set() - hash Hash handle - key key (path to table etc..) - length Length of key - data data to to associate with the data - - NOTES - This can be used both to insert a new entry and change an existing - entry. - If one associates a key with the default key cache, the key is deleted - - RETURN - 0 ok - 1 error (Can only be EOM). In this case my_message() is called. -*/ - -static my_bool safe_hash_set(SAFE_HASH *hash, const uchar *key, uint length, - uchar *data) -{ - SAFE_HASH_ENTRY *entry; - my_bool error= 0; - DBUG_ENTER("safe_hash_set"); - DBUG_PRINT("enter",("key: %.*s data: 0x%lx", length, key, (long) data)); - - rw_wrlock(&hash->mutex); - entry= (SAFE_HASH_ENTRY*) my_hash_search(&hash->hash, key, length); - - if (data == hash->default_value) - { - /* - The key is to be associated with the default entry. In this case - we can just delete the entry (if it existed) from the hash as a - search will return the default entry - */ - if (!entry) /* nothing to do */ - goto end; - /* unlink entry from list */ - if ((*entry->prev= entry->next)) - entry->next->prev= entry->prev; - my_hash_delete(&hash->hash, (uchar*) entry); - goto end; - } - if (entry) - { - /* Entry existed; Just change the pointer to point at the new data */ - entry->data= data; - } - else - { - if (!(entry= (SAFE_HASH_ENTRY *) my_malloc(sizeof(*entry) + length, - MYF(MY_WME)))) - { - error= 1; - goto end; - } - entry->key= (uchar*) (entry +1); - memcpy((char*) entry->key, (char*) key, length); - entry->length= length; - entry->data= data; - /* Link entry to list */ - if ((entry->next= hash->root)) - entry->next->prev= &entry->next; - entry->prev= &hash->root; - hash->root= entry; - if (my_hash_insert(&hash->hash, (uchar*) entry)) - { - /* This can only happen if hash got out of memory */ - my_free(entry); - error= 1; - goto end; - } - } - -end: - rw_unlock(&hash->mutex); - DBUG_RETURN(error); -} - - -/* - Change all entres with one data value to another data value - - SYONOPSIS - safe_hash_change() - hash Hash handle - old_data Old data - new_data Change all 'old_data' to this - - NOTES - We use the linked list to traverse all elements in the hash as - this allows us to delete elements in the case where 'new_data' is the - default value. -*/ - -static void safe_hash_change(SAFE_HASH *hash, uchar *old_data, uchar *new_data) -{ - SAFE_HASH_ENTRY *entry, *next; - DBUG_ENTER("safe_hash_set"); - - rw_wrlock(&hash->mutex); - - for (entry= hash->root ; entry ; entry= next) - { - next= entry->next; - if (entry->data == old_data) - { - if (new_data == hash->default_value) - { - if ((*entry->prev= entry->next)) - entry->next->prev= entry->prev; - my_hash_delete(&hash->hash, (uchar*) entry); - } - else - entry->data= new_data; - } - } - - rw_unlock(&hash->mutex); - DBUG_VOID_RETURN; -} - +#include "my_safehash.h" /***************************************************************************** Functions to handle the key cache objects @@ -313,6 +54,7 @@ void multi_keycache_free(void) multi_key_cache_search() key key to find (usually table path) uint length Length of key. + def Default value if no key cache NOTES This function is coded in such a way that we will return the @@ -323,11 +65,13 @@ void multi_keycache_free(void) key cache to use */ -KEY_CACHE *multi_key_cache_search(uchar *key, uint length) +KEY_CACHE *multi_key_cache_search(uchar *key, uint length, + KEY_CACHE *def) { if (!key_cache_hash.hash.records) - return dflt_key_cache; - return (KEY_CACHE*) safe_hash_search(&key_cache_hash, key, length); + return def; + return (KEY_CACHE*) safe_hash_search(&key_cache_hash, key, length, + (void*) def); } @@ -359,3 +103,5 @@ void multi_key_cache_change(KEY_CACHE *old_data, { safe_hash_change(&key_cache_hash, (uchar*) old_data, (uchar*) new_data); } + + diff --git a/mysys/mf_loadpath.c b/mysys/mf_loadpath.c index 776435e0e75..7bad535585a 100644 --- a/mysys/mf_loadpath.c +++ b/mysys/mf_loadpath.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2000, 2010, Oracle and/or its affiliates This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,7 +27,8 @@ char * my_load_path(char * to, const char *path, const char *own_path_prefix) { - char buff[FN_REFLEN]; + char buff[FN_REFLEN+1]; + const char *from= buff; int is_cur; DBUG_ENTER("my_load_path"); DBUG_PRINT("enter",("path: %s prefix: %s",path, @@ -34,7 +36,7 @@ char * my_load_path(char * to, const char *path, if ((path[0] == FN_HOMELIB && path[1] == FN_LIBCHAR) || test_if_hard_path(path)) - (void) strnmov(buff, path, FN_REFLEN); + from= path; else if ((is_cur=(path[0] == FN_CURLIB && path[1] == FN_LIBCHAR)) || (is_prefix(path,FN_PARENTDIR)) || ! own_path_prefix) @@ -42,14 +44,16 @@ char * my_load_path(char * to, const char *path, if (is_cur) is_cur=2; /* Remove current dir */ if (! my_getwd(buff,(uint) (FN_REFLEN-strlen(path)+is_cur),MYF(0))) - (void) strncat(buff, path+is_cur, FN_REFLEN-1); + { + size_t length= strlen(buff); + (void) strmake(buff + length, path+is_cur, FN_REFLEN - length); + } else - (void) strnmov(buff, path, FN_REFLEN); /* Return org file name */ + from= path; /* Return org file name */ } else (void) strxnmov(buff, FN_REFLEN, own_path_prefix, path, NullS); - strnmov(to, buff, FN_REFLEN); - to[FN_REFLEN-1]= '\0'; + strmake(to, from, FN_REFLEN-1); DBUG_PRINT("exit",("to: %s",to)); DBUG_RETURN(to); } /* my_load_path */ diff --git a/mysys/mf_pack.c b/mysys/mf_pack.c index 7f844db5b9d..e246ff17f22 100644 --- a/mysys/mf_pack.c +++ b/mysys/mf_pack.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. + Copyright (c) 2012, 2013, Monty Program Ab. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,7 +32,7 @@ void pack_dirname(char * to, const char *from) int cwd_err; size_t d_length,length,UNINIT_VAR(buff_length); char * start; - char buff[FN_REFLEN]; + char buff[FN_REFLEN + 1]; DBUG_ENTER("pack_dirname"); (void) intern_filename(to,from); /* Change to intern name */ @@ -128,7 +129,7 @@ size_t cleanup_dirname(register char *to, const char *from) reg3 char * from_ptr; reg4 char * start; char parent[5], /* for "FN_PARENTDIR" */ - buff[FN_REFLEN+1],*end_parentdir; + buff[FN_REFLEN + 1],*end_parentdir; #ifdef BACKSLASH_MBTAIL CHARSET_INFO *fs= fs_character_set(); #endif @@ -214,12 +215,6 @@ size_t cleanup_dirname(register char *to, const char *from) } else if (pos-start > 1 && pos[-1] == FN_CURLIB && pos[-2] == FN_LIBCHAR) pos-=2; /* Skip /./ */ - else if (pos > buff+1 && pos[-1] == FN_HOMELIB && pos[-2] == FN_LIBCHAR) - { /* Found ..../~/ */ - buff[0]=FN_HOMELIB; - buff[1]=FN_LIBCHAR; - start=buff; pos=buff+1; - } } } (void) strmov(to,buff); @@ -242,7 +237,7 @@ my_bool my_use_symdir=0; /* Set this if you want to use symdirs */ #ifdef USE_SYMDIR void symdirget(char *dir) { - char buff[FN_REFLEN+1]; + char buff[FN_REFLEN + 1]; char *pos=strend(dir); if (dir[0] && pos[-1] != FN_DEVCHAR && my_access(dir, F_OK)) { @@ -292,7 +287,7 @@ void symdirget(char *dir) size_t normalize_dirname(char *to, const char *from) { size_t length; - char buff[FN_REFLEN]; + char buff[FN_REFLEN + 1]; DBUG_ENTER("normalize_dirname"); /* @@ -419,7 +414,7 @@ static char * expand_tilde(char **path) size_t unpack_filename(char * to, const char *from) { size_t length, n_length, buff_length; - char buff[FN_REFLEN]; + char buff[FN_REFLEN + 1]; DBUG_ENTER("unpack_filename"); length=dirname_part(buff, from, &buff_length);/* copy & convert dirname */ @@ -449,7 +444,7 @@ size_t system_filename(char *to, const char *from) char *intern_filename(char *to, const char *from) { size_t length, to_length; - char buff[FN_REFLEN]; + char buff[FN_REFLEN + 1]; if (from == to) { /* Dirname may destroy from */ (void) strnmov(buff, from, FN_REFLEN); diff --git a/mysys/mf_qsort.c b/mysys/mf_qsort.c index 45a948feab8..3e91c0ac0b0 100644 --- a/mysys/mf_qsort.c +++ b/mysys/mf_qsort.c @@ -109,7 +109,7 @@ qsort_t my_qsort(void *base_ptr, size_t count, size_t size, qsort_cmp cmp) low = (char*) base_ptr; high = low+ size * (count - 1); stack_ptr = stack + 1; -#ifdef HAVE_purify +#ifdef HAVE_valgrind /* The first element in the stack will be accessed for the last POP */ stack[0].low=stack[0].high=0; #endif diff --git a/mysys/mf_soundex.c b/mysys/mf_soundex.c index 3452d52018d..2784d112805 100644 --- a/mysys/mf_soundex.c +++ b/mysys/mf_soundex.c @@ -48,7 +48,7 @@ void soundex(CHARSET_INFO * cs,register char * out_pntr, char * in_pntr, { char ch,last_ch; reg3 char * end; - register uchar *map=cs->to_upper; + register const uchar *map=cs->to_upper; if (remove_garbage) { diff --git a/mysys/my_access.c b/mysys/my_access.c index c5d4006a26c..22f145a75c9 100644 --- a/mysys/my_access.c +++ b/mysys/my_access.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2012, Oracle and/or its affiliates + Copyright (c) 2012, 2014, SkySQL Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,11 +27,6 @@ path Path to file amode Access method - DESCRIPTION - This function wraps the normal access method because the access - available in MSVCRT> +reports that filenames such as LPT1 and - COM1 are valid (they are but should not be so for us). - RETURN VALUES 0 ok -1 error (We use -1 as my_access is mapped to access on other platforms) @@ -38,12 +34,11 @@ int my_access(const char *path, int amode) { - WIN32_FILE_ATTRIBUTE_DATA fileinfo; - BOOL result; - - result= GetFileAttributesEx(path, GetFileExInfoStandard, &fileinfo); - if (! result || - (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY) && (amode & W_OK)) + DWORD attributes; + + attributes = GetFileAttributes(path); + if (attributes == INVALID_FILE_ATTRIBUTES || + (attributes & FILE_ATTRIBUTE_READONLY) && (amode & W_OK)) { my_errno= errno= EACCES; return -1; @@ -148,75 +143,13 @@ static char reserved_map[256]= int check_if_legal_tablename(const char *name) { DBUG_ENTER("check_if_legal_tablename"); - DBUG_RETURN(name[0] != 0 && name[1] != 0 && - (reserved_map[(uchar) name[0]] & 1) && + DBUG_RETURN((reserved_map[(uchar) name[0]] & 1) && (reserved_map[(uchar) name[1]] & 2) && (reserved_map[(uchar) name[2]] & 4) && str_list_find(&reserved_names[1], name)); } -#ifdef __WIN__ -/** - Checks if the drive letter supplied is valid or not. Valid drive - letters are A to Z, both lower case and upper case. - - @param drive_letter : The drive letter to validate. - - @return TRUE if the drive exists, FALSE otherwise. -*/ -static my_bool does_drive_exists(char drive_letter) -{ - DWORD drive_mask= GetLogicalDrives(); - drive_letter= toupper(drive_letter); - - return (drive_letter >= 'A' && drive_letter <= 'Z') && - (drive_mask & (0x1 << (drive_letter - 'A'))); -} -#endif - -/** - Verifies if the file name supplied is allowed or not. On Windows - file names with a colon (:) are not allowed because such file names - store data in Alternate Data Streams which can be used to hide - the data. - - @param name contains the file name with or without path - @param length contains the length of file name - - @return TRUE if the file name is allowed, FALSE otherwise. -*/ -my_bool is_filename_allowed(const char *name __attribute__((unused)), - size_t length __attribute__((unused))) -{ -#ifdef __WIN__ - /* - For Windows, check if the file name contains : character. - Start from end of path and search if the file name contains : - */ - const char* ch = NULL; - for(ch= name + length - 1; ch >= name; --ch) - { - if (FN_LIBCHAR == *ch || '/' == *ch) - break; - else if (':' == *ch) - { - /* - File names like C:foobar.txt are allowed since the syntax means - file foobar.txt in current directory of C drive. However file - names likes CC:foobar are not allowed since this syntax means ADS - foobar in file CC. - */ - return ((ch - name == 1) && does_drive_exists(*name)); - } - } - return TRUE; -#else - /* For other platforms, file names can contain colon : */ - return TRUE; -#endif -} /* is_filename_allowed */ - #if defined(__WIN__) || defined(__EMX__) @@ -238,9 +171,6 @@ int check_if_legal_filename(const char *path) const char **reserved_name; DBUG_ENTER("check_if_legal_filename"); - if(!is_filename_allowed(path, strlen(path))) - DBUG_RETURN(1); - path+= dirname_length(path); /* To start of filename */ if (!(end= strchr(path, FN_EXTCHAR))) end= strend(path); diff --git a/mysys/my_addr_resolve.c b/mysys/my_addr_resolve.c new file mode 100644 index 00000000000..90e6f43f390 --- /dev/null +++ b/mysys/my_addr_resolve.c @@ -0,0 +1,202 @@ +/* Copyright (C) 2011 Monty Program Ab + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "mysys_priv.h" +#include <m_string.h> +#include <my_sys.h> +#include <my_stacktrace.h> + +/** + strip the path, leave the file name and the last dirname +*/ +static const char *strip_path(const char *s) __attribute__((unused)); +static const char *strip_path(const char *s) +{ + const char *prev, *last; + for(prev= last= s; *s; s++) + if (*s == '/' || *s == '\\') + { + prev= last; + last= s + 1; + } + return prev; +} + +/* + The following is very much single-threaded code and it's only supposed + to be used on shutdown or for a crash report + Or the caller should take care and use mutexes. + + Also it does not free any its memory. For the same reason - + it's only used for crash reports or on shutdown when we already + have a memory leak. +*/ + +#ifdef HAVE_BFD_H +#include <bfd.h> +static bfd *bfdh= 0; +static asymbol **symtable= 0; + +/** + finds a file name, a line number, and a function name corresponding to addr. + + the function name is demangled. + the file name is stripped of its path, only the two last components are kept + the resolving logic is mostly based on addr2line of binutils-2.17 + + @return 0 on success, 1 on failure +*/ +int my_addr_resolve(void *ptr, my_addr_loc *loc) +{ + bfd_vma addr= (intptr)ptr; + asection *sec; + + for (sec= bfdh->sections; sec; sec= sec->next) + { + bfd_vma start; + + if ((bfd_get_section_flags(bfdh, sec) & SEC_ALLOC) == 0) + continue; + + start = bfd_get_section_vma(bfdh, sec); + if (addr < start || addr >= start + bfd_get_section_size(sec)) + continue; + + if (bfd_find_nearest_line(bfdh, sec, symtable, addr - start, + &loc->file, &loc->func, &loc->line)) + { + if (loc->file) + loc->file= strip_path(loc->file); + else + loc->file= ""; + + if (loc->func) + { + const char *str= bfd_demangle(bfdh, loc->func, 3); + if (str) + loc->func= str; + } + + return 0; + } + } + + return 1; +} + +const char *my_addr_resolve_init() +{ + if (!bfdh) + { + uint unused; + char **matching; + + bfdh= bfd_openr(my_progname, NULL); + if (!bfdh) + goto err; + + if (bfd_check_format(bfdh, bfd_archive)) + goto err; + if (!bfd_check_format_matches (bfdh, bfd_object, &matching)) + goto err; + + if (bfd_read_minisymbols(bfdh, FALSE, (void *)&symtable, &unused) < 0) + goto err; + } + return 0; + +err: + return bfd_errmsg(bfd_get_error()); +} +#elif defined(HAVE_LIBELF_H) +/* + another possible implementation. +*/ +#elif defined(MY_ADDR_RESOLVE_FORK) +/* + yet another - just execute addr2line or eu-addr2line, whatever available, + pipe the addresses to it, and parse the output +*/ + +#include <m_string.h> +#include <ctype.h> +static int in[2], out[2]; +static int initialized= 0; +static char output[1024]; +int my_addr_resolve(void *ptr, my_addr_loc *loc) +{ + char input[32], *s; + size_t len; + + len= my_snprintf(input, sizeof(input), "%p\n", ptr); + if (write(in[1], input, len) <= 0) + return 1; + if (read(out[0], output, sizeof(output)) <= 0) + return 1; + loc->func= s= output; + while (*s != '\n') + s++; + *s++= 0; + loc->file= s; + while (*s != ':') + s++; + *s++= 0; + + if (strcmp(loc->file, "??") == 0) + return 1; + + loc->line= 0; + while (isdigit(*s)) + loc->line = loc->line * 10 + (*s++ - '0'); + *s = 0; + loc->file= strip_path(loc->file); + + return 0; +} + +const char *my_addr_resolve_init() +{ + if (!initialized) + { + pid_t pid; + + if (pipe(in) < 0) + return "pipe(in)"; + if (pipe(out) < 0) + return "pipe(out)"; + + pid = fork(); + if (pid == -1) + return "fork"; + + if (!pid) /* child */ + { + dup2(in[0], 0); + dup2(out[1], 1); + close(in[0]); + close(in[1]); + close(out[0]); + close(out[1]); + execlp("addr2line", "addr2line", "-C", "-f", "-e", my_progname, NULL); + exit(1); + } + + close(in[0]); + close(out[1]); + initialized= 1; + } + return 0; +} +#endif diff --git a/mysys/my_alloc.c b/mysys/my_alloc.c index 65af5605206..1054db6cee4 100644 --- a/mysys/my_alloc.c +++ b/mysys/my_alloc.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2000, 2010, Oracle and/or its affiliates This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,7 +22,6 @@ #undef EXTRA_DEBUG #define EXTRA_DEBUG - /* Initialize memory root @@ -56,7 +56,7 @@ void init_alloc_root(MEM_ROOT *mem_root, size_t block_size, mem_root->block_num= 4; /* We shift this with >>2 */ mem_root->first_block_usage= 0; -#if !(defined(HAVE_purify) && defined(EXTRA_DEBUG)) +#if !(defined(HAVE_valgrind) && defined(EXTRA_DEBUG)) if (pre_alloc_size) { if ((mem_root->free= mem_root->pre_alloc= @@ -96,7 +96,7 @@ void reset_root_defaults(MEM_ROOT *mem_root, size_t block_size, DBUG_ASSERT(alloc_root_inited(mem_root)); mem_root->block_size= block_size - ALLOC_ROOT_MIN_BLOCK_SIZE; -#if !(defined(HAVE_purify) && defined(EXTRA_DEBUG)) +#if !(defined(HAVE_valgrind) && defined(EXTRA_DEBUG)) if (pre_alloc_size) { size_t size= pre_alloc_size + ALIGN_SIZE(sizeof(USED_MEM)); @@ -147,7 +147,7 @@ void reset_root_defaults(MEM_ROOT *mem_root, size_t block_size, void *alloc_root(MEM_ROOT *mem_root, size_t length) { -#if defined(HAVE_purify) && defined(EXTRA_DEBUG) +#if defined(HAVE_valgrind) && defined(EXTRA_DEBUG) reg1 USED_MEM *next; DBUG_ENTER("alloc_root"); DBUG_PRINT("enter",("root: 0x%lx", (long) mem_root)); @@ -236,6 +236,7 @@ void *alloc_root(MEM_ROOT *mem_root, size_t length) mem_root->used= next; mem_root->first_block_usage= 0; } + TRASH_ALLOC(point, length); DBUG_PRINT("exit",("ptr: 0x%lx", (ulong) point)); DBUG_RETURN((void*) point); #endif diff --git a/mysys/my_basename.c b/mysys/my_basename.c new file mode 100644 index 00000000000..9d1eaf53efa --- /dev/null +++ b/mysys/my_basename.c @@ -0,0 +1,41 @@ +/* Copyright (C) 2011 Monty Program Ab + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include <my_sys.h> +/** + @brief retrieve last component of the filename. + Loosely based on Single Unix Spec definition. + + @fn my_basename() + @param filename Filename +*/ +const char *my_basename(const char *filename) +{ + const char *last; + const char *s=filename; + + /* Handle basename()'s special cases, as per single unix spec */ + if (!filename || !filename[0]) + return "."; + if(filename[0] == '/' && filename[1]== '\0') + return filename; + + for(last= s; *s; s++) + { + if (*s == '/' || *s == '\\') + last= s + 1; + } + return last; +} diff --git a/mysys/my_bitmap.c b/mysys/my_bitmap.c index 879060fd49b..851fe2b1026 100644 --- a/mysys/my_bitmap.c +++ b/mysys/my_bitmap.c @@ -1,5 +1,6 @@ /* - Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2001, 2011, Oracle and/or its affiliates. + Copyright (C) 2009- 2011 Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,14 +23,13 @@ * the internal size is a set of 32 bit words * the number of bits specified in creation can be any number > 0 - * there are THREAD safe versions of most calls called bitmap_lock_* TODO: - Make assembler THREAD safe versions of these using test-and-set instructions + Make assembler thread safe versions of these using test-and-set instructions Original version created by Sergei Golubchik 2001 - 2004. New version written and test program added and some changes to the interface - was made by Mikael Ronström 2005, with assistance of Tomas Ulin and Mats + was made by Mikael Ronstrom 2005, with assistance of Tomas Ulin and Mats Kindahl. */ @@ -38,16 +38,31 @@ #include <m_string.h> #include <my_bit.h> -void create_last_word_mask(MY_BITMAP *map) + +/* Create a mask of the significant bits for the last byte (1,3,7,..255) */ + +static inline uchar last_byte_mask(uint bits) { - /* Get the number of used bits (1..8) in the last byte */ - unsigned int const used= 1U + ((map->n_bits-1U) & 0x7U); + /* Get the number of used bits-1 (0..7) in the last byte */ + unsigned int const used= (bits - 1U) & 7U; + /* Return bitmask for the significant bits */ + return ((2U << used) - 1); +} - /* - Create a mask with the upper 'unused' bits set and the lower 'used' - bits clear. The bits within each byte is stored in big-endian order. - */ - unsigned char const mask= (~((1 << used) - 1)) & 255; +/* + Create a mask with the upper 'unused' bits set and the lower 'used' + bits clear. The bits within each byte is stored in big-endian order. +*/ + +static inline uchar invers_last_byte_mask(uint bits) +{ + return last_byte_mask(bits) ^ 255; +} + + +void create_last_word_mask(MY_BITMAP *map) +{ + unsigned char const mask= invers_last_byte_mask(map->n_bits); /* The first bytes are to be set to zero since they represent real bits @@ -81,57 +96,54 @@ void create_last_word_mask(MY_BITMAP *map) } -static inline void bitmap_lock(MY_BITMAP *map __attribute__((unused))) +static inline my_bitmap_map last_word_mask(uint bit) { - if (map->mutex) - mysql_mutex_lock(map->mutex); -} + my_bitmap_map last_word_mask; + uint n_bits= bit + 1; + unsigned char const mask= invers_last_byte_mask(n_bits); + /* + The first bytes are to be set to zero since they represent real bits + in the bitvector. The last bytes are set to 0xFF since they represent + bytes not used by the bitvector. Finally the last byte contains bits + as set by the mask above. + */ + unsigned char *ptr= (unsigned char*)&last_word_mask; -static inline void bitmap_unlock(MY_BITMAP *map __attribute__((unused))) -{ - if (map->mutex) - mysql_mutex_unlock(map->mutex); + switch ((n_bits + 7)/8 & 3) { + case 1: + last_word_mask= ~0U; + ptr[0]= mask; + break; + case 2: + last_word_mask= ~0U; + ptr[0]= 0; + ptr[1]= mask; + break; + case 3: + last_word_mask= 0U; + ptr[2]= mask; + ptr[3]= 0xFFU; + break; + case 0: + last_word_mask= 0U; + ptr[3]= mask; + break; + } + return last_word_mask; } -static inline uint get_first_set(uint32 value, uint word_pos) +static inline void bitmap_lock(MY_BITMAP *map __attribute__((unused))) { - uchar *byte_ptr= (uchar*)&value; - uchar byte_value; - uint byte_pos, bit_pos; - - for (byte_pos=0; byte_pos < 4; byte_pos++, byte_ptr++) - { - byte_value= *byte_ptr; - if (byte_value) - { - for (bit_pos=0; ; bit_pos++) - if (byte_value & (1 << bit_pos)) - return (word_pos*32) + (byte_pos*8) + bit_pos; - } - } - return MY_BIT_NONE; + if (map->mutex) + mysql_mutex_lock(map->mutex); } - -static inline uint get_first_not_set(uint32 value, uint word_pos) +static inline void bitmap_unlock(MY_BITMAP *map __attribute__((unused))) { - uchar *byte_ptr= (uchar*)&value; - uchar byte_value; - uint byte_pos, bit_pos; - - for (byte_pos=0; byte_pos < 4; byte_pos++, byte_ptr++) - { - byte_value= *byte_ptr; - if (byte_value != 0xFF) - { - for (bit_pos=0; ; bit_pos++) - if (!(byte_value & (1 << bit_pos))) - return (word_pos*32) + (byte_pos*8) + bit_pos; - } - } - return MY_BIT_NONE; + if (map->mutex) + mysql_mutex_unlock(map->mutex); } @@ -143,31 +155,25 @@ my_bool bitmap_init(MY_BITMAP *map, my_bitmap_map *buf, uint n_bits, { uint size_in_bytes= bitmap_buffer_size(n_bits); uint extra= 0; - if (thread_safe) { size_in_bytes= ALIGN_SIZE(size_in_bytes); extra= sizeof(mysql_mutex_t); } map->mutex= 0; - if (!(buf= (my_bitmap_map*) my_malloc(size_in_bytes+extra, MYF(MY_WME)))) DBUG_RETURN(1); - if (thread_safe) { map->mutex= (mysql_mutex_t *) ((char*) buf + size_in_bytes); mysql_mutex_init(key_BITMAP_mutex, map->mutex, MY_MUTEX_INIT_FAST); } - } - else { DBUG_ASSERT(thread_safe == 0); } - map->bitmap= buf; map->n_bits= n_bits; create_last_word_mask(map); @@ -183,7 +189,6 @@ void bitmap_free(MY_BITMAP *map) { if (map->mutex) mysql_mutex_destroy(map->mutex); - my_free(map->bitmap); map->bitmap=0; } @@ -293,7 +298,10 @@ void bitmap_set_prefix(MY_BITMAP *map, uint prefix_size) memset(m, 0xff, prefix_bytes); m+= prefix_bytes; if ((prefix_bits= prefix_size & 7)) - *(m++)= (1 << prefix_bits)-1; + { + *m++= (1 << prefix_bits)-1; + prefix_bytes++; + } if ((d= no_bytes_in_map(map)-prefix_bytes)) bzero(m, d); } @@ -301,43 +309,31 @@ void bitmap_set_prefix(MY_BITMAP *map, uint prefix_size) my_bool bitmap_is_prefix(const MY_BITMAP *map, uint prefix_size) { - uint prefix_bits= prefix_size % 32; - my_bitmap_map *word_ptr= map->bitmap, last_word; - my_bitmap_map *end_prefix= word_ptr + prefix_size / 32; - DBUG_ASSERT(word_ptr && prefix_size <= map->n_bits); - - /* 1: Words that should be filled with 1 */ - for (; word_ptr < end_prefix; word_ptr++) - if (*word_ptr != 0xFFFFFFFF) - return FALSE; - - last_word= *map->last_word_ptr & ~map->last_word_mask; - - /* 2: Word which contains the end of the prefix (if any) */ - if (prefix_bits) - { - if (word_ptr == map->last_word_ptr) - return uint4korr((uchar*)&last_word) == (uint32)((1 << prefix_bits) - 1); - else if (uint4korr((uchar*)word_ptr) != (uint32)((1 << prefix_bits) - 1)) - return FALSE; - word_ptr++; - } - - /* 3: Words that should be filled with 0 */ - for (; word_ptr < map->last_word_ptr; word_ptr++) - if (*word_ptr != 0) - return FALSE; - - /* - We can end up here in two situations: - 1) We went through the whole bitmap in step 1. This will happen if the - whole bitmap is filled with 1 and prefix_size is a multiple of 32 - (i.e. the prefix does not end in the middle of a word). - In this case word_ptr will be larger than map->last_word_ptr. - 2) We have gone through steps 1-3 and just need to check that also - the last word is 0. - */ - return word_ptr > map->last_word_ptr || last_word == 0; + uint prefix_mask= last_byte_mask(prefix_size); + uchar *m= (uchar*) map->bitmap; + uchar *end_prefix= m+(prefix_size-1)/8; + uchar *end; + DBUG_ASSERT(m && prefix_size <= map->n_bits); + + /* Empty prefix is always true */ + if (!prefix_size) + return 1; + + while (m < end_prefix) + if (*m++ != 0xff) + return 0; + + end= ((uchar*) map->bitmap) + no_bytes_in_map(map) - 1; + if (m == end) + return ((*m & last_byte_mask(map->n_bits)) == prefix_mask); + + if (*m != prefix_mask) + return 0; + + while (++m < end) + if (*m != 0) + return 0; + return ((*m & last_byte_mask(map->n_bits)) == 0); } @@ -345,13 +341,10 @@ my_bool bitmap_is_set_all(const MY_BITMAP *map) { my_bitmap_map *data_ptr= map->bitmap; my_bitmap_map *end= map->last_word_ptr; - for (; data_ptr < end; data_ptr++) if (*data_ptr != 0xFFFFFFFF) return FALSE; - if ((*map->last_word_ptr | map->last_word_mask) != 0xFFFFFFFF) - return FALSE; - return TRUE; + return (*data_ptr | map->last_word_mask) == 0xFFFFFFFF; } @@ -363,9 +356,7 @@ my_bool bitmap_is_clear_all(const MY_BITMAP *map) for (; data_ptr < end; data_ptr++) if (*data_ptr) return FALSE; - if (*map->last_word_ptr & ~map->last_word_mask) - return FALSE; - return TRUE; + return (*data_ptr & ~map->last_word_mask) == 0; } /* Return TRUE if map1 is a subset of map2 */ @@ -378,14 +369,13 @@ my_bool bitmap_is_subset(const MY_BITMAP *map1, const MY_BITMAP *map2) map1->n_bits==map2->n_bits); end= map1->last_word_ptr; - for (; m1 < end; m1++, m2++) - if (*m1 & ~(*m2)) - return FALSE; - - if ((*map1->last_word_ptr & ~map1->last_word_mask) & - ~(*map2->last_word_ptr & ~map2->last_word_mask)) - return FALSE; - return TRUE; + while (m1 < end) + { + if ((*m1++) & ~(*m2++)) + return 0; + } + /* here both maps have the same number of bits - see assert above */ + return ((*m1 & ~*m2 & ~map1->last_word_mask) ? 0 : 1); } /* True if bitmaps has any common bits */ @@ -398,14 +388,13 @@ my_bool bitmap_is_overlapping(const MY_BITMAP *map1, const MY_BITMAP *map2) map1->n_bits==map2->n_bits); end= map1->last_word_ptr; - for (; m1 < end; m1++, m2++) - if (*m1 & *m2) - return TRUE; - - if ((*map1->last_word_ptr & ~map1->last_word_mask) & - (*map2->last_word_ptr & ~map2->last_word_mask)) - return TRUE; - return FALSE; + while (m1 < end) + { + if ((*m1++) & (*m2++)) + return 1; + } + /* here both maps have the same number of bits - see assert above */ + return ((*m1 & *m2 & ~map1->last_word_mask) ? 1 : 0); } @@ -417,21 +406,88 @@ void bitmap_intersect(MY_BITMAP *map, const MY_BITMAP *map2) DBUG_ASSERT(map->bitmap && map2->bitmap); end= to+min(len,len2); - for (; to < end; to++, from++) - *to &= *from; + while (to < end) + *to++ &= *from++; + + if (len2 <= len) + { + to[-1]&= ~map2->last_word_mask; /* Clear last not relevant bits */ + end+= len-len2; + while (to < end) + *to++= 0; + } +} + - if (len >= len2) - map->bitmap[len2 - 1] &= ~map2->last_word_mask; +/* + Check if there is some bit index between start_bit and end_bit, such that + this is bit is set for all bitmaps in bitmap_list. + + SYNOPSIS + bitmap_exists_intersection() + bitmpap_array [in] a set of MY_BITMAPs + bitmap_count [in] number of elements in bitmpap_array + start_bit [in] beginning (inclusive) of the range of bits to search + end_bit [in] end (inclusive) of the range of bits to search, must be + no bigger than the bits of the shortest bitmap. + + NOTES + This function assumes that for at least one of the bitmaps in bitmap_array all + bits outside the range [start_bit, end_bit] are 0. As a result is not + necessary to take care of the bits outside the range [start_bit, end_bit]. + + RETURN + TRUE if an intersecion exists + FALSE no intersection +*/ + +my_bool bitmap_exists_intersection(const MY_BITMAP **bitmap_array, + uint bitmap_count, + uint start_bit, uint end_bit) +{ + uint i, j, start_idx, end_idx; + my_bitmap_map cur_res; - if (len2 < len) + DBUG_ASSERT(bitmap_count && end_bit >= start_bit); + for (j= 0; j < bitmap_count; j++) + DBUG_ASSERT(end_bit < bitmap_array[j]->n_bits); + + start_idx= start_bit/8/sizeof(my_bitmap_map); + end_idx= end_bit/8/sizeof(my_bitmap_map); + + for (i= start_idx; i < end_idx; i++) { - end+=len-len2; - for (; to < end; to++) - *to= 0; + cur_res= ~0; + for (j= 0; cur_res && j < bitmap_count; j++) + cur_res &= bitmap_array[j]->bitmap[i]; + if (cur_res) + return TRUE; } + cur_res= ~last_word_mask(end_bit); + for (j= 0; cur_res && j < bitmap_count; j++) + cur_res &= bitmap_array[j]->bitmap[end_idx]; + return cur_res != 0; +} + + +/* True if union of bitmaps have all bits set */ + +my_bool bitmap_union_is_set_all(const MY_BITMAP *map1, const MY_BITMAP *map2) +{ + my_bitmap_map *m1= map1->bitmap, *m2= map2->bitmap, *end; + + DBUG_ASSERT(map1->bitmap && map2->bitmap && + map1->n_bits==map2->n_bits); + end= map1->last_word_ptr; + while ( m1 < end) + if ((*m1++ | *m2++) != 0xFFFFFFFF) + return FALSE; + /* here both maps have the same number of bits - see assert above */ + return ((*m1 | *m2 | map1->last_word_mask) != 0xFFFFFFFF); } + /* Set/clear all bits above a bit. @@ -458,8 +514,8 @@ void bitmap_set_above(MY_BITMAP *map, uint from_byte, uint use_bit) uchar *to= (uchar *)map->bitmap + from_byte; uchar *end= (uchar *)map->bitmap + (map->n_bits+7)/8; - for (; to < end; to++) - *to= use_byte; + while (to < end) + *to++= use_byte; } @@ -468,45 +524,46 @@ void bitmap_subtract(MY_BITMAP *map, const MY_BITMAP *map2) my_bitmap_map *to= map->bitmap, *from= map2->bitmap, *end; DBUG_ASSERT(map->bitmap && map2->bitmap && map->n_bits==map2->n_bits); + end= map->last_word_ptr; - for (; to <= end; to++, from++) - *to &= ~(*from); + while (to <= end) + *to++ &= ~(*from++); } void bitmap_union(MY_BITMAP *map, const MY_BITMAP *map2) { my_bitmap_map *to= map->bitmap, *from= map2->bitmap, *end; + DBUG_ASSERT(map->bitmap && map2->bitmap && map->n_bits==map2->n_bits); end= map->last_word_ptr; - for (; to <= end; to++, from++) - *to |= *from; + while (to <= end) + *to++ |= *from++; } void bitmap_xor(MY_BITMAP *map, const MY_BITMAP *map2) { - my_bitmap_map *to= map->bitmap, *from= map2->bitmap, *end; + my_bitmap_map *to= map->bitmap, *from= map2->bitmap, *end= map->last_word_ptr; DBUG_ASSERT(map->bitmap && map2->bitmap && map->n_bits==map2->n_bits); - end= map->last_word_ptr; - - for (; to <= end; to++, from++) - *to ^= *from; + while (to <= end) + *to++ ^= *from++; } void bitmap_invert(MY_BITMAP *map) { my_bitmap_map *to= map->bitmap, *end; + DBUG_ASSERT(map->bitmap); end= map->last_word_ptr; - for (; to <= end; to++) - *to ^= 0xFFFFFFFF; + while (to <= end) + *to++ ^= 0xFFFFFFFF; } @@ -525,48 +582,87 @@ uint bitmap_bits_set(const MY_BITMAP *map) return res; } - void bitmap_copy(MY_BITMAP *map, const MY_BITMAP *map2) { my_bitmap_map *to= map->bitmap, *from= map2->bitmap, *end; + DBUG_ASSERT(map->bitmap && map2->bitmap && map->n_bits==map2->n_bits); end= map->last_word_ptr; - for (; to <= end; to++, from++) - *to = *from; + while (to <= end) + *to++ = *from++; } uint bitmap_get_first_set(const MY_BITMAP *map) { - uint word_pos; + uchar *byte_ptr; + uint i,j,k; my_bitmap_map *data_ptr, *end= map->last_word_ptr; DBUG_ASSERT(map->bitmap); data_ptr= map->bitmap; - for (word_pos=0; data_ptr < end; data_ptr++, word_pos++) + for (i=0; data_ptr < end; data_ptr++, i++) if (*data_ptr) - return get_first_set(*data_ptr, word_pos); + goto found; + if (!(*data_ptr & ~map->last_word_mask)) + return MY_BIT_NONE; - return get_first_set(*map->last_word_ptr & ~map->last_word_mask, word_pos); +found: + { + byte_ptr= (uchar*)data_ptr; + for (j=0; ; j++, byte_ptr++) + { + if (*byte_ptr) + { + for (k=0; ; k++) + { + if (*byte_ptr & (1 << k)) + return (i*32) + (j*8) + k; + } + } + } + } + DBUG_ASSERT(0); + return MY_BIT_NONE; /* Impossible */ } uint bitmap_get_first(const MY_BITMAP *map) { - uint word_pos; + uchar *byte_ptr; + uint i,j,k; my_bitmap_map *data_ptr, *end= map->last_word_ptr; DBUG_ASSERT(map->bitmap); data_ptr= map->bitmap; + *map->last_word_ptr|= map->last_word_mask; - for (word_pos=0; data_ptr < end; data_ptr++, word_pos++) + for (i=0; data_ptr < end; data_ptr++, i++) if (*data_ptr != 0xFFFFFFFF) - return get_first_not_set(*data_ptr, word_pos); + goto found; + if ((*data_ptr | map->last_word_mask) == 0xFFFFFFFF) + return MY_BIT_NONE; - return get_first_not_set(*map->last_word_ptr | map->last_word_mask, word_pos); +found: + { + byte_ptr= (uchar*)data_ptr; + for (j=0; ; j++, byte_ptr++) + { + if (*byte_ptr != 0xFF) + { + for (k=0; ; k++) + { + if (!(*byte_ptr & (1 << k))) + return (i*32) + (j*8) + k; + } + } + } + } + DBUG_ASSERT(0); + return MY_BIT_NONE; /* Impossible */ } @@ -587,3 +683,4 @@ void bitmap_lock_clear_bit(MY_BITMAP *map, uint bitmap_bit) bitmap_clear_bit(map, bitmap_bit); bitmap_unlock(map); } + diff --git a/mysys/my_chmod.c b/mysys/my_chmod.c new file mode 100644 index 00000000000..afdea758833 --- /dev/null +++ b/mysys/my_chmod.c @@ -0,0 +1,48 @@ +/* Copyright (C) 2000 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "mysys_priv.h" +#include "mysys_err.h" + +/** + @brief Change mode of file. + + @fn my_chmod() + @param name Filename + @param mode_t Mode + @param my_flags Flags + + @notes + The mode of the file given by path or referenced by fildes is changed + + @retval 0 Ok + @retval # Error +*/ + +int my_chmod(const char *name, mode_t mode, myf my_flags) +{ + DBUG_ENTER("my_chmod"); + DBUG_PRINT("my",("name: %s mode: %lu flags: %d", name, (ulong) mode, + my_flags)); + + if (chmod(name, mode)) + { + my_errno= errno; + if (my_flags & MY_WME) + my_error(EE_CANT_CHMOD, MYF(0), name, (ulong) mode, my_errno); + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} diff --git a/mysys/my_chsize.c b/mysys/my_chsize.c index 97ecd881af1..63964916d6f 100644 --- a/mysys/my_chsize.c +++ b/mysys/my_chsize.c @@ -67,13 +67,6 @@ int my_chsize(File fd, my_off_t newlength, int filler, myf MyFlags) goto err; } DBUG_RETURN(0); -#elif defined(HAVE_CHSIZE) - if (chsize(fd, (off_t) newlength)) - { - my_errno=errno; - goto err; - } - DBUG_RETURN(0); #else /* Fill space between requested length and true length with 'filler' diff --git a/mysys/my_compare.c b/mysys/my_compare.c index c9e9c4e98bf..82b30ab3ed3 100644 --- a/mysys/my_compare.c +++ b/mysys/my_compare.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2011, Oracle and/or its affiliates. + Copyright (C) 2009-2011 Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,10 +20,8 @@ #include <my_compare.h> #include <my_sys.h> -#define CMP_NUM(a,b) (((a) < (b)) ? -1 : ((a) == (b)) ? 0 : 1) - -int ha_compare_text(CHARSET_INFO *charset_info, uchar *a, uint a_length, - uchar *b, uint b_length, my_bool part_key, +int ha_compare_text(CHARSET_INFO *charset_info, const uchar *a, uint a_length, + const uchar *b, uint b_length, my_bool part_key, my_bool skip_end_space) { if (!part_key) @@ -33,11 +32,12 @@ int ha_compare_text(CHARSET_INFO *charset_info, uchar *a, uint a_length, } -static int compare_bin(uchar *a, uint a_length, uchar *b, uint b_length, +static int compare_bin(const uchar *a, uint a_length, + const uchar *b, uint b_length, my_bool part_key, my_bool skip_end_space) { uint length= min(a_length,b_length); - uchar *end= a+ length; + const uchar *end= a+ length; int flag; while (a < end) @@ -81,13 +81,15 @@ static int compare_bin(uchar *a, uint a_length, uchar *b, uint b_length, ha_key_cmp() keyseg Array of key segments of key to compare a First key to compare, in format from _mi_pack_key() - This is normally key specified by user - b Second key to compare. This is always from a row - key_length Length of key to compare. This can be shorter than - a to just compare sub keys + This is always from the row + b Second key to compare. This is from the row or the user + key_length Length of key to compare, based on key b. This can be shorter + than b to just compare sub keys next_flag How keys should be compared If bit SEARCH_FIND is not set the keys includes the row position and this should also be compared + If SEARCH_PAGE_KEY_HAS_TRANSID is set then 'a' has transid + If SEARCH_USER_KEY_HAS_TRANSID is set then 'b' has transid diff_pos OUT Number of first keypart where values differ, counting from one. diff_pos[1] OUT (b + diff_pos[1]) points to first value in tuple b @@ -116,8 +118,8 @@ static int compare_bin(uchar *a, uint a_length, uchar *b, uint b_length, #define FCMP(A,B) ((int) (A) - (int) (B)) -int ha_key_cmp(register HA_KEYSEG *keyseg, register uchar *a, - register uchar *b, uint key_length, uint nextflag, +int ha_key_cmp(HA_KEYSEG *keyseg, const uchar *a, + const uchar *b, uint key_length, uint32 nextflag, uint *diff_pos) { int flag; @@ -127,12 +129,12 @@ int ha_key_cmp(register HA_KEYSEG *keyseg, register uchar *a, float f_1,f_2; double d_1,d_2; uint next_key_length; - uchar *orig_b= b; + const uchar *orig_b= b; *diff_pos=0; for ( ; (int) key_length >0 ; key_length=next_key_length, keyseg++) { - uchar *end; + const uchar *end; uint piks=! (keyseg->flag & HA_NO_SORT); (*diff_pos)++; diff_pos[1]= (uint)(b - orig_b); @@ -149,8 +151,13 @@ int ha_key_cmp(register HA_KEYSEG *keyseg, register uchar *a, b++; if (!*a++) /* If key was NULL */ { - if (nextflag == (SEARCH_FIND | SEARCH_UPDATE)) - nextflag=SEARCH_SAME; /* Allow duplicate keys */ + if ((nextflag & (SEARCH_FIND | SEARCH_UPDATE | SEARCH_INSERT | + SEARCH_NULL_ARE_EQUAL)) == + (SEARCH_FIND | SEARCH_UPDATE | SEARCH_INSERT)) + { + /* Allow duplicate keys */ + nextflag= (nextflag & ~(SEARCH_FIND | SEARCH_UPDATE)) | SEARCH_SAME; + } else if (nextflag & SEARCH_NULL_ARE_NOT_EQUAL) { /* @@ -363,7 +370,7 @@ int ha_key_cmp(register HA_KEYSEG *keyseg, register uchar *a, if (keyseg->flag & HA_REVERSE_SORT) { - swap_variables(uchar*, a, b); + swap_variables(const uchar*, a, b); swap_flag=1; /* Remember swap of a & b */ end= a+ (int) (end-b); } @@ -388,7 +395,7 @@ int ha_key_cmp(register HA_KEYSEG *keyseg, register uchar *a, if (*b != '-') return -1; a++; b++; - swap_variables(uchar*, a, b); + swap_variables(const uchar*, a, b); swap_variables(int, alength, blength); swap_flag=1-swap_flag; alength--; blength--; @@ -417,7 +424,7 @@ int ha_key_cmp(register HA_KEYSEG *keyseg, register uchar *a, } if (swap_flag) /* Restore pointers */ - swap_variables(uchar*, a, b); + swap_variables(const uchar*, a, b); break; } #ifdef HAVE_LONG_LONG @@ -452,18 +459,90 @@ int ha_key_cmp(register HA_KEYSEG *keyseg, register uchar *a, end: if (!(nextflag & SEARCH_FIND)) { + /* + Compare rowid and possible transid + This happens in the following case: + - INSERT, UPDATE, DELETE when we have not unique keys or + are using versioning + - SEARCH_NEXT, SEARCH_PREVIOUS when we need to restart search + + The logic for comparing transid are as follows: + Keys with have a transid have lowest bit in the rowidt. This means that + if we are comparing a key with a transid with another key that doesn't + have a tranid, we must reset the lowest bit for both keys. + + When we have transid, the keys are compared in transid order. + A key without a transid is regared to be smaller than a key with + a transid. + */ + uint i; + uchar key_mask, tmp_a, tmp_b; + if (nextflag & (SEARCH_NO_FIND | SEARCH_LAST)) /* Find record after key */ return (nextflag & (SEARCH_BIGGER | SEARCH_LAST)) ? -1 : 1; - flag=0; - for (i=keyseg->length ; i-- > 0 ; ) + key_mask= (uchar) 255; + + if (!(nextflag & (SEARCH_USER_KEY_HAS_TRANSID | + SEARCH_PAGE_KEY_HAS_TRANSID))) + { + /* + Neither key has a trid. Only compare row id's and don't + try to store rows in trid order + */ + key_length= keyseg->length; + nextflag&= ~SEARCH_INSERT; + } + else + { + /* + Set key_mask so that we reset the last bit in the rowid before + we compare it. This is needed as the lowest bit in the rowid is + used to mark if the key has a transid or not. + */ + key_mask= (uchar) 254; + if (!test_all_bits(nextflag, (SEARCH_USER_KEY_HAS_TRANSID | + SEARCH_PAGE_KEY_HAS_TRANSID))) + { + /* + No transaction id for user key or for key on page + Ignore transid as at least one of the keys are visible for all + */ + key_length= keyseg->length; + } + else + { + /* + Both keys have trids. No need of special handling of incomplete + trids below. + */ + nextflag&= ~SEARCH_INSERT; + } + } + DBUG_ASSERT(key_length > 0); + + for (i= key_length-1 ; (int) i-- > 0 ; ) { if (*a++ != *b++) { flag= FCMP(a[-1],b[-1]); - break; + goto found; } } + tmp_a= *a & key_mask; + tmp_b= *b & key_mask; + flag= FCMP(tmp_a, tmp_b); + + if (flag == 0 && (nextflag & SEARCH_INSERT)) + { + /* + Ensure that on insert we get rows stored in trid order. + If one of the parts doesn't have a trid, this should be regarded + as smaller than the other + */ + return (nextflag & SEARCH_USER_KEY_HAS_TRANSID) ? -1 : 1; + } +found: if (nextflag & SEARCH_SAME) return (flag); /* read same */ if (nextflag & SEARCH_BIGGER) @@ -473,4 +552,92 @@ end: return 0; } /* ha_key_cmp */ +/* + Find the first NULL value in index-suffix values tuple + + SYNOPSIS + ha_find_null() + keyseg Array of keyparts for key suffix + a Key suffix value tuple + + DESCRIPTION + Find the first NULL value in index-suffix values tuple. + + TODO + Consider optimizing this function or its use so we don't search for + NULL values in completely NOT NULL index suffixes. + + RETURN + First key part that has NULL as value in values tuple, or the last key + part (with keyseg->type==HA_TYPE_END) if values tuple doesn't contain + NULLs. +*/ + +HA_KEYSEG *ha_find_null(HA_KEYSEG *keyseg, const uchar *a) +{ + for (; (enum ha_base_keytype) keyseg->type != HA_KEYTYPE_END; keyseg++) + { + const uchar *end; + if (keyseg->null_bit) + { + if (!*a++) + return keyseg; + } + end= a+ keyseg->length; + + switch ((enum ha_base_keytype) keyseg->type) { + case HA_KEYTYPE_TEXT: + case HA_KEYTYPE_BINARY: + case HA_KEYTYPE_BIT: + if (keyseg->flag & HA_SPACE_PACK) + { + int a_length; + get_key_length(a_length, a); + a += a_length; + break; + } + else + a= end; + break; + case HA_KEYTYPE_VARTEXT1: + case HA_KEYTYPE_VARTEXT2: + case HA_KEYTYPE_VARBINARY1: + case HA_KEYTYPE_VARBINARY2: + { + int a_length; + get_key_length(a_length, a); + a+= a_length; + break; + } + case HA_KEYTYPE_NUM: + if (keyseg->flag & HA_SPACE_PACK) + { + int alength= *a++; + end= a+alength; + } + a= end; + break; + case HA_KEYTYPE_INT8: + case HA_KEYTYPE_SHORT_INT: + case HA_KEYTYPE_USHORT_INT: + case HA_KEYTYPE_LONG_INT: + case HA_KEYTYPE_ULONG_INT: + case HA_KEYTYPE_INT24: + case HA_KEYTYPE_UINT24: +#ifdef HAVE_LONG_LONG + case HA_KEYTYPE_LONGLONG: + case HA_KEYTYPE_ULONGLONG: +#endif + case HA_KEYTYPE_FLOAT: + case HA_KEYTYPE_DOUBLE: + a= end; + break; + case HA_KEYTYPE_END: /* purecov: inspected */ + /* keep compiler happy */ + DBUG_ASSERT(0); + break; + } + } + return keyseg; +} diff --git a/mysys/my_compress.c b/mysys/my_compress.c index 84200051952..ea56900db05 100644 --- a/mysys/my_compress.c +++ b/mysys/my_compress.c @@ -57,19 +57,86 @@ my_bool my_compress(uchar *packet, size_t *len, size_t *complen) } +/* + Valgrind normally gives false alarms for zlib operations, in the form of + "conditional jump depends on uninitialised values" etc. The reason is + explained in the zlib FAQ (http://www.zlib.net/zlib_faq.html#faq36): + + "That is intentional for performance reasons, and the output of deflate + is not affected." + + Also discussed on a blog + (http://www.sirena.org.uk/log/2006/02/19/zlib-generating-valgrind-warnings/): + + "...loop unrolling in the zlib library causes the mentioned + “Conditional jump or move depends on uninitialised value(s)” + warnings. These are safe since the results of the comparison are + subsequently ignored..." + + "the results of the calculations are discarded by bounds checking done + after the loop exits" + + Fix by initializing the memory allocated by zlib when running under Valgrind. + + This fix is safe, since such memory is only used internally by zlib, so we + will not hide any bugs in mysql this way. +*/ +void *my_az_allocator(void *dummy __attribute__((unused)), unsigned int items, + unsigned int size) +{ + return my_malloc((size_t)items*(size_t)size, IF_VALGRIND(MY_ZEROFILL, MYF(0))); +} + +void my_az_free(void *dummy __attribute__((unused)), void *address) +{ + my_free(address); +} + +/* + This works like zlib compress(), but using custom memory allocators to work + better with my_malloc leak detection and Valgrind. +*/ +int my_compress_buffer(uchar *dest, size_t *destLen, + const uchar *source, size_t sourceLen) +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + stream.next_out = (Bytef*)dest; + stream.avail_out = (uInt)*destLen; + if ((size_t)stream.avail_out != *destLen) + return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)my_az_allocator; + stream.zfree = (free_func)my_az_free; + stream.opaque = (voidpf)0; + + err = deflateInit(&stream, Z_DEFAULT_COMPRESSION); + if (err != Z_OK) return err; + + err = deflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + deflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = deflateEnd(&stream); + return err; +} + uchar *my_compress_alloc(const uchar *packet, size_t *len, size_t *complen) { uchar *compbuf; - uLongf tmp_complen; int res; *complen= *len * 120 / 100 + 12; if (!(compbuf= (uchar *) my_malloc(*complen, MYF(MY_WME)))) return 0; /* Not enough memory */ - tmp_complen= (uint) *complen; - res= compress((Bytef*) compbuf, &tmp_complen, (Bytef*) packet, (uLong) *len); - *complen= tmp_complen; + res= my_compress_buffer(compbuf, complen, packet, *len); if (res != Z_OK) { @@ -118,7 +185,7 @@ my_bool my_uncompress(uchar *packet, size_t len, size_t *complen) if (!compbuf) DBUG_RETURN(1); /* Not enough memory */ - tmp_complen= (uint) *complen; + tmp_complen= (uLongf) *complen; error= uncompress((Bytef*) compbuf, &tmp_complen, (Bytef*) packet, (uLong) len); *complen= tmp_complen; diff --git a/mysys/my_context.c b/mysys/my_context.c new file mode 100644 index 00000000000..9be5ab80468 --- /dev/null +++ b/mysys/my_context.c @@ -0,0 +1,762 @@ +/* + Copyright 2011 Kristian Nielsen and Monty Program Ab + + This file is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + Implementation of async context spawning using Posix ucontext and + swapcontext(). +*/ + +#include "mysys_priv.h" +#include "m_string.h" +#include "my_context.h" + +#ifdef HAVE_VALGRIND +#include <valgrind/valgrind.h> +#endif + +#ifdef MY_CONTEXT_USE_UCONTEXT +/* + The makecontext() only allows to pass integers into the created context :-( + We want to pass pointers, so we do it this kinda hackish way. + Anyway, it should work everywhere, and at least it does not break strict + aliasing. +*/ +union pass_void_ptr_as_2_int { + int a[2]; + void *p; +}; + + +/* + We use old-style function definition here, as this is passed to + makecontext(). And the type of the makecontext() argument does not match + the actual type (as the actual type can differ from call to call). +*/ +static void +my_context_spawn_internal(i0, i1) +int i0, i1; +{ + int err; + struct my_context *c; + union pass_void_ptr_as_2_int u; + + u.a[0]= i0; + u.a[1]= i1; + c= (struct my_context *)u.p; + + (*c->user_func)(c->user_data); + c->active= 0; + err= setcontext(&c->base_context); + fprintf(stderr, "Aieie, setcontext() failed: %d (errno=%d)\n", err, errno); +} + + +int +my_context_continue(struct my_context *c) +{ + int err; + + if (!c->active) + return 0; + + DBUG_SWAP_CODE_STATE(&c->dbug_state); + err= swapcontext(&c->base_context, &c->spawned_context); + DBUG_SWAP_CODE_STATE(&c->dbug_state); + if (err) + { + fprintf(stderr, "Aieie, swapcontext() failed: %d (errno=%d)\n", + err, errno); + return -1; + } + + return c->active; +} + + +int +my_context_spawn(struct my_context *c, void (*f)(void *), void *d) +{ + int err; + union pass_void_ptr_as_2_int u; + + err= getcontext(&c->spawned_context); + if (err) + return -1; + c->spawned_context.uc_stack.ss_sp= c->stack; + c->spawned_context.uc_stack.ss_size= c->stack_size; + c->spawned_context.uc_link= NULL; + c->user_func= f; + c->user_data= d; + c->active= 1; + u.p= c; + makecontext(&c->spawned_context, my_context_spawn_internal, 2, + u.a[0], u.a[1]); + + return my_context_continue(c); +} + + +int +my_context_yield(struct my_context *c) +{ + int err; + + if (!c->active) + return -1; + + err= swapcontext(&c->spawned_context, &c->base_context); + if (err) + return -1; + return 0; +} + +int +my_context_init(struct my_context *c, size_t stack_size) +{ +#if SIZEOF_CHARP > SIZEOF_INT*2 +#error Error: Unable to store pointer in 2 ints on this architecture +#endif + bzero(c, sizeof(*c)); + if (!(c->stack= malloc(stack_size))) + return -1; /* Out of memory */ + c->stack_size= stack_size; +#ifdef HAVE_VALGRIND + c->valgrind_stack_id= + VALGRIND_STACK_REGISTER(c->stack, ((unsigned char *)(c->stack))+stack_size); +#endif + return 0; +} + +void +my_context_destroy(struct my_context *c) +{ + if (c->stack) + { +#ifdef HAVE_VALGRIND + VALGRIND_STACK_DEREGISTER(c->valgrind_stack_id); +#endif + free(c->stack); + } + DBUG_FREE_CODE_STATE(&c->dbug_state); +} + +#endif /* MY_CONTEXT_USE_UCONTEXT */ + + +#ifdef MY_CONTEXT_USE_X86_64_GCC_ASM +/* + GCC-amd64 implementation of my_context. + + This is slightly optimized in the common case where we never yield + (eg. fetch next row and it is already fully received in buffer). In this + case we do not need to restore registers at return (though we still need to + save them as we cannot know if we will yield or not in advance). +*/ + +#include <stdint.h> +#include <stdlib.h> + +/* + Layout of saved registers etc. + Since this is accessed through gcc inline assembler, it is simpler to just + use numbers than to try to define nice constants or structs. + + 0 0 %rsp + 1 8 %rbp + 2 16 %rbx + 3 24 %r12 + 4 32 %r13 + 5 40 %r14 + 6 48 %r15 + 7 56 %rip for done + 8 64 %rip for yield/continue +*/ + +int +my_context_spawn(struct my_context *c, void (*f)(void *), void *d) +{ + int ret; + + DBUG_SWAP_CODE_STATE(&c->dbug_state); + + /* + There are 6 callee-save registers we need to save and restore when + suspending and continuing, plus stack pointer %rsp and instruction pointer + %rip. + + However, if we never suspend, the user-supplied function will in any case + restore the 6 callee-save registers, so we can avoid restoring them in + this case. + */ + __asm__ __volatile__ + ( + "movq %%rsp, (%[save])\n\t" + "movq %[stack], %%rsp\n\t" +#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 4 && !defined(__INTEL_COMPILER) + /* + This emits a DWARF DW_CFA_undefined directive to make the return address + undefined. This indicates that this is the top of the stack frame, and + helps tools that use DWARF stack unwinding to obtain stack traces. + (I use numeric constant to avoid a dependency on libdwarf includes). + */ + ".cfi_escape 0x07, 16\n\t" +#endif + "movq %%rbp, 8(%[save])\n\t" + "movq %%rbx, 16(%[save])\n\t" + "movq %%r12, 24(%[save])\n\t" + "movq %%r13, 32(%[save])\n\t" + "movq %%r14, 40(%[save])\n\t" + "movq %%r15, 48(%[save])\n\t" + "leaq 1f(%%rip), %%rax\n\t" + "leaq 2f(%%rip), %%rcx\n\t" + "movq %%rax, 56(%[save])\n\t" + "movq %%rcx, 64(%[save])\n\t" + /* + Constraint below puts the argument to the user function into %rdi, as + needed for the calling convention. + */ + "callq *%[f]\n\t" + "jmpq *56(%[save])\n" + /* + Come here when operation is done. + We do not need to restore callee-save registers, as the called function + will do this for us if needed. + */ + "1:\n\t" + "movq (%[save]), %%rsp\n\t" + "xorl %[ret], %[ret]\n\t" + "jmp 3f\n" + /* Come here when operation was suspended. */ + "2:\n\t" + "movl $1, %[ret]\n" + "3:\n" + : [ret] "=a" (ret), + [f] "+S" (f), + /* Need this in %rdi to follow calling convention. */ + [d] "+D" (d) + : [stack] "a" (c->stack_top), + /* Need this in callee-save register to preserve in function call. */ + [save] "b" (&c->save[0]) + : "rcx", "rdx", "r8", "r9", "r10", "r11", "memory", "cc" + ); + + DBUG_SWAP_CODE_STATE(&c->dbug_state); + + return ret; +} + +int +my_context_continue(struct my_context *c) +{ + int ret; + + DBUG_SWAP_CODE_STATE(&c->dbug_state); + + __asm__ __volatile__ + ( + "movq (%[save]), %%rax\n\t" + "movq %%rsp, (%[save])\n\t" + "movq %%rax, %%rsp\n\t" + "movq 8(%[save]), %%rax\n\t" + "movq %%rbp, 8(%[save])\n\t" + "movq %%rax, %%rbp\n\t" + "movq 24(%[save]), %%rax\n\t" + "movq %%r12, 24(%[save])\n\t" + "movq %%rax, %%r12\n\t" + "movq 32(%[save]), %%rax\n\t" + "movq %%r13, 32(%[save])\n\t" + "movq %%rax, %%r13\n\t" + "movq 40(%[save]), %%rax\n\t" + "movq %%r14, 40(%[save])\n\t" + "movq %%rax, %%r14\n\t" + "movq 48(%[save]), %%rax\n\t" + "movq %%r15, 48(%[save])\n\t" + "movq %%rax, %%r15\n\t" + + "leaq 1f(%%rip), %%rax\n\t" + "leaq 2f(%%rip), %%rcx\n\t" + "movq %%rax, 56(%[save])\n\t" + "movq 64(%[save]), %%rax\n\t" + "movq %%rcx, 64(%[save])\n\t" + + "movq 16(%[save]), %%rcx\n\t" + "movq %%rbx, 16(%[save])\n\t" + "movq %%rcx, %%rbx\n\t" + + "jmpq *%%rax\n" + /* + Come here when operation is done. + Be sure to use the same callee-save register for %[save] here and in + my_context_spawn(), so we preserve the value correctly at this point. + */ + "1:\n\t" + "movq (%[save]), %%rsp\n\t" + "movq 8(%[save]), %%rbp\n\t" + /* %rbx is preserved from my_context_spawn() in this case. */ + "movq 24(%[save]), %%r12\n\t" + "movq 32(%[save]), %%r13\n\t" + "movq 40(%[save]), %%r14\n\t" + "movq 48(%[save]), %%r15\n\t" + "xorl %[ret], %[ret]\n\t" + "jmp 3f\n" + /* Come here when operation is suspended. */ + "2:\n\t" + "movl $1, %[ret]\n" + "3:\n" + : [ret] "=a" (ret) + : /* Need this in callee-save register to preserve in function call. */ + [save] "b" (&c->save[0]) + : "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11", "memory", "cc" + ); + + DBUG_SWAP_CODE_STATE(&c->dbug_state); + + return ret; +} + +int +my_context_yield(struct my_context *c) +{ + uint64_t *save= &c->save[0]; + __asm__ __volatile__ + ( + "movq (%[save]), %%rax\n\t" + "movq %%rsp, (%[save])\n\t" + "movq %%rax, %%rsp\n\t" + "movq 8(%[save]), %%rax\n\t" + "movq %%rbp, 8(%[save])\n\t" + "movq %%rax, %%rbp\n\t" + "movq 16(%[save]), %%rax\n\t" + "movq %%rbx, 16(%[save])\n\t" + "movq %%rax, %%rbx\n\t" + "movq 24(%[save]), %%rax\n\t" + "movq %%r12, 24(%[save])\n\t" + "movq %%rax, %%r12\n\t" + "movq 32(%[save]), %%rax\n\t" + "movq %%r13, 32(%[save])\n\t" + "movq %%rax, %%r13\n\t" + "movq 40(%[save]), %%rax\n\t" + "movq %%r14, 40(%[save])\n\t" + "movq %%rax, %%r14\n\t" + "movq 48(%[save]), %%rax\n\t" + "movq %%r15, 48(%[save])\n\t" + "movq %%rax, %%r15\n\t" + "movq 64(%[save]), %%rax\n\t" + "leaq 1f(%%rip), %%rcx\n\t" + "movq %%rcx, 64(%[save])\n\t" + + "jmpq *%%rax\n" + + "1:\n" + : [save] "+D" (save) + : + : "rax", "rcx", "rdx", "rsi", "r8", "r9", "r10", "r11", "memory", "cc" + ); + return 0; +} + +int +my_context_init(struct my_context *c, size_t stack_size) +{ + bzero(c, sizeof(*c)); + + if (!(c->stack_bot= malloc(stack_size))) + return -1; /* Out of memory */ + /* + The x86_64 ABI specifies 16-byte stack alignment. + Also put two zero words at the top of the stack. + */ + c->stack_top= (void *) + (( ((intptr)c->stack_bot + stack_size) & ~(intptr)0xf) - 16); + bzero(c->stack_top, 16); + +#ifdef HAVE_VALGRIND + c->valgrind_stack_id= + VALGRIND_STACK_REGISTER(c->stack_bot, c->stack_top); +#endif + return 0; +} + +void +my_context_destroy(struct my_context *c) +{ + if (c->stack_bot) + { + free(c->stack_bot); +#ifdef HAVE_VALGRIND + VALGRIND_STACK_DEREGISTER(c->valgrind_stack_id); +#endif + } + DBUG_FREE_CODE_STATE(&c->dbug_state); +} + +#endif /* MY_CONTEXT_USE_X86_64_GCC_ASM */ + + +#ifdef MY_CONTEXT_USE_I386_GCC_ASM +/* + GCC-i386 implementation of my_context. + + This is slightly optimized in the common case where we never yield + (eg. fetch next row and it is already fully received in buffer). In this + case we do not need to restore registers at return (though we still need to + save them as we cannot know if we will yield or not in advance). +*/ + +#include <stdint.h> +#include <stdlib.h> + +/* + Layout of saved registers etc. + Since this is accessed through gcc inline assembler, it is simpler to just + use numbers than to try to define nice constants or structs. + + 0 0 %esp + 1 4 %ebp + 2 8 %ebx + 3 12 %esi + 4 16 %edi + 5 20 %eip for done + 6 24 %eip for yield/continue +*/ + +int +my_context_spawn(struct my_context *c, void (*f)(void *), void *d) +{ + void (*tmp_f)(void *)= f; + void *tmp_d= d; + int ret; + + DBUG_SWAP_CODE_STATE(&c->dbug_state); + + /* + There are 4 callee-save registers we need to save and restore when + suspending and continuing, plus stack pointer %esp and instruction pointer + %eip. + + However, if we never suspend, the user-supplied function will in any case + restore the 4 callee-save registers, so we can avoid restoring them in + this case. + */ + __asm__ __volatile__ + ( + "movl %%esp, (%[save])\n\t" + "movl %[stack], %%esp\n\t" +#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 4 && !defined(__INTEL_COMPILER) + /* + This emits a DWARF DW_CFA_undefined directive to make the return address + undefined. This indicates that this is the top of the stack frame, and + helps tools that use DWARF stack unwinding to obtain stack traces. + (I use numeric constant to avoid a dependency on libdwarf includes). + */ + ".cfi_escape 0x07, 8\n\t" +#endif + /* Push the parameter on the stack. */ + "pushl %[d]\n\t" + "movl %%ebp, 4(%[save])\n\t" + "movl %%ebx, 8(%[save])\n\t" + "movl %%esi, 12(%[save])\n\t" + "movl %%edi, 16(%[save])\n\t" + /* Get label addresses in -fPIC-compatible way (no pc-relative on 32bit) */ + "call 1f\n" + "1:\n\t" + "popl %%eax\n\t" + "addl $(2f-1b), %%eax\n\t" + "movl %%eax, 20(%[save])\n\t" + "addl $(3f-2f), %%eax\n\t" + "movl %%eax, 24(%[save])\n\t" + "call *%[f]\n\t" + "jmp *20(%[save])\n" + /* + Come here when operation is done. + We do not need to restore callee-save registers, as the called function + will do this for us if needed. + */ + "2:\n\t" + "movl (%[save]), %%esp\n\t" + "xorl %[ret], %[ret]\n\t" + "jmp 4f\n" + /* Come here when operation was suspended. */ + "3:\n\t" + "movl $1, %[ret]\n" + "4:\n" + : [ret] "=a" (ret), + [f] "+c" (tmp_f), + [d] "+d" (tmp_d) + : [stack] "a" (c->stack_top), + /* Need this in callee-save register to preserve across function call. */ + [save] "D" (&c->save[0]) + : "memory", "cc" + ); + + DBUG_SWAP_CODE_STATE(&c->dbug_state); + + return ret; +} + +int +my_context_continue(struct my_context *c) +{ + int ret; + + DBUG_SWAP_CODE_STATE(&c->dbug_state); + + __asm__ __volatile__ + ( + "movl (%[save]), %%eax\n\t" + "movl %%esp, (%[save])\n\t" + "movl %%eax, %%esp\n\t" + "movl 4(%[save]), %%eax\n\t" + "movl %%ebp, 4(%[save])\n\t" + "movl %%eax, %%ebp\n\t" + "movl 8(%[save]), %%eax\n\t" + "movl %%ebx, 8(%[save])\n\t" + "movl %%eax, %%ebx\n\t" + "movl 12(%[save]), %%eax\n\t" + "movl %%esi, 12(%[save])\n\t" + "movl %%eax, %%esi\n\t" + + "movl 24(%[save]), %%eax\n\t" + "call 1f\n" + "1:\n\t" + "popl %%ecx\n\t" + "addl $(2f-1b), %%ecx\n\t" + "movl %%ecx, 20(%[save])\n\t" + "addl $(3f-2f), %%ecx\n\t" + "movl %%ecx, 24(%[save])\n\t" + + /* Must restore %edi last as it is also our %[save] register. */ + "movl 16(%[save]), %%ecx\n\t" + "movl %%edi, 16(%[save])\n\t" + "movl %%ecx, %%edi\n\t" + + "jmp *%%eax\n" + /* + Come here when operation is done. + Be sure to use the same callee-save register for %[save] here and in + my_context_spawn(), so we preserve the value correctly at this point. + */ + "2:\n\t" + "movl (%[save]), %%esp\n\t" + "movl 4(%[save]), %%ebp\n\t" + "movl 8(%[save]), %%ebx\n\t" + "movl 12(%[save]), %%esi\n\t" + "movl 16(%[save]), %%edi\n\t" + "xorl %[ret], %[ret]\n\t" + "jmp 4f\n" + /* Come here when operation is suspended. */ + "3:\n\t" + "movl $1, %[ret]\n" + "4:\n" + : [ret] "=a" (ret) + : /* Need this in callee-save register to preserve in function call. */ + [save] "D" (&c->save[0]) + : "ecx", "edx", "memory", "cc" + ); + + DBUG_SWAP_CODE_STATE(&c->dbug_state); + + return ret; +} + +int +my_context_yield(struct my_context *c) +{ + uint64_t *save= &c->save[0]; + __asm__ __volatile__ + ( + "movl (%[save]), %%eax\n\t" + "movl %%esp, (%[save])\n\t" + "movl %%eax, %%esp\n\t" + "movl 4(%[save]), %%eax\n\t" + "movl %%ebp, 4(%[save])\n\t" + "movl %%eax, %%ebp\n\t" + "movl 8(%[save]), %%eax\n\t" + "movl %%ebx, 8(%[save])\n\t" + "movl %%eax, %%ebx\n\t" + "movl 12(%[save]), %%eax\n\t" + "movl %%esi, 12(%[save])\n\t" + "movl %%eax, %%esi\n\t" + "movl 16(%[save]), %%eax\n\t" + "movl %%edi, 16(%[save])\n\t" + "movl %%eax, %%edi\n\t" + + "movl 24(%[save]), %%eax\n\t" + "call 1f\n" + "1:\n\t" + "popl %%ecx\n\t" + "addl $(2f-1b), %%ecx\n\t" + "movl %%ecx, 24(%[save])\n\t" + + "jmp *%%eax\n" + + "2:\n" + : [save] "+d" (save) + : + : "eax", "ecx", "memory", "cc" + ); + return 0; +} + +int +my_context_init(struct my_context *c, size_t stack_size) +{ + bzero(c, sizeof(*c)); + if (!(c->stack_bot= malloc(stack_size))) + return -1; /* Out of memory */ + c->stack_top= (void *) + (( ((intptr)c->stack_bot + stack_size) & ~(intptr)0xf) - 16); + bzero(c->stack_top, 16); + +#ifdef HAVE_VALGRIND + c->valgrind_stack_id= + VALGRIND_STACK_REGISTER(c->stack_bot, c->stack_top); +#endif + return 0; +} + +void +my_context_destroy(struct my_context *c) +{ + if (c->stack_bot) + { + free(c->stack_bot); +#ifdef HAVE_VALGRIND + VALGRIND_STACK_DEREGISTER(c->valgrind_stack_id); +#endif + } + DBUG_FREE_CODE_STATE(&c->dbug_state); +} + +#endif /* MY_CONTEXT_USE_I386_GCC_ASM */ + + +#ifdef MY_CONTEXT_USE_WIN32_FIBERS +int +my_context_yield(struct my_context *c) +{ + c->return_value= 1; + SwitchToFiber(c->app_fiber); + return 0; +} + + +static void WINAPI +my_context_trampoline(void *p) +{ + struct my_context *c= (struct my_context *)p; + /* + Reuse the Fiber by looping infinitely, each time we are scheduled we + spawn the appropriate function and switch back when it is done. + + This way we avoid the overhead of CreateFiber() for every asynchroneous + operation. + */ + for(;;) + { + (*(c->user_func))(c->user_arg); + c->return_value= 0; + SwitchToFiber(c->app_fiber); + } +} + +int +my_context_init(struct my_context *c, size_t stack_size) +{ + bzero(c, sizeof(*c)); + c->lib_fiber= CreateFiber(stack_size, my_context_trampoline, c); + if (c->lib_fiber) + return 0; + return -1; +} + +void +my_context_destroy(struct my_context *c) +{ + DBUG_FREE_CODE_STATE(&c->dbug_state); + if (c->lib_fiber) + { + DeleteFiber(c->lib_fiber); + c->lib_fiber= NULL; + } +} + +int +my_context_spawn(struct my_context *c, void (*f)(void *), void *d) +{ + void *current_fiber; + c->user_func= f; + c->user_arg= d; + /* + This seems to be a common trick to run ConvertThreadToFiber() only on the + first occurence in a thread, in a way that works on multiple Windows + versions. + */ + current_fiber= GetCurrentFiber(); + if (current_fiber == NULL || current_fiber == (void *)0x1e00) + current_fiber= ConvertThreadToFiber(c); + c->app_fiber= current_fiber; + DBUG_SWAP_CODE_STATE(&c->dbug_state); + SwitchToFiber(c->lib_fiber); + DBUG_SWAP_CODE_STATE(&c->dbug_state); + return c->return_value; +} + +int +my_context_continue(struct my_context *c) +{ + DBUG_SWAP_CODE_STATE(&c->dbug_state); + SwitchToFiber(c->lib_fiber); + DBUG_SWAP_CODE_STATE(&c->dbug_state); + return c->return_value; +} + +#endif /* MY_CONTEXT_USE_WIN32_FIBERS */ + +#ifdef MY_CONTEXT_DISABLE +int +my_context_continue(struct my_context *c) +{ + return -1; +} + + +int +my_context_spawn(struct my_context *c, void (*f)(void *), void *d) +{ + return -1; +} + + +int +my_context_yield(struct my_context *c) +{ + return -1; +} + +int +my_context_init(struct my_context *c, size_t stack_size) +{ + return -1; /* Out of memory */ +} + +void +my_context_destroy(struct my_context *c) +{ +} + +#endif diff --git a/mysys/my_copy.c b/mysys/my_copy.c index e89f8c8e5b8..21de1e953a2 100644 --- a/mysys/my_copy.c +++ b/mysys/my_copy.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2000, 2010, Oracle and/or its affiliates This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -14,6 +15,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mysys_priv.h" +#include "mysys_err.h" #include <my_dir.h> /* for stat */ #include <m_string.h> #include "mysys_err.h" @@ -57,6 +59,7 @@ int my_copy(const char *from, const char *to, myf MyFlags) File from_file,to_file; uchar buff[IO_SIZE]; MY_STAT stat_buff,new_stat_buff; + my_bool file_created= 0; DBUG_ENTER("my_copy"); DBUG_PRINT("my",("from %s to %s MyFlags %d", from, to, MyFlags)); @@ -81,6 +84,7 @@ int my_copy(const char *from, const char *to, myf MyFlags) MyFlags)) < 0) goto err; + file_created= 1; while ((Count=my_read(from_file, buff, sizeof(buff), MyFlags)) != 0) { if (Count == (uint) -1 || @@ -98,6 +102,8 @@ int my_copy(const char *from, const char *to, myf MyFlags) if (my_close(from_file,MyFlags) | my_close(to_file,MyFlags)) DBUG_RETURN(-1); /* Error on close */ + from_file=to_file= -1; /* Files are closed */ + /* Copy modes if possible */ if (MyFlags & MY_HOLD_ORIGINAL_MODES && !new_file_stat) @@ -106,18 +112,20 @@ int my_copy(const char *from, const char *to, myf MyFlags) if (chmod(to, stat_buff.st_mode & 07777)) { my_errno= errno; - if (MyFlags & (MY_FAE+MY_WME)) - my_error(EE_CHANGE_PERMISSIONS, MYF(ME_BELL+ME_WAITTANG), from, errno); - goto err; + if (MyFlags & MY_WME) + my_error(EE_CHANGE_PERMISSIONS, MYF(ME_BELL+ME_WAITTANG), to, errno); + if (MyFlags & MY_FAE) + goto err; } #if !defined(__WIN__) /* Copy ownership */ if (chown(to, stat_buff.st_uid, stat_buff.st_gid)) { my_errno= errno; - if (MyFlags & (MY_FAE+MY_WME)) - my_error(EE_CHANGE_OWNERSHIP, MYF(ME_BELL+ME_WAITTANG), from, errno); - goto err; + if (MyFlags & MY_WME) + my_error(EE_CANT_COPY_OWNERSHIP, MYF(ME_BELL+ME_WAITTANG), to, errno); + if (MyFlags & MY_FAE) + goto err; } #endif @@ -134,11 +142,11 @@ int my_copy(const char *from, const char *to, myf MyFlags) err: if (from_file >= 0) (void) my_close(from_file,MyFlags); - if (to_file >= 0) - { - (void) my_close(to_file, MyFlags); - /* attempt to delete the to-file we've partially written */ + if (to_file >= 0) (void) my_close(to_file, MyFlags); + + /* attempt to delete the to-file we've partially written */ + if (file_created) (void) my_delete(to, MyFlags); - } + DBUG_RETURN(-1); } /* my_copy */ diff --git a/mysys/my_delete.c b/mysys/my_delete.c index cf9573f592b..f5737ea66e0 100644 --- a/mysys/my_delete.c +++ b/mysys/my_delete.c @@ -17,13 +17,23 @@ #include "mysys_err.h" #include <my_sys.h> +#ifdef _WIN32 +static int my_win_unlink(const char *name); +#endif + int my_delete(const char *name, myf MyFlags) { int err; DBUG_ENTER("my_delete"); DBUG_PRINT("my",("name %s MyFlags %d", name, MyFlags)); - if ((err = unlink(name)) == -1) +#ifdef _WIN32 + err = my_win_unlink(name); +#else + err = unlink(name); +#endif + + if(err) { my_errno=errno; if (MyFlags & (MY_FAE+MY_WME)) @@ -36,90 +46,108 @@ int my_delete(const char *name, myf MyFlags) DBUG_RETURN(err); } /* my_delete */ -#if defined(__WIN__) -/** - Delete file which is possibly not closed. - This function is intended to be used exclusively as a temporal solution - for Win NT in case when it is needed to delete a not closed file (note - that the file must be opened everywhere with FILE_SHARE_DELETE mode). - Deleting not-closed files can not be supported on Win 98|ME (and because - of that is considered harmful). - - The function deletes the file with its preliminary renaming. This is - because when not-closed share-delete file is deleted it still lives on - a disk until it will not be closed everwhere. This may conflict with an - attempt to create a new file with the same name. The deleted file is - renamed to <name>.<num>.deleted where <name> - the initial name of the - file, <num> - a hexadecimal number chosen to make the temporal name to - be unique. +#if defined (_WIN32) +/* + Delete file. - @param the name of the being deleted file - @param the flags instructing how to react on an error internally in - the function + The function also makes best effort to minimize number of errors, + where another program (or thread in the current program) has the the same file + open. - @note The per-thread @c my_errno holds additional info for a caller to - decide how critical the error can be. + We're using 2 tricks to prevent the errors. - @retval - 0 ok - @retval - 1 error + 1. A usual Win32's DeleteFile() can with ERROR_SHARED_VIOLATION, + because the file is opened in another application (often, antivirus or backup) + + We avoid the error by using CreateFile() with FILE_FLAG_DELETE_ON_CLOSE, instead + of DeleteFile() + 2. If file which is deleted (delete on close) but has not entirely gone, + because it is still opened by some app, an attempt to trcreate file with the + same name would result in yet another error. The workaround here is renaming + a file to unique name. -*/ -int nt_share_delete(const char *name, myf MyFlags) + Symbolic link are deleted without renaming. Directories are not deleted. + */ +static int my_win_unlink(const char *name) { - char buf[MAX_PATH + 20]; - ulong cnt; - DBUG_ENTER("nt_share_delete"); - DBUG_PRINT("my",("name %s MyFlags %d", name, MyFlags)); - - for (cnt= GetTickCount(); cnt; cnt--) + HANDLE handle= INVALID_HANDLE_VALUE; + DWORD attributes; + DWORD last_error; + char unique_filename[MAX_PATH + 35]; + unsigned long long tsc; /* time stamp counter, for unique filename*/ + + DBUG_ENTER("my_win_unlink"); + attributes= GetFileAttributes(name); + if (attributes == INVALID_FILE_ATTRIBUTES) { - errno= 0; - sprintf(buf, "%s.%08X.deleted", name, cnt); - if (MoveFile(name, buf)) - break; - - if ((errno= GetLastError()) == ERROR_ALREADY_EXISTS) - continue; - - /* This happened during tests with MERGE tables. */ - if (errno == ERROR_ACCESS_DENIED) - continue; - - DBUG_PRINT("warning", ("Failed to rename %s to %s, errno: %d", - name, buf, errno)); - break; + last_error= GetLastError(); + DBUG_PRINT("error",("GetFileAttributes(%s) failed with %u\n", name, last_error)); + goto error; } - if (errno == ERROR_FILE_NOT_FOUND) + if (attributes & FILE_ATTRIBUTE_DIRECTORY) { - my_errno= ENOENT; // marking, that `name' doesn't exist + DBUG_PRINT("error",("can't remove %s - it is a directory\n", name)); + errno= EINVAL; + DBUG_RETURN(-1); } - else if (errno == 0) + + if (attributes & FILE_ATTRIBUTE_REPARSE_POINT) + { + /* Symbolic link. Delete link, the not target */ + if (!DeleteFile(name)) + { + last_error= GetLastError(); + DBUG_PRINT("error",("DeleteFile(%s) failed with %u\n", name,last_error)); + goto error; + } + DBUG_RETURN(0); + } + + handle= CreateFile(name, DELETE, 0, NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL); + if (handle != INVALID_HANDLE_VALUE) { - if (DeleteFile(buf)) - DBUG_RETURN(0); /* - The below is more complicated than necessary. For some reason, the - assignment to my_errno clears the error number, which is retrieved - by GetLastError() (VC2005EE). Assigning to errno first, allows to - retrieve the correct value. + We opened file without sharing flags (exclusive), noone else has this file + opened, thus it is save to close handle to remove it. No renaming is + necessary. */ - errno= GetLastError(); - if (errno == 0) - my_errno= ENOENT; // marking, that `buf' doesn't exist - else - my_errno= errno; + CloseHandle(handle); + DBUG_RETURN(0); + } + + /* + Can't open file exclusively, hence the file must be already opened by + someone else. Open it for delete (with all FILE_SHARE flags set), + rename to unique name, close. + */ + handle= CreateFile(name, DELETE, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL); + if (handle == INVALID_HANDLE_VALUE) + { + last_error= GetLastError(); + DBUG_PRINT("error", + ("CreateFile(%s) with FILE_FLAG_DELETE_ON_CLOSE failed with %u\n", + name,last_error)); + goto error; } - else - my_errno= errno; - if (MyFlags & (MY_FAE+MY_WME)) - my_error(EE_DELETE, MYF(ME_BELL + ME_WAITTANG + (MyFlags & ME_NOINPUT)), - name, my_errno); + tsc= __rdtsc(); + my_snprintf(unique_filename,sizeof(unique_filename),"%s.%llx.deleted", + name, tsc); + if (!MoveFile(name, unique_filename)) + { + DBUG_PRINT("warning", ("moving %s to unique filename failed, error %u\n", + name,GetLastError())); + } + + CloseHandle(handle); + DBUG_RETURN(0); + +error: + my_osmaperr(last_error); DBUG_RETURN(-1); } -#endif +#endif
\ No newline at end of file diff --git a/mysys/my_error.c b/mysys/my_error.c index 5c1e1b5e92a..23bc06c3d9d 100644 --- a/mysys/my_error.c +++ b/mysys/my_error.c @@ -114,7 +114,7 @@ void my_printf_error(uint error, const char *format, myf MyFlags, ...) va_list args; char ebuff[ERRMSGSIZE]; DBUG_ENTER("my_printf_error"); - DBUG_PRINT("my", ("nr: %d MyFlags: %d errno: %d Format: %s", + DBUG_PRINT("my", ("nr: %d MyFlags: %d errno: %d format: %s", error, MyFlags, errno, format)); va_start(args,MyFlags); @@ -148,27 +148,6 @@ void my_printv_error(uint error, const char *format, myf MyFlags, va_list ap) DBUG_VOID_RETURN; } -/* - Warning as printf - - SYNOPSIS - my_printf_warning() - format> Format string - ...> variable list -*/ -void(*sql_print_warning_hook)(const char *format,...); -void my_printf_warning(const char *format, ...) -{ - va_list args; - char wbuff[ERRMSGSIZE]; - DBUG_ENTER("my_printf_warning"); - DBUG_PRINT("my", ("Format: %s", format)); - va_start(args,format); - (void) my_vsnprintf (wbuff, sizeof(wbuff), format, args); - va_end(args); - (*sql_print_warning_hook)(wbuff); - DBUG_VOID_RETURN; -} /* Give message using error_handler_hook diff --git a/mysys/my_file.c b/mysys/my_file.c index 719b77cc8b5..8d01285a94b 100644 --- a/mysys/my_file.c +++ b/mysys/my_file.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2000, 2010, Oracle and/or its affiliates This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -51,7 +52,7 @@ static uint set_max_open_files(uint max_file_limit) DBUG_PRINT("info", ("rlim_cur: %u rlim_max: %u", (uint) rlimit.rlim_cur, (uint) rlimit.rlim_max)); - if (rlimit.rlim_cur == RLIM_INFINITY) + if ((ulonglong) rlimit.rlim_cur == (ulonglong) RLIM_INFINITY) rlimit.rlim_cur = max_file_limit; if (rlimit.rlim_cur >= max_file_limit) DBUG_RETURN(rlimit.rlim_cur); /* purecov: inspected */ diff --git a/mysys/my_fopen.c b/mysys/my_fopen.c index 12db47f9539..52f61649bb3 100644 --- a/mysys/my_fopen.c +++ b/mysys/my_fopen.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2012, Oracle and/or its affiliates + Copyright (c) 1985, 2011, Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -264,7 +265,7 @@ FILE *my_fdopen(File Filedes, const char *name, int Flags, myf MyFlags) FILE *fd; char type[5]; DBUG_ENTER("my_fdopen"); - DBUG_PRINT("my",("Fd: %d Flags: %d MyFlags: %d", + DBUG_PRINT("my",("fd: %d Flags: %d MyFlags: %d", Filedes, Flags, MyFlags)); make_ftype(type,Flags); diff --git a/mysys/my_gethwaddr.c b/mysys/my_gethwaddr.c index 54823a7c5ca..74dae29f235 100644 --- a/mysys/my_gethwaddr.c +++ b/mysys/my_gethwaddr.c @@ -1,4 +1,6 @@ -/* Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2004, 2010, Oracle and/or its affiliates + Copyright (c) 2011, Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,8 +23,17 @@ #ifndef MAIN -#ifdef __FreeBSD__ +static my_bool memcpy_and_test(uchar *to, uchar *from, uint len) +{ + uint i, res= 1; + + for (i= 0; i < len; i++) + if ((*to++= *from++)) + res= 0; + return res; +} +#if defined(__APPLE__) || defined(__FreeBSD__) #include <net/ethernet.h> #include <sys/sysctl.h> #include <net/route.h> @@ -32,11 +43,10 @@ my_bool my_gethwaddr(uchar *to) { size_t len; - char *buf, *next, *end; + uchar *buf, *next, *end, *addr; struct if_msghdr *ifm; struct sockaddr_dl *sdl; - int res=1, mib[6]={CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, 0}; - char zero_array[ETHER_ADDR_LEN] = {0}; + int res= 1, mib[6]= {CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, 0}; if (sysctl(mib, 6, NULL, &len, NULL, 0) == -1) goto err; @@ -52,9 +62,9 @@ my_bool my_gethwaddr(uchar *to) ifm = (struct if_msghdr *)next; if (ifm->ifm_type == RTM_IFINFO) { - sdl= (struct sockaddr_dl *)(ifm + 1); - memcpy(to, LLADDR(sdl), ETHER_ADDR_LEN); - res= memcmp(to, zero_array, ETHER_ADDR_LEN) ? 0 : 1; + sdl = (struct sockaddr_dl *)(ifm + 1); + addr= (uchar *)LLADDR(sdl); + res= memcpy_and_test(to, addr, ETHER_ADDR_LEN); } } @@ -62,140 +72,98 @@ err: return res; } -#elif __linux__ - +#elif defined(__linux__) || defined(__sun__) #include <net/if.h> #include <sys/ioctl.h> -#include <net/ethernet.h> +#include <net/if_arp.h> +#ifdef HAVE_SYS_SOCKIO_H +#include <sys/sockio.h> +#endif + +#define ETHER_ADDR_LEN 6 my_bool my_gethwaddr(uchar *to) { int fd, res= 1; - struct ifreq ifr; - char zero_array[ETHER_ADDR_LEN] = {0}; + struct ifreq ifr[32]; + struct ifconf ifc; + + ifc.ifc_req= ifr; + ifc.ifc_len= sizeof(ifr); fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) goto err; - bzero(&ifr, sizeof(ifr)); - strnmov(ifr.ifr_name, "eth0", sizeof(ifr.ifr_name) - 1); - - do + if (ioctl(fd, SIOCGIFCONF, (char*)&ifc) >= 0) { - if (ioctl(fd, SIOCGIFHWADDR, &ifr) >= 0) + uint i; + for (i= 0; res && i < ifc.ifc_len / sizeof(ifr[0]); i++) { - memcpy(to, &ifr.ifr_hwaddr.sa_data, ETHER_ADDR_LEN); - res= memcmp(to, zero_array, ETHER_ADDR_LEN) ? 0 : 1; +#ifdef __linux__ + if (ioctl(fd, SIOCGIFHWADDR, &ifr[i]) >= 0) + res= memcpy_and_test(to, (uchar *)&ifr[i].ifr_hwaddr.sa_data, + ETHER_ADDR_LEN); +#else + /* + A bug in OpenSolaris used to prevent non-root from getting a mac address: + {no url. Oracle killed the old OpenSolaris bug database} + + Thus, we'll use an alternative method and extract the address from the + arp table. + */ + struct arpreq arpr; + arpr.arp_pa= ifr[i].ifr_addr; + + if (ioctl(fd, SIOCGARP, (char*)&arpr) >= 0) + res= memcpy_and_test(to, (uchar *)&arpr.arp_ha.sa_data, + ETHER_ADDR_LEN); +#endif } - } while (res && (errno == 0 || errno == ENODEV) && ifr.ifr_name[3]++ < '6'); + } close(fd); err: return res; } -#elif defined(__WIN__) - -/* - Workaround for BUG#32082 (Definition of VOID in my_global.h conflicts with - windows headers) -*/ -#ifdef VOID -#undef VOID -#define VOID void -#endif - +#elif defined(_WIN32) +#include <winsock2.h> #include <iphlpapi.h> +#pragma comment(lib, "iphlpapi.lib")
-/* - The following typedef is for dynamically loading iphlpapi.dll / - GetAdaptersAddresses. Dynamic loading is used because - GetAdaptersAddresses is not available on Windows 2000 which MySQL - still supports. Static linking would cause an unresolved export. -*/ -typedef DWORD (WINAPI *pfnGetAdaptersAddresses)(IN ULONG Family, - IN DWORD Flags,IN PVOID Reserved, - OUT PIP_ADAPTER_ADDRESSES pAdapterAddresses, - IN OUT PULONG pOutBufLen); +#define ETHER_ADDR_LEN 6 -/* - my_gethwaddr - Windows version +my_bool my_gethwaddr(uchar *to) +{ + my_bool res= 1; - @brief Retrieve MAC address from network hardware + IP_ADAPTER_INFO *info= NULL; + ULONG info_len= 0; - @param[out] to MAC address exactly six bytes + if (GetAdaptersInfo(info, &info_len) != ERROR_BUFFER_OVERFLOW) + goto err; - @return Operation status - @retval 0 OK - @retval <>0 FAILED -*/ -my_bool my_gethwaddr(uchar *to) -{ - PIP_ADAPTER_ADDRESSES pAdapterAddresses; - PIP_ADAPTER_ADDRESSES pCurrAddresses; - IP_ADAPTER_ADDRESSES adapterAddresses; - ULONG address_len; - my_bool return_val= 1; - static pfnGetAdaptersAddresses fnGetAdaptersAddresses= - (pfnGetAdaptersAddresses)-1; - - if(fnGetAdaptersAddresses == (pfnGetAdaptersAddresses)-1) - { - /* Get the function from the DLL */ - fnGetAdaptersAddresses= (pfnGetAdaptersAddresses) - GetProcAddress(LoadLibrary("iphlpapi.dll"), - "GetAdaptersAddresses"); - } - if (!fnGetAdaptersAddresses) - return 1; /* failed to get function */ - address_len= sizeof (IP_ADAPTER_ADDRESSES); + info= (IP_ADAPTER_INFO *)alloca(info_len); - /* Get the required size for the address data. */ - if (fnGetAdaptersAddresses(AF_UNSPEC, 0, 0, &adapterAddresses, &address_len) - == ERROR_BUFFER_OVERFLOW) - { - pAdapterAddresses= my_malloc(address_len, 0); - if (!pAdapterAddresses) - return 1; /* error, alloc failed */ - } - else - pAdapterAddresses= &adapterAddresses; /* one is enough don't alloc */ + if (GetAdaptersInfo(info, &info_len) != NO_ERROR) + goto err; - /* Get the hardware info. */ - if (fnGetAdaptersAddresses(AF_UNSPEC, 0, 0, pAdapterAddresses, &address_len) - == NO_ERROR) + while (info && res) { - pCurrAddresses= pAdapterAddresses; - - while (pCurrAddresses) + if (info->Type == MIB_IF_TYPE_ETHERNET && + info->AddressLength == ETHER_ADDR_LEN) { - /* Look for ethernet cards. */ - if (pCurrAddresses->IfType == IF_TYPE_ETHERNET_CSMACD) - { - /* check for a good address */ - if (pCurrAddresses->PhysicalAddressLength < 6) - continue; /* bad address */ - - /* save 6 bytes of the address in the 'to' parameter */ - memcpy(to, pCurrAddresses->PhysicalAddress, 6); - - /* Network card found, we're done. */ - return_val= 0; - break; - } - pCurrAddresses= pCurrAddresses->Next; + res= memcpy_and_test(to, info->Address, ETHER_ADDR_LEN); } + info = info->Next; } - /* Clean up memory allocation. */ - if (pAdapterAddresses != &adapterAddresses) - my_free(pAdapterAddresses); - - return return_val; +err: + return res; } -#else /* __FreeBSD__ || __linux__ || __WIN__ */ +#else /* unsupported system */ /* just fail */ my_bool my_gethwaddr(uchar *to __attribute__((unused))) { @@ -214,7 +182,7 @@ int main(int argc __attribute__((unused)),char **argv) printf("my_gethwaddr failed with errno %d\n", errno); exit(1); } - for (i=0; i < sizeof(mac); i++) + for (i= 0; i < sizeof(mac); i++) { if (i) printf(":"); printf("%02x", mac[i]); diff --git a/mysys/my_getncpus.c b/mysys/my_getncpus.c index 34257ffa014..cc2c3843e0e 100644 --- a/mysys/my_getncpus.c +++ b/mysys/my_getncpus.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2006, 2010, Oracle and/or its affiliates This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -41,7 +42,7 @@ int my_getncpus() ncpus= sysinfo.dwNumberOfProcessors; #else -/* unknown so play safe: assume SMP and forbid uniprocessor build */ + /* unknown so play safe: assume SMP and forbid uniprocessor build */ ncpus= 2; #endif } diff --git a/mysys/my_getopt.c b/mysys/my_getopt.c index 03469589173..dd16e535eda 100644 --- a/mysys/my_getopt.c +++ b/mysys/my_getopt.c @@ -1,4 +1,6 @@ -/* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2002, 2013, Oracle and/or its affiliates + Copyright (c) 2009, 2013, Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -72,6 +74,8 @@ static void default_reporter(enum loglevel level, const char *format, ...) { va_list args; + DBUG_ENTER("default_reporter"); + va_start(args, format); if (level == WARNING_LEVEL) fprintf(stderr, "%s", "Warning: "); @@ -81,6 +85,7 @@ static void default_reporter(enum loglevel level, va_end(args); fputc('\n', stderr); fflush(stderr); + DBUG_VOID_RETURN; } static my_getopt_value getopt_get_addr; @@ -191,6 +196,7 @@ int handle_options(int *argc, char ***argv, void *value; int error, i; my_bool is_cmdline_arg= 1; + DBUG_ENTER("handle_options"); /* handle_options() assumes arg0 (program name) always exists */ DBUG_ASSERT(argc && *argc >= 1); @@ -292,7 +298,7 @@ int handle_options(int *argc, char ***argv, my_progname, special_opt_prefix[i], opt_str, special_opt_prefix[i], prev_found); - return EXIT_AMBIGUOUS_OPTION; + DBUG_RETURN(EXIT_AMBIGUOUS_OPTION); } switch (i) { case OPT_SKIP: @@ -337,7 +343,7 @@ int handle_options(int *argc, char ***argv, "%s: unknown variable '%s'", my_progname, cur_arg); if (!option_is_loose) - return EXIT_UNKNOWN_VARIABLE; + DBUG_RETURN(EXIT_UNKNOWN_VARIABLE); } else { @@ -347,7 +353,7 @@ int handle_options(int *argc, char ***argv, "%s: unknown option '--%s'", my_progname, cur_arg); if (!option_is_loose) - return EXIT_UNKNOWN_OPTION; + DBUG_RETURN(EXIT_UNKNOWN_OPTION); } if (option_is_loose) { @@ -364,7 +370,7 @@ int handle_options(int *argc, char ***argv, my_getopt_error_reporter(ERROR_LEVEL, "%s: variable prefix '%s' is not unique", my_progname, opt_str); - return EXIT_VAR_PREFIX_NOT_UNIQUE; + DBUG_RETURN(EXIT_VAR_PREFIX_NOT_UNIQUE); } else { @@ -373,7 +379,7 @@ int handle_options(int *argc, char ***argv, "%s: ambiguous option '--%s' (%s, %s)", my_progname, opt_str, prev_found, optp->name); - return EXIT_AMBIGUOUS_OPTION; + DBUG_RETURN(EXIT_AMBIGUOUS_OPTION); } } if ((optp->var_type & GET_TYPE_MASK) == GET_DISABLED) @@ -387,14 +393,14 @@ int handle_options(int *argc, char ***argv, (*argc)--; continue; } - return EXIT_OPTION_DISABLED; + DBUG_RETURN(EXIT_OPTION_DISABLED); } error= 0; value= optp->var_type & GET_ASK_ADDR ? (*getopt_get_addr)(key_name, (uint) strlen(key_name), optp, &error) : optp->value; if (error) - return error; + DBUG_RETURN(error); if (optp->arg_type == NO_ARG) { @@ -409,7 +415,7 @@ int handle_options(int *argc, char ***argv, my_getopt_error_reporter(ERROR_LEVEL, "%s: option '--%s' cannot take an argument", my_progname, optp->name); - return EXIT_NO_ARGUMENT_ALLOWED; + DBUG_RETURN(EXIT_NO_ARGUMENT_ALLOWED); } if ((optp->var_type & GET_TYPE_MASK) == GET_BOOL) { @@ -420,10 +426,12 @@ int handle_options(int *argc, char ***argv, */ (*argc)--; if (!optend || *optend == '1' || - !my_strcasecmp(&my_charset_latin1, optend, "true")) + !my_strcasecmp(&my_charset_latin1, optend, "true") || + !my_strcasecmp(&my_charset_latin1, optend, "on")) *((my_bool*) value)= (my_bool) 1; else if (*optend == '0' || - !my_strcasecmp(&my_charset_latin1, optend, "false")) + !my_strcasecmp(&my_charset_latin1, optend, "false") || + !my_strcasecmp(&my_charset_latin1, optend, "off")) *((my_bool*) value)= (my_bool) 0; else { @@ -436,7 +444,7 @@ int handle_options(int *argc, char ***argv, if (get_one_option && get_one_option(optp->id, optp, *((my_bool*) value) ? enabled_my_option : disabled_my_option)) - return EXIT_ARGUMENT_INVALID; + DBUG_RETURN(EXIT_ARGUMENT_INVALID); continue; } argument= optend; @@ -453,7 +461,7 @@ int handle_options(int *argc, char ***argv, my_getopt_error_reporter(ERROR_LEVEL, "%s: option '--%s' requires an argument", my_progname, optp->name); - return EXIT_ARGUMENT_REQUIRED; + DBUG_RETURN(EXIT_ARGUMENT_REQUIRED); } argument= *pos; (*argc)--; @@ -478,14 +486,14 @@ int handle_options(int *argc, char ***argv, fprintf(stderr, "%s: ERROR: Option '-%c' used, but is disabled\n", my_progname, optp->id); - return EXIT_OPTION_DISABLED; + DBUG_RETURN(EXIT_OPTION_DISABLED); } if ((optp->var_type & GET_TYPE_MASK) == GET_BOOL && optp->arg_type == NO_ARG) { *((my_bool*) optp->value)= (my_bool) 1; if (get_one_option && get_one_option(optp->id, optp, argument)) - return EXIT_UNSPECIFIED_ERROR; + DBUG_RETURN(EXIT_UNSPECIFIED_ERROR); continue; } else if (optp->arg_type == REQUIRED_ARG || @@ -505,7 +513,7 @@ int handle_options(int *argc, char ***argv, if (optp->var_type == GET_BOOL) *((my_bool*) optp->value)= (my_bool) 1; if (get_one_option && get_one_option(optp->id, optp, argument)) - return EXIT_UNSPECIFIED_ERROR; + DBUG_RETURN(EXIT_UNSPECIFIED_ERROR); continue; } /* Check if there are more arguments after this one */ @@ -515,7 +523,7 @@ int handle_options(int *argc, char ***argv, my_getopt_error_reporter(ERROR_LEVEL, "%s: option '-%c' requires an argument", my_progname, optp->id); - return EXIT_ARGUMENT_REQUIRED; + DBUG_RETURN(EXIT_ARGUMENT_REQUIRED); } argument= *++pos; (*argc)--; @@ -524,9 +532,9 @@ int handle_options(int *argc, char ***argv, } if ((error= setval(optp, optp->value, argument, set_maximum_value))) - return error; + DBUG_RETURN(error); if (get_one_option && get_one_option(optp->id, optp, argument)) - return EXIT_UNSPECIFIED_ERROR; + DBUG_RETURN(EXIT_UNSPECIFIED_ERROR); break; } } @@ -560,7 +568,7 @@ int handle_options(int *argc, char ***argv, my_getopt_error_reporter(ERROR_LEVEL, "%s: unknown option '-%c'", my_progname, *optend); - return EXIT_UNKNOWN_OPTION; + DBUG_RETURN(EXIT_UNKNOWN_OPTION); } } } @@ -568,10 +576,11 @@ int handle_options(int *argc, char ***argv, (*argc)--; /* option handled (short), decrease argument count */ continue; } - if ((error= setval(optp, value, argument, set_maximum_value))) - return error; + if (((error= setval(optp, value, argument, set_maximum_value))) && + !option_is_loose) + DBUG_RETURN(error); if (get_one_option && get_one_option(optp->id, optp, argument)) - return EXIT_UNSPECIFIED_ERROR; + DBUG_RETURN(EXIT_UNSPECIFIED_ERROR); (*argc)--; /* option handled (long), decrease argument count */ } @@ -585,7 +594,7 @@ int handle_options(int *argc, char ***argv, to the program, yet to be (possibly) handled. */ (*argv)[argvpos]= 0; - return 0; + DBUG_RETURN(0); } @@ -606,6 +615,7 @@ int handle_options(int *argc, char ***argv, static char *check_struct_option(char *cur_arg, char *key_name) { char *ptr, *end; + DBUG_ENTER("check_struct_option"); ptr= strcend(cur_arg + 1, '.'); /* Skip the first character */ end= strcend(cur_arg, '='); @@ -622,12 +632,12 @@ static char *check_struct_option(char *cur_arg, char *key_name) uint len= (uint) (ptr - cur_arg); set_if_smaller(len, FN_REFLEN-1); strmake(key_name, cur_arg, len); - return ++ptr; + DBUG_RETURN(++ptr); } else { key_name[0]= 0; - return cur_arg; + DBUG_RETURN(cur_arg); } } @@ -643,18 +653,20 @@ static char *check_struct_option(char *cur_arg, char *key_name) static my_bool get_bool_argument(const struct my_option *opts, const char *argument) { + DBUG_ENTER("get_bool_argument"); + if (!my_strcasecmp(&my_charset_latin1, argument, "true") || !my_strcasecmp(&my_charset_latin1, argument, "on") || !my_strcasecmp(&my_charset_latin1, argument, "1")) - return 1; + DBUG_RETURN(1); else if (!my_strcasecmp(&my_charset_latin1, argument, "false") || !my_strcasecmp(&my_charset_latin1, argument, "off") || !my_strcasecmp(&my_charset_latin1, argument, "0")) - return 0; + DBUG_RETURN(0); my_getopt_error_reporter(WARNING_LEVEL, "option '%s': boolean value '%s' wasn't recognized. Set to OFF.", opts->name, argument); - return 0; + DBUG_RETURN(0); } /* @@ -668,6 +680,7 @@ static int setval(const struct my_option *opts, void *value, char *argument, my_bool set_maximum_value) { int err= 0, res= 0; + DBUG_ENTER("setval"); if (!argument) argument= enabled_my_option; @@ -679,7 +692,7 @@ static int setval(const struct my_option *opts, void *value, char *argument, my_getopt_error_reporter(ERROR_LEVEL, "%s: Maximum value of '%s' cannot be set", my_progname, opts->name); - return EXIT_NO_PTR_TO_VARIABLE; + DBUG_RETURN(EXIT_NO_PTR_TO_VARIABLE); } switch ((opts->var_type & GET_TYPE_MASK)) { @@ -708,15 +721,13 @@ static int setval(const struct my_option *opts, void *value, char *argument, *((double*) value)= getopt_double(argument, opts, &err); break; case GET_STR: - if (argument == enabled_my_option) - break; /* string options don't use this default of "1" */ - *((char**) value)= argument; + /* If no argument or --enable-string-option, set string to "" */ + *((char**) value)= argument == enabled_my_option ? (char*) "" : argument; break; case GET_STR_ALLOC: - if (argument == enabled_my_option) - break; /* string options don't use this default of "1" */ my_free(*((char**) value)); - if (!(*((char**) value)= my_strdup(argument, MYF(MY_WME)))) + if (!(*((char**) value)= my_strdup(argument == enabled_my_option ? "" : + argument, MYF(MY_WME)))) { res= EXIT_OUT_OF_MEMORY; goto ret; @@ -791,13 +802,13 @@ static int setval(const struct my_option *opts, void *value, char *argument, goto ret; }; } - return 0; + DBUG_RETURN(0); ret: my_getopt_error_reporter(ERROR_LEVEL, "%s: Error while setting value '%s' to '%s'", my_progname, argument, opts->name); - return res; + DBUG_RETURN(res); } @@ -829,7 +840,7 @@ static int findopt(char *optpat, uint length, { uint count; const struct my_option *opt= *opt_res; - my_bool is_prefix= FALSE; + DBUG_ENTER("findopt"); for (count= 0; opt->name; opt++) { @@ -837,15 +848,13 @@ static int findopt(char *optpat, uint length, { (*opt_res)= opt; if (!opt->name[length]) /* Exact match */ - return 1; + DBUG_RETURN(1); if (!count) { /* We only need to know one prev */ count= 1; *ffname= opt->name; - if (opt->name[length]) - is_prefix= TRUE; } else if (strcmp(*ffname, opt->name)) { @@ -857,13 +866,7 @@ static int findopt(char *optpat, uint length, } } } - if (is_prefix && count == 1) - my_getopt_error_reporter(WARNING_LEVEL, - "Using unique option prefix %.*s instead of %s " - "is deprecated and will be removed in a future " - "release. Please use the full name instead.", - length, optpat, *ffname); - return count; + DBUG_RETURN(count); } @@ -878,12 +881,14 @@ my_bool getopt_compare_strings(register const char *s, register const char *t, uint length) { char const *end= s + length; + DBUG_ENTER("getopt_compare_strings"); + for (;s != end ; s++, t++) { if ((*s != '-' ? *s : '_') != (*t != '-' ? *t : '_')) - return 1; + DBUG_RETURN(1); } - return 0; + DBUG_RETURN(0); } /* @@ -897,6 +902,8 @@ static longlong eval_num_suffix(char *argument, int *error, char *option_name) { char *endchar; longlong num; + DBUG_ENTER("eval_num_suffix"); + *error= 0; errno= 0; @@ -906,7 +913,7 @@ static longlong eval_num_suffix(char *argument, int *error, char *option_name) my_getopt_error_reporter(ERROR_LEVEL, "Incorrect integer value: '%s'", argument); *error= 1; - return 0; + DBUG_RETURN(0); } if (*endchar == 'k' || *endchar == 'K') num*= 1024L; @@ -920,12 +927,12 @@ static longlong eval_num_suffix(char *argument, int *error, char *option_name) "Unknown suffix '%c' used for variable '%s' (value '%s')\n", *endchar, option_name, argument); *error= 1; - return 0; + DBUG_RETURN(0); } - return num; + DBUG_RETURN(num); } -/* +/* function: getopt_ll Evaluates and returns the value that user gave as an argument @@ -956,6 +963,7 @@ longlong getopt_ll_limit_value(longlong num, const struct my_option *optp, my_bool adjusted= FALSE; char buf1[255], buf2[255]; ulonglong block_size= (optp->block_size ? (ulonglong) optp->block_size : 1L); + DBUG_ENTER("getopt_ll_limit_value"); if (num > 0 && ((ulonglong) num > (ulonglong) optp->max_value) && optp->max_value) /* if max value is not set -> no upper limit */ @@ -1002,7 +1010,7 @@ longlong getopt_ll_limit_value(longlong num, const struct my_option *optp, my_getopt_error_reporter(WARNING_LEVEL, "option '%s': signed value %s adjusted to %s", optp->name, llstr(old, buf1), llstr(num, buf2)); - return num; + DBUG_RETURN(num); } /* @@ -1025,6 +1033,7 @@ ulonglong getopt_ull_limit_value(ulonglong num, const struct my_option *optp, my_bool adjusted= FALSE; ulonglong old= num; char buf1[255], buf2[255]; + DBUG_ENTER("getopt_ull_limit_value"); if ((ulonglong) num > (ulonglong) optp->max_value && optp->max_value) /* if max value is not set -> no upper limit */ @@ -1075,7 +1084,7 @@ ulonglong getopt_ull_limit_value(ulonglong num, const struct my_option *optp, "option '%s': unsigned value %s adjusted to %s", optp->name, ullstr(old, buf1), ullstr(num, buf2)); - return num; + DBUG_RETURN(num); } double getopt_double_limit_value(double num, const struct my_option *optp, @@ -1084,6 +1093,7 @@ double getopt_double_limit_value(double num, const struct my_option *optp, my_bool adjusted= FALSE; double old= num; double min, max; + DBUG_ENTER("getopt_double_limit_value"); max= getopt_ulonglong2double(optp->max_value); min= getopt_ulonglong2double(optp->min_value); @@ -1103,7 +1113,7 @@ double getopt_double_limit_value(double num, const struct my_option *optp, my_getopt_error_reporter(WARNING_LEVEL, "option '%s': value %g adjusted to %g", optp->name, old, num); - return num; + DBUG_RETURN(num); } /* @@ -1139,8 +1149,8 @@ static double getopt_double(char *arg, const struct my_option *optp, int *err) SYNOPSIS init_one_value() - option Option to initialize - value Pointer to variable + option Option to initialize + value Pointer to variable */ static void init_one_value(const struct my_option *option, void *variable, @@ -1237,11 +1247,13 @@ static void fini_one_value(const struct my_option *option, void *variable, void my_cleanup_options(const struct my_option *options) { + DBUG_ENTER("my_cleanup_options"); init_variables(options, fini_one_value); + DBUG_VOID_RETURN; } -/* +/* initialize all variables to their default values SYNOPSIS @@ -1281,6 +1293,7 @@ static void init_variables(const struct my_option *options, static uint print_name(const struct my_option *optp) { const char *s= optp->name; + for (;*s;s++) putchar(*s == '_' ? '-' : *s); return s - optp->name; @@ -1297,6 +1310,7 @@ void my_print_help(const struct my_option *options) uint col, name_space= 22, comment_space= 57; const char *line_end; const struct my_option *optp; + DBUG_ENTER("my_print_help"); for (optp= options; optp->name; optp++) { @@ -1371,6 +1385,7 @@ void my_print_help(const struct my_option *options) } } } + DBUG_VOID_RETURN; } @@ -1386,6 +1401,7 @@ void my_print_variables(const struct my_option *options) ulonglong llvalue; char buff[255]; const struct my_option *optp; + DBUG_ENTER("my_print_variables"); for (optp= options; optp->name; optp++) { @@ -1445,7 +1461,7 @@ void my_print_variables(const struct my_option *options) printf("%d\n", *((int*) value)); break; case GET_UINT: - printf("%d\n", *((uint*) value)); + printf("%u\n", *((uint*) value)); break; case GET_LONG: printf("%ld\n", *((long*) value)); @@ -1457,7 +1473,7 @@ void my_print_variables(const struct my_option *options) printf("%s\n", llstr(*((longlong*) value), buff)); break; case GET_ULL: - longlong2str(*((ulonglong*) value), buff, 10); + longlong10_to_str(*((ulonglong*) value), buff, 10); printf("%s\n", buff); break; case GET_DOUBLE: @@ -1472,4 +1488,5 @@ void my_print_variables(const struct my_option *options) } } } + DBUG_VOID_RETURN; } diff --git a/mysys/my_getsystime.c b/mysys/my_getsystime.c index 498bd3eb19a..1cedeb21d6e 100644 --- a/mysys/my_getsystime.c +++ b/mysys/my_getsystime.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2004, 2011, Oracle and/or its affiliates. + Copyright (c) 2009-2011 Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -13,170 +14,120 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ -/* get time since epoc in 100 nanosec units */ -/* thus to get the current time we should use the system function - with the highest possible resolution */ - -/* - TODO: in functions my_micro_time() and my_micro_time_and_time() there - exists some common code that should be merged into a function. -*/ #include "mysys_priv.h" #include "my_static.h" -/** - Get high-resolution time. +#ifdef __WIN__ +#define OFFSET_TO_EPOC 116444736000000000LL +static ulonglong query_performance_frequency; +#endif +#ifdef HAVE_LINUX_UNISTD_H +#include <linux/unistd.h> +#endif + +/* For CYGWIN */ +#if !defined(CLOCK_THREAD_CPUTIME_ID) && defined(CLOCK_THREAD_CPUTIME) +#define CLOCK_THREAD_CPUTIME_ID CLOCK_THREAD_CPUTIME +#endif + +/* + return number of nanoseconds since unspecified (but always the same) + point in the past - @remark For windows platforms we need the frequency value of - the CPU. This is initialized in my_init.c through - QueryPerformanceFrequency(). If the Windows platform - doesn't support QueryPerformanceFrequency(), zero is - returned. + NOTE: + Thus to get the current time we should use the system function + with the highest possible resolution - @retval current high-resolution time. + The value is not anchored to any specific point in time (e.g. epoch) nor + is it subject to resetting or drifting by way of adjtime() or settimeofday(), + and thus it is *NOT* appropriate for getting the current timestamp. It can be + used for calculating time intervals, though. */ -ulonglong my_getsystime() +ulonglong my_interval_timer() { #ifdef HAVE_CLOCK_GETTIME struct timespec tp; - clock_gettime(CLOCK_REALTIME, &tp); - return (ulonglong)tp.tv_sec*10000000+(ulonglong)tp.tv_nsec/100; -#elif defined(_WIN32) + clock_gettime(CLOCK_MONOTONIC, &tp); + return tp.tv_sec*1000000000ULL+tp.tv_nsec; +#elif defined(HAVE_GETHRTIME) + return gethrtime(); +#elif defined(__WIN__) LARGE_INTEGER t_cnt; if (query_performance_frequency) { QueryPerformanceCounter(&t_cnt); - return ((t_cnt.QuadPart / query_performance_frequency * 10000000) + - ((t_cnt.QuadPart % query_performance_frequency) * 10000000 / - query_performance_frequency) + query_performance_offset); + return (t_cnt.QuadPart / query_performance_frequency * 1000000000ULL) + + ((t_cnt.QuadPart % query_performance_frequency) * 1000000000ULL / + query_performance_frequency); + } + else + { + ulonglong newtime; + GetSystemTimeAsFileTime((FILETIME*)&newtime); + return newtime*100ULL; } - return 0; #else /* TODO: check for other possibilities for hi-res timestamping */ struct timeval tv; gettimeofday(&tv,NULL); - return (ulonglong)tv.tv_sec*10000000+(ulonglong)tv.tv_usec*10; + return tv.tv_sec*1000000000ULL+tv.tv_usec*1000ULL; #endif } -/** - Return current time. - - @param flags If MY_WME is set, write error if time call fails. - - @retval current time. -*/ - -time_t my_time(myf flags) -{ - time_t t; - /* The following loop is here beacuse time() may fail on some systems */ - while ((t= time(0)) == (time_t) -1) - { - if (flags & MY_WME) - fprintf(stderr, "%s: Warning: time() call failed\n", my_progname); - } - return t; -} - - -/** - Return time in microseconds. - - @remark This function is to be used to measure performance in - micro seconds. As it's not defined whats the start time - for the clock, this function us only useful to measure - time between two moments. +/* Return current time in HRTIME_RESOLUTION (microseconds) since epoch */ - @retval Value in microseconds from some undefined point in time. -*/ - -ulonglong my_micro_time() +my_hrtime_t my_hrtime() { -#ifdef _WIN32 + my_hrtime_t hrtime; +#if defined(__WIN__) ulonglong newtime; GetSystemTimeAsFileTime((FILETIME*)&newtime); - return (newtime/10); + newtime -= OFFSET_TO_EPOC; + hrtime.val= newtime/10; +#elif defined(HAVE_CLOCK_GETTIME) + struct timespec tp; + clock_gettime(CLOCK_REALTIME, &tp); + hrtime.val= tp.tv_sec*1000000ULL+tp.tv_nsec/1000ULL; #else - ulonglong newtime; struct timeval t; - /* - The following loop is here because gettimeofday may fail on some systems - */ - while (gettimeofday(&t, NULL) != 0) - {} - newtime= (ulonglong)t.tv_sec * 1000000 + t.tv_usec; - return newtime; + /* The following loop is here because gettimeofday may fail */ + while (gettimeofday(&t, NULL) != 0) {} + hrtime.val= t.tv_sec*1000000ULL + t.tv_usec; #endif + return hrtime; } -/** - Return time in seconds and timer in microseconds (not different start!) - - @param time_arg Will be set to seconds since epoch. - - @remark This function is to be useful when we need both the time and - microtime. For example in MySQL this is used to get the query - time start of a query and to measure the time of a query (for - the slow query log) - - @remark The time source is the same as for my_micro_time(), meaning - that time values returned by both functions can be intermixed - in meaningful ways (i.e. for comparison purposes). - - @retval Value in microseconds from some undefined point in time. -*/ - -/* Difference between GetSystemTimeAsFileTime() and now() */ -#define OFFSET_TO_EPOCH 116444736000000000ULL - -ulonglong my_micro_time_and_time(time_t *time_arg) +void my_time_init() { -#ifdef _WIN32 - ulonglong newtime; - GetSystemTimeAsFileTime((FILETIME*)&newtime); - *time_arg= (time_t) ((newtime - OFFSET_TO_EPOCH) / 10000000); - return (newtime/10); -#else - ulonglong newtime; - struct timeval t; - /* - The following loop is here because gettimeofday may fail on some systems - */ - while (gettimeofday(&t, NULL) != 0) - {} - *time_arg= t.tv_sec; - newtime= (ulonglong)t.tv_sec * 1000000 + t.tv_usec; - return newtime; +#ifdef __WIN__ + compile_time_assert(sizeof(LARGE_INTEGER) == + sizeof(query_performance_frequency)); + if (QueryPerformanceFrequency((LARGE_INTEGER *)&query_performance_frequency) == 0) + query_performance_frequency= 0; #endif } -/** - Returns current time. - - @param microtime Value from very recent my_micro_time(). - - @remark This function returns the current time. The microtime argument - is only used if my_micro_time() uses a function that can safely - be converted to the current time. - - @retval current time. +/* + Return cpu time in 1/10th on a microsecond (1e-7 s) */ -time_t my_time_possible_from_micro(ulonglong microtime __attribute__((unused))) +ulonglong my_getcputime() { -#ifdef _WIN32 - time_t t; - while ((t= time(0)) == (time_t) -1) - {} - return t; -#else - return (time_t) (microtime / 1000000); -#endif +#ifdef CLOCK_THREAD_CPUTIME_ID + struct timespec tp; + if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp)) + return 0; + return (ulonglong)tp.tv_sec*10000000+(ulonglong)tp.tv_nsec/100; +#elif defined(__NR_clock_gettime) + struct timespec tp; + if (syscall(__NR_clock_gettime, CLOCK_THREAD_CPUTIME_ID, &tp)) + return 0; + return (ulonglong)tp.tv_sec*10000000+(ulonglong)tp.tv_nsec/100; +#endif /* CLOCK_THREAD_CPUTIME_ID */ + return 0; } - diff --git a/mysys/my_getwd.c b/mysys/my_getwd.c index 3552081b85f..444ed4273b5 100644 --- a/mysys/my_getwd.c +++ b/mysys/my_getwd.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2000, 2010, Oracle and/or its affiliates This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/mysys/my_handler_errors.h b/mysys/my_handler_errors.h index 1c2f02f26cb..bcc1ba9c476 100644 --- a/mysys/my_handler_errors.h +++ b/mysys/my_handler_errors.h @@ -1,7 +1,7 @@ #ifndef MYSYS_MY_HANDLER_ERRORS_INCLUDED #define MYSYS_MY_HANDLER_ERRORS_INCLUDED -/* Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2008, 2012, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -49,7 +49,7 @@ static const char *handler_error_messages[]= "Table is crashed and last repair failed", "Table was marked as crashed and should be repaired", "Lock timed out; Retry transaction", - "Lock table is full; Restart program with a larger locktable", + "Lock table is full; Restart program with a larger lock table", "Updates are not allowed under a read only transactions", "Lock deadlock; Retry transaction", "Foreign key constraint is incorrectly formed", @@ -63,32 +63,31 @@ static const char *handler_error_messages[]= "Unexpected null pointer found when using spatial index", "The table changed in storage engine", "There's no partition in table for the given value", - "Row-based binlogging of row failed", + "Row-based binary logging of row failed", "Index needed in foreign key constraint", - "Upholding foreign key constraints would lead to a duplicate key error in " - "some other table", + "Upholding foreign key constraints would lead to a duplicate key error in some other table", "Table needs to be upgraded before it can be used", "Table is read only", "Failed to get next auto increment value", "Failed to set row auto increment value", "Unknown (generic) error from engine", - "Record is the same", + "Record was not update. Original values was same as new values", "It is not possible to log this statement", "The event was corrupt, leading to illegal data being read", "The table is of a new format not supported by this version", - "The event could not be processed no other hanlder error happened", - "Got a fatal error during initialzaction of handler", - "File to short; Expected more data in file", + "The event could not be processed. No other handler error happened", + "Got a fatal error during initialization of handler", + "File too short; Expected more data in file", "Read page with wrong checksum", "Too many active concurrent transactions", "Index column length exceeds limit", "Index corrupted", "Undo record too big", - "Table is being used in foreign key check" + "Table is being used in foreign key check", + "Row is not visible by the current transaction", + "Operation was interrupted by end user (probably kill command?)", + "Disk full", + "Incompatible key or row definition between the MariaDB .frm file and the information in the storage engine. You have to dump and restore the table to fix this" }; -extern void my_handler_error_register(void); -extern void my_handler_error_unregister(void); - - #endif /* MYSYS_MY_HANDLER_ERRORS_INCLUDED */ diff --git a/mysys/my_init.c b/mysys/my_init.c index ad14b17496e..193c8281577 100644 --- a/mysys/my_init.c +++ b/mysys/my_init.c @@ -1,4 +1,6 @@ -/* Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2000, 2012, Oracle and/or its affiliates + Copyright (c) 2009, 2011, Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,12 +35,15 @@ static my_bool win32_init_tcp_ip(); #define my_win_init() #endif +extern pthread_key(struct st_my_thread_var*, THR_KEY_mysys); + #define SCALE_SEC 100 #define SCALE_USEC 10000 my_bool my_init_done= 0; uint mysys_usage_id= 0; /* Incremented for each my_init() */ -ulong my_thread_stack_size= 65536; + +ulonglong my_thread_stack_size= (sizeof(void*) <= 4)? 65536: ((256-16)*1024); static ulong atoi_octal(const char *str) { @@ -74,6 +79,7 @@ my_bool my_init(void) mysys_usage_id++; my_umask= 0660; /* Default umask for new files */ my_umask_dir= 0700; /* Default umask for new directories */ + my_global_flags= 0; /* Default creation of new files */ if ((str= getenv("UMASK")) != 0) @@ -88,16 +94,15 @@ my_bool my_init(void) instrumented_stdin.m_psi= NULL; /* not yet instrumented */ mysql_stdin= & instrumented_stdin; - if (my_thread_global_init()) - return 1; + my_progname_short= "unknown"; + if (my_progname) + my_progname_short= my_progname + dirname_length(my_progname); -#if defined(SAFE_MUTEX) - safe_mutex_global_init(); /* Must be called early */ -#endif + /* Initalize our mutex handling */ + my_mutex_init(); -#if defined(MY_PTHREAD_FASTMUTEX) && !defined(SAFE_MUTEX) - fastmutex_global_init(); /* Must be called early */ -#endif + if (my_thread_global_init()) + return 1; /* $HOME is needed early to parse configuration files located in ~/ */ if ((home_dir= getenv("HOME")) != 0) @@ -106,6 +111,7 @@ my_bool my_init(void) { DBUG_ENTER("my_init"); DBUG_PROCESS((char*) (my_progname ? my_progname : "unknown")); + my_time_init(); my_win_init(); DBUG_PRINT("exit", ("home: '%s'", home_dir)); #ifdef __WIN__ @@ -164,7 +170,7 @@ void my_end(int infoflag) { #ifdef HAVE_GETRUSAGE struct rusage rus; -#ifdef HAVE_purify +#ifdef HAVE_valgrind /* Purify assumes that rus is uninitialized after getrusage call */ bzero((char*) &rus, sizeof(rus)); #endif @@ -197,13 +203,13 @@ Voluntary context switches %ld, Involuntary context switches %ld\n", #endif } - if (!(infoflag & MY_DONT_FREE_DBUG)) - { - DBUG_END(); /* Must be done before my_thread_end */ - } - my_thread_end(); my_thread_global_end(); + + if (!(infoflag & MY_DONT_FREE_DBUG)) + DBUG_END(); /* Must be done as late as possible */ + + my_mutex_end(); #if defined(SAFE_MUTEX) /* Check on destroying of mutexes. A few may be left that will get cleaned @@ -217,10 +223,19 @@ Voluntary context switches %ld, Involuntary context switches %ld\n", if (have_tcpip) WSACleanup(); #endif /* __WIN__ */ - + + /* At very last, delete mysys key, it is used everywhere including DBUG */ + pthread_key_delete(THR_KEY_mysys); my_init_done=0; } /* my_end */ +#ifndef DBUG_OFF +/* Dummy tag function for debugging */ + +void my_debug_put_break_here(void) +{ +} +#endif #ifdef __WIN__ @@ -240,6 +255,7 @@ void my_parameter_handler(const wchar_t * expression, const wchar_t * function, { DBUG_PRINT("my",("Expression: %s function: %s file: %s, line: %d", expression, function, file, line)); + __debugbreak(); } @@ -264,41 +280,13 @@ int handle_rtc_failure(int err_type, const char *file, int line, fprintf(stderr, " At %s:%d\n", file, line); va_end(args); (void) fflush(stderr); + __debugbreak(); return 0; /* Error is handled */ } #pragma runtime_checks("", restore) #endif -#define OFFSET_TO_EPOC ((__int64) 134774 * 24 * 60 * 60 * 1000 * 1000 * 10) -#define MS 10000000 - -static void win_init_time(void) -{ - /* The following is used by time functions */ - FILETIME ft; - LARGE_INTEGER li, t_cnt; - - DBUG_ASSERT(sizeof(LARGE_INTEGER) == sizeof(query_performance_frequency)); - - if (QueryPerformanceFrequency((LARGE_INTEGER *)&query_performance_frequency) == 0) - query_performance_frequency= 0; - else - { - GetSystemTimeAsFileTime(&ft); - li.LowPart= ft.dwLowDateTime; - li.HighPart= ft.dwHighDateTime; - query_performance_offset= li.QuadPart-OFFSET_TO_EPOC; - QueryPerformanceCounter(&t_cnt); - query_performance_offset-= (t_cnt.QuadPart / - query_performance_frequency * MS + - t_cnt.QuadPart % - query_performance_frequency * MS / - query_performance_frequency); - } -} - - /* Open HKEY_LOCAL_MACHINE\SOFTWARE\MySQL and set any strings found there as environment variables @@ -382,7 +370,6 @@ static void my_win_init(void) _tzset(); - win_init_time(); win_init_registry(); DBUG_VOID_RETURN; @@ -469,7 +456,7 @@ PSI_mutex_key key_BITMAP_mutex, key_IO_CACHE_append_buffer_lock, key_THR_LOCK_isam, key_THR_LOCK_lock, key_THR_LOCK_malloc, key_THR_LOCK_mutex, key_THR_LOCK_myisam, key_THR_LOCK_net, key_THR_LOCK_open, key_THR_LOCK_threads, - key_TMPDIR_mutex, key_THR_LOCK_myisam_mmap; + key_TMPDIR_mutex, key_THR_LOCK_myisam_mmap, key_LOCK_uuid_generator; static PSI_mutex_info all_mysys_mutexes[]= { @@ -496,12 +483,13 @@ static PSI_mutex_info all_mysys_mutexes[]= { &key_THR_LOCK_open, "THR_LOCK_open", PSI_FLAG_GLOBAL}, { &key_THR_LOCK_threads, "THR_LOCK_threads", PSI_FLAG_GLOBAL}, { &key_TMPDIR_mutex, "TMPDIR_mutex", PSI_FLAG_GLOBAL}, - { &key_THR_LOCK_myisam_mmap, "THR_LOCK_myisam_mmap", PSI_FLAG_GLOBAL} + { &key_THR_LOCK_myisam_mmap, "THR_LOCK_myisam_mmap", PSI_FLAG_GLOBAL}, + { &key_LOCK_uuid_generator, "LOCK_uuid_generator", PSI_FLAG_GLOBAL } }; PSI_cond_key key_COND_alarm, key_IO_CACHE_SHARE_cond, key_IO_CACHE_SHARE_cond_writer, key_my_thread_var_suspend, - key_THR_COND_threads; + key_THR_COND_threads, key_WT_RESOURCE_cond; static PSI_cond_info all_mysys_conds[]= { @@ -509,7 +497,15 @@ static PSI_cond_info all_mysys_conds[]= { &key_IO_CACHE_SHARE_cond, "IO_CACHE_SHARE::cond", 0}, { &key_IO_CACHE_SHARE_cond_writer, "IO_CACHE_SHARE::cond_writer", 0}, { &key_my_thread_var_suspend, "my_thread_var::suspend", 0}, - { &key_THR_COND_threads, "THR_COND_threads", 0} + { &key_THR_COND_threads, "THR_COND_threads", PSI_FLAG_GLOBAL}, + { &key_WT_RESOURCE_cond, "WT_RESOURCE::cond", 0} +}; + +PSI_rwlock_key key_SAFEHASH_mutex; + +static PSI_rwlock_info all_mysys_rwlocks[]= +{ + { &key_SAFEHASH_mutex, "SAFE_HASH::mutex", 0} }; #ifdef USE_ALARM_THREAD @@ -549,6 +545,9 @@ void my_init_mysys_psi_keys() count= sizeof(all_mysys_conds)/sizeof(all_mysys_conds[0]); PSI_server->register_cond(category, all_mysys_conds, count); + count= sizeof(all_mysys_rwlocks)/sizeof(all_mysys_rwlocks[0]); + PSI_server->register_rwlock(category, all_mysys_rwlocks, count); + #ifdef USE_ALARM_THREAD count= sizeof(all_mysys_threads)/sizeof(all_mysys_threads[0]); PSI_server->register_thread(category, all_mysys_threads, count); diff --git a/mysys/my_lock.c b/mysys/my_lock.c index b1e347b875b..54ec3838b58 100644 --- a/mysys/my_lock.c +++ b/mysys/my_lock.c @@ -146,13 +146,13 @@ int my_lock(File fd, int locktype, my_off_t start, my_off_t length, DBUG_ENTER("my_lock"); DBUG_PRINT("my",("fd: %d Op: %d start: %ld Length: %ld MyFlags: %d", fd,locktype,(long) start,(long) length,MyFlags)); - if (my_disable_locking) + if (my_disable_locking && ! (MyFlags & MY_FORCE_LOCK)) DBUG_RETURN(0); #if defined(_WIN32) { int timeout_sec; - if (MyFlags & MY_DONT_WAIT) + if (MyFlags & MY_NO_WAIT) timeout_sec= 0; else timeout_sec= WIN_LOCK_INFINITE; @@ -170,10 +170,16 @@ int my_lock(File fd, int locktype, my_off_t start, my_off_t length, lock.l_start= (off_t) start; lock.l_len= (off_t) length; - if (MyFlags & MY_DONT_WAIT) + if (MyFlags & (MY_NO_WAIT | MY_SHORT_WAIT)) { if (fcntl(fd,F_SETLK,&lock) != -1) /* Check if we can lock */ - DBUG_RETURN(0); /* Ok, file locked */ + DBUG_RETURN(0); /* Ok, file locked */ + if (MyFlags & MY_NO_WAIT) + { + my_errno= (errno == EACCES) ? EAGAIN : errno ? errno : -1; + DBUG_RETURN(-1); + } + DBUG_PRINT("info",("Was locked, trying with alarm")); ALARM_INIT; while ((value=fcntl(fd,F_SETLKW,&lock)) && ! ALARM_TEST && @@ -206,7 +212,7 @@ int my_lock(File fd, int locktype, my_off_t start, my_off_t length, if (lockf(fd,locktype,length) != -1) DBUG_RETURN(0); #endif /* HAVE_FCNTL */ -#endif /* HAVE_LOCKING */ +#endif /* _WIN32 */ /* We got an error. We don't want EACCES errors */ my_errno=(errno == EACCES) ? EAGAIN : errno ? errno : -1; diff --git a/mysys/my_malloc.c b/mysys/my_malloc.c index 43dcb4d663e..5264210a6b4 100644 --- a/mysys/my_malloc.c +++ b/mysys/my_malloc.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates + Copyright (c) 2009, 2014, SkySQL Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,15 +31,17 @@ void *my_malloc(size_t size, myf my_flags) void* point; DBUG_ENTER("my_malloc"); DBUG_PRINT("my",("size: %lu my_flags: %d", (ulong) size, my_flags)); + if (!(my_flags & (MY_WME | MY_FAE))) + my_flags|= my_global_flags; /* Safety */ if (!size) size=1; - point= malloc(size); + point= sf_malloc(size); DBUG_EXECUTE_IF("simulate_out_of_memory", { - free(point); + my_free(point); point= NULL; }); DBUG_EXECUTE_IF("simulate_persistent_out_of_memory", @@ -87,24 +90,7 @@ void *my_realloc(void *oldpoint, size_t size, myf my_flags) DBUG_ASSERT(size > 0); if (!oldpoint && (my_flags & MY_ALLOW_ZERO_PTR)) DBUG_RETURN(my_malloc(size, my_flags)); -#ifdef USE_HALLOC - if (!(point = malloc(size))) - { - if (my_flags & MY_FREE_ON_ERROR) - my_free(oldpoint); - if (my_flags & MY_HOLD_ON_ERROR) - DBUG_RETURN(oldpoint); - my_errno=errno; - if (my_flags & MY_FAE+MY_WME) - my_error(EE_OUTOFMEMORY, MYF(ME_BELL + ME_WAITTANG + ME_FATALERROR),size); - } - else - { - memcpy(point,oldpoint,size); - free(oldpoint); - } -#else - if ((point= realloc(oldpoint, size)) == NULL) + if ((point= sf_realloc(oldpoint, size)) == NULL) { if (my_flags & MY_FREE_ON_ERROR) my_free(oldpoint); @@ -114,7 +100,6 @@ void *my_realloc(void *oldpoint, size_t size, myf my_flags) if (my_flags & (MY_FAE+MY_WME)) my_error(EE_OUTOFMEMORY, MYF(ME_BELL + ME_WAITTANG + ME_FATALERROR), size); } -#endif DBUG_PRINT("exit",("ptr: %p", point)); DBUG_RETURN(point); } @@ -131,7 +116,7 @@ void my_free(void *ptr) { DBUG_ENTER("my_free"); DBUG_PRINT("my",("ptr: %p", ptr)); - free(ptr); + sf_free(ptr); DBUG_VOID_RETURN; } @@ -139,9 +124,11 @@ void my_free(void *ptr) void *my_memdup(const void *from, size_t length, myf my_flags) { void *ptr; + DBUG_ENTER("my_memdup"); + if ((ptr= my_malloc(length,my_flags)) != 0) memcpy(ptr, from, length); - return ptr; + DBUG_RETURN(ptr); } @@ -149,20 +136,24 @@ char *my_strdup(const char *from, myf my_flags) { char *ptr; size_t length= strlen(from)+1; + DBUG_ENTER("my_strdup"); + if ((ptr= (char*) my_malloc(length, my_flags))) memcpy(ptr, from, length); - return ptr; + DBUG_RETURN(ptr); } char *my_strndup(const char *from, size_t length, myf my_flags) { char *ptr; + DBUG_ENTER("my_strndup"); + if ((ptr= (char*) my_malloc(length+1, my_flags))) { memcpy(ptr, from, length); ptr[length]= 0; } - return ptr; + DBUG_RETURN(ptr); } diff --git a/mysys/my_new.cc b/mysys/my_new.cc index 377d9be22a8..4266452da43 100644 --- a/mysys/my_new.cc +++ b/mysys/my_new.cc @@ -16,33 +16,55 @@ /* This is a replacement of new/delete operators to be used when compiling - with gcc 3.0.x to avoid including libstdc++ + with gcc 3.0.x to avoid including libstdc++ + + It is also used to make all memory allocations to go through + my_malloc/my_free wrappers (for debugging/safemalloc and accounting) */ #include "mysys_priv.h" +#include <new> #ifdef USE_MYSYS_NEW void *operator new (size_t sz) { - return (void *) malloc (sz ? sz : 1); + return (void *) my_malloc (sz ? sz : 1, MYF(0)); } void *operator new[] (size_t sz) { - return (void *) malloc (sz ? sz : 1); + return (void *) my_malloc (sz ? sz : 1, MYF(0)); +} + +void* operator new(std::size_t sz, const std::nothrow_t&) throw() +{ + return (void *) my_malloc (sz ? sz : 1, MYF(0)); +} + +void* operator new[](std::size_t sz, const std::nothrow_t&) throw() +{ + return (void *) my_malloc (sz ? sz : 1, MYF(0)); } void operator delete (void *ptr) { - if (ptr) - free(ptr); + my_free(ptr); } void operator delete[] (void *ptr) throw () { - if (ptr) - free(ptr); + my_free(ptr); +} + +void operator delete(void* ptr, const std::nothrow_t&) throw() +{ + my_free(ptr); +} + +void operator delete[](void* ptr, const std::nothrow_t&) throw() +{ + my_free(ptr); } C_MODE_START @@ -54,6 +76,11 @@ int __cxa_pure_virtual() } C_MODE_END - +#else +/* + Define a dummy symbol, just to avoid compiler/linker warnings + about compiling an essentially empty file. +*/ +int my_new_cc_symbol; #endif /* USE_MYSYS_NEW */ diff --git a/mysys/my_open.c b/mysys/my_open.c index c707839b1a9..645d6709358 100644 --- a/mysys/my_open.c +++ b/mysys/my_open.c @@ -41,6 +41,8 @@ File my_open(const char *FileName, int Flags, myf MyFlags) DBUG_ENTER("my_open"); DBUG_PRINT("my",("Name: '%s' Flags: %d MyFlags: %d", FileName, Flags, MyFlags)); + if (!(MyFlags & (MY_WME | MY_FAE | MY_FFNF))) + MyFlags|= my_global_flags; #if defined(_WIN32) fd= my_win_open(FileName, Flags); #elif !defined(NO_OPEN_3) @@ -49,8 +51,9 @@ File my_open(const char *FileName, int Flags, myf MyFlags) fd = open((char *) FileName, Flags); #endif - DBUG_RETURN(my_register_filename(fd, FileName, FILE_BY_OPEN, - EE_FILENOTFOUND, MyFlags)); + fd= my_register_filename(fd, FileName, FILE_BY_OPEN, + EE_FILENOTFOUND, MyFlags); + DBUG_RETURN(fd); } /* my_open */ @@ -69,6 +72,8 @@ int my_close(File fd, myf MyFlags) int err; DBUG_ENTER("my_close"); DBUG_PRINT("my",("fd: %d MyFlags: %d",fd, MyFlags)); + if (!(MyFlags & (MY_WME | MY_FAE))) + MyFlags|= my_global_flags; mysql_mutex_lock(&THR_LOCK_open); #ifndef _WIN32 @@ -84,14 +89,12 @@ int my_close(File fd, myf MyFlags) DBUG_PRINT("error",("Got error %d on close",err)); my_errno=errno; if (MyFlags & (MY_FAE | MY_WME)) - my_error(EE_BADCLOSE, MYF(ME_BELL+ME_WAITTANG),my_filename(fd),errno); + my_error(EE_BADCLOSE, MYF(ME_BELL | ME_WAITTANG | (MyFlags & (ME_JUST_INFO | ME_NOREFRESH))), + my_filename(fd),errno); } if ((uint) fd < my_file_limit && my_file_info[fd].type != UNOPEN) { my_free(my_file_info[fd].name); -#if !defined(HAVE_PREAD) && !defined(_WIN32) - mysql_mutex_destroy(&my_file_info[fd].mutex); -#endif my_file_info[fd].type = UNOPEN; } my_file_opened--; @@ -125,12 +128,8 @@ File my_register_filename(File fd, const char *FileName, enum file_type { if ((uint) fd >= my_file_limit) { -#if !defined(HAVE_PREAD) - my_errno= EMFILE; -#else thread_safe_increment(my_file_opened,&THR_LOCK_open); DBUG_RETURN(fd); /* safeguard */ -#endif } else { @@ -140,10 +139,6 @@ File my_register_filename(File fd, const char *FileName, enum file_type my_file_opened++; my_file_total_opened++; my_file_info[fd].type = type_of_file; -#if !defined(HAVE_PREAD) && !defined(_WIN32) - mysql_mutex_init(key_my_file_info_mutex, &my_file_info[fd].mutex, - MY_MUTEX_INIT_FAST); -#endif mysql_mutex_unlock(&THR_LOCK_open); DBUG_PRINT("exit",("fd: %d",fd)); DBUG_RETURN(fd); @@ -161,8 +156,8 @@ File my_register_filename(File fd, const char *FileName, enum file_type { if (my_errno == EMFILE) error_message_number= EE_OUT_OF_FILERESOURCES; - DBUG_PRINT("error",("print err: %d",error_message_number)); - my_error(error_message_number, MYF(ME_BELL+ME_WAITTANG), + my_error(error_message_number, + MYF(ME_BELL | ME_WAITTANG | (MyFlags & (ME_JUST_INFO | ME_NOREFRESH))), FileName, my_errno); } DBUG_RETURN(-1); diff --git a/mysys/my_port.c b/mysys/my_port.c new file mode 100644 index 00000000000..96dbe10b1bd --- /dev/null +++ b/mysys/my_port.c @@ -0,0 +1,40 @@ +/* Copyright (C) 2002 MySQL AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; version 2 + of the License. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + MA 02111-1307, USA */ + +/* + Small functions to make code portable +*/ + +#include "mysys_priv.h" + +#ifdef _AIX + +/* + On AIX, at least with gcc 3.1, the expression + '(double) (ulonglong) var' doesn't always work for big unsigned + integers like '18446744073709551615'. The end result is that the + high bit is simply dropped. (probably bug in gcc optimizations) + Handling the conversion in a sub function seems to work. + + It doesn't work to make this function inline. +*/ + +double my_ulonglong2double(unsigned long long nr) +{ + return (double) nr; +} +#endif /* _AIX */ diff --git a/mysys/my_pread.c b/mysys/my_pread.c index 310291d019f..51b4c5f5599 100644 --- a/mysys/my_pread.c +++ b/mysys/my_pread.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. + Copyright (c) 2011, Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,12 +19,10 @@ #include "my_base.h" #include <m_string.h> #include <errno.h> -#if defined (HAVE_PREAD) && !defined(_WIN32) +#ifndef _WIN32 #include <unistd.h> #endif - - /* Read a chunk of bytes from a file from a given position @@ -50,56 +49,48 @@ size_t my_pread(File Filedes, uchar *Buffer, size_t Count, my_off_t offset, { size_t readbytes; int error= 0; -#if !defined (HAVE_PREAD) && !defined (_WIN32) - int save_errno; -#endif + DBUG_ENTER("my_pread"); + DBUG_PRINT("my",("fd: %d Seek: %llu Buffer: %p Count: %lu MyFlags: %d", Filedes, (ulonglong)offset, Buffer, (ulong)Count, MyFlags)); + + if (!(MyFlags & (MY_WME | MY_FAE | MY_FNABP))) + MyFlags|= my_global_flags; + for (;;) { errno= 0; /* Linux, Windows don't reset this on EOF/success */ -#if !defined (HAVE_PREAD) && !defined (_WIN32) - mysql_mutex_lock(&my_file_info[Filedes].mutex); - readbytes= (uint) -1; - error= (lseek(Filedes, offset, MY_SEEK_SET) == (my_off_t) -1 || - (readbytes= read(Filedes, Buffer, Count)) != Count); - save_errno= errno; - mysql_mutex_unlock(&my_file_info[Filedes].mutex); - if (error) - errno= save_errno; -#else -#if defined(_WIN32) +#ifdef _WIN32 readbytes= my_win_pread(Filedes, Buffer, Count, offset); -#else +#else readbytes= pread(Filedes, Buffer, Count, offset); #endif - error= (readbytes != Count); -#endif - if(error) + error = (readbytes != Count); + + if (error) { my_errno= errno ? errno : -1; if (errno == 0 || (readbytes != (size_t) -1 && - (MyFlags & (MY_NABP | MY_FNABP)))) - my_errno= HA_ERR_FILE_TOO_SHORT; - + (MyFlags & (MY_NABP | MY_FNABP)))) + my_errno= HA_ERR_FILE_TOO_SHORT; DBUG_PRINT("warning",("Read only %d bytes off %u from %d, errno: %d", (int) readbytes, (uint) Count,Filedes,my_errno)); - if ((readbytes == 0 || readbytes == (size_t) -1) && errno == EINTR) { DBUG_PRINT("debug", ("my_pread() was interrupted and returned %d", (int) readbytes)); continue; /* Interrupted */ } - if (MyFlags & (MY_WME | MY_FAE | MY_FNABP)) { - if (readbytes == (size_t) -1) - my_error(EE_READ, MYF(ME_BELL+ME_WAITTANG), - my_filename(Filedes),my_errno); - else if (MyFlags & (MY_NABP | MY_FNABP)) - my_error(EE_EOFERR, MYF(ME_BELL+ME_WAITTANG), + if (readbytes == (size_t) -1) + my_error(EE_READ, + MYF(ME_BELL | ME_WAITTANG | (MyFlags & (ME_JUST_INFO | ME_NOREFRESH))), + my_filename(Filedes),my_errno); + else if (MyFlags & (MY_NABP | MY_FNABP)) + my_error(EE_EOFERR, + MYF(ME_BELL | ME_WAITTANG | (MyFlags & (ME_JUST_INFO | ME_NOREFRESH))), my_filename(Filedes),my_errno); } if (readbytes == (size_t) -1 || (MyFlags & (MY_FNABP | MY_NABP))) @@ -133,50 +124,40 @@ size_t my_pread(File Filedes, uchar *Buffer, size_t Count, my_off_t offset, # Number of bytes read */ -size_t my_pwrite(File Filedes, const uchar *Buffer, size_t Count, +size_t my_pwrite(int Filedes, const uchar *Buffer, size_t Count, my_off_t offset, myf MyFlags) { size_t writtenbytes, written; uint errors; - DBUG_ENTER("my_pwrite"); DBUG_PRINT("my",("fd: %d Seek: %llu Buffer: %p Count: %lu MyFlags: %d", - Filedes, offset, Buffer, (ulong)Count, MyFlags)); + Filedes, (ulonglong)offset, Buffer, (ulong)Count, MyFlags)); errors= 0; written= 0; + if (!(MyFlags & (MY_WME | MY_FAE | MY_FNABP))) + MyFlags|= my_global_flags; for (;;) { -#if !defined (HAVE_PREAD) && !defined (_WIN32) - int error; - writtenbytes= (size_t) -1; - mysql_mutex_lock(&my_file_info[Filedes].mutex); - error= (lseek(Filedes, offset, MY_SEEK_SET) != (my_off_t) -1 && - (writtenbytes= write(Filedes, Buffer, Count)) == Count); - mysql_mutex_unlock(&my_file_info[Filedes].mutex); - if (error) - break; -#elif defined (_WIN32) - writtenbytes= my_win_pwrite(Filedes, Buffer, Count, offset); +#ifdef _WIN32 + writtenbytes= my_win_pwrite(Filedes, Buffer, Count,offset); #else writtenbytes= pwrite(Filedes, Buffer, Count, offset); #endif - if(writtenbytes == Count) + if (writtenbytes == Count) break; my_errno= errno; if (writtenbytes != (size_t) -1) - { - written+= writtenbytes; - Buffer+= writtenbytes; - Count-= writtenbytes; - offset+= writtenbytes; + { /* Safegueard */ + written+=writtenbytes; + Buffer+=writtenbytes; + Count-=writtenbytes; + offset+=writtenbytes; } DBUG_PRINT("error",("Write only %u bytes", (uint) writtenbytes)); #ifndef NO_BACKGROUND - if (my_thread_var->abort) MyFlags&= ~ MY_WAIT_IF_FULL; /* End if aborted by user */ - if ((my_errno == ENOSPC || my_errno == EDQUOT) && (MyFlags & MY_WAIT_IF_FULL)) { @@ -187,20 +168,19 @@ size_t my_pwrite(File Filedes, const uchar *Buffer, size_t Count, if ((writtenbytes && writtenbytes != (size_t) -1) || my_errno == EINTR) continue; /* Retry */ #endif + + /* Don't give a warning if it's ok that we only write part of the data */ if (MyFlags & (MY_NABP | MY_FNABP)) { if (MyFlags & (MY_WME | MY_FAE | MY_FNABP)) - { - my_error(EE_WRITE, MYF(ME_BELL | ME_WAITTANG), + my_error(EE_WRITE, MYF(ME_BELL | ME_WAITTANG | (MyFlags & (ME_JUST_INFO | ME_NOREFRESH))), my_filename(Filedes),my_errno); - } - DBUG_RETURN(MY_FILE_ERROR); /* Error on read */ + DBUG_RETURN(MY_FILE_ERROR); /* Error on write */ } - else - break; /* Return bytes written */ + break; /* Return bytes written */ } DBUG_EXECUTE_IF("check", my_seek(Filedes, -1, SEEK_SET, MYF(0));); if (MyFlags & (MY_NABP | MY_FNABP)) - DBUG_RETURN(0); /* Want only errors */ - DBUG_RETURN(writtenbytes+written); /* purecov: inspected */ + DBUG_RETURN(0); /* Want only errors */ + DBUG_RETURN(writtenbytes+written); /* purecov: inspected */ } /* my_pwrite */ diff --git a/mysys/my_pthread.c b/mysys/my_pthread.c index 81d1f2af53f..20e53a23ab5 100644 --- a/mysys/my_pthread.c +++ b/mysys/my_pthread.c @@ -30,20 +30,6 @@ uint thd_lib_detected= 0; -/* To allow use of pthread_getspecific with two arguments */ - -#ifdef HAVE_NONPOSIX_PTHREAD_GETSPECIFIC -#undef pthread_getspecific - -void *my_pthread_getspecific_imp(pthread_key_t key) -{ - void *value; - if (pthread_getspecific(key,(void *) &value)) - return 0; - return value; -} -#endif - /* Some functions for RTS threads, AIX, Siemens Unix and UnixWare 7 (and DEC OSF/1 3.2 too) @@ -51,18 +37,6 @@ void *my_pthread_getspecific_imp(pthread_key_t key) int my_pthread_create_detached=1; -#if defined(HAVE_NONPOSIX_SIGWAIT) || defined(HAVE_DEC_3_2_THREADS) - -int my_sigwait(const sigset_t *set,int *sig) -{ - int signal=sigwait((sigset_t*) set); - if (signal < 0) - return errno; - *sig=signal; - return 0; -} -#endif - /* localtime_r for SCO 3.2V4.2 */ #if !defined(HAVE_LOCALTIME_R) || !defined(HAVE_GMTIME_R) @@ -117,7 +91,7 @@ struct tm *gmtime_r(const time_t *clock, struct tm *res) ** Author: Gary Wisniewski <garyw@spidereye.com.au>, much modified by Monty ****************************************************************************/ -#if !defined(HAVE_SIGWAIT) && !defined(sigwait) && !defined(__WIN__) && !defined(HAVE_rts_threads) && !defined(HAVE_NONPOSIX_SIGWAIT) && !defined(HAVE_DEC_3_2_THREADS) +#if !defined(HAVE_SIGWAIT) && !defined(sigwait) && !defined(__WIN__) && !defined(HAVE_rts_threads) #if !defined(DONT_USE_SIGSUSPEND) @@ -269,13 +243,7 @@ void *sigwait_thread(void *set_arg) for (;;) { /* Wait for signals */ -#ifdef HAVE_NOT_BROKEN_SELECT - fd_set fd; - FD_ZERO(&fd); - select(0,&fd,0,0,0); -#else sleep(1); /* Because of broken BSDI */ -#endif } } @@ -352,37 +320,6 @@ int sigwait(sigset_t *setp, int *sigp) #undef pthread_attr_getstacksize /***************************************************************************** -** Patches for AIX and DEC OSF/1 3.2 -*****************************************************************************/ - -#if defined(HAVE_NONPOSIX_PTHREAD_MUTEX_INIT) - -#include <netdb.h> - -int my_pthread_mutex_init(pthread_mutex_t *mp, const pthread_mutexattr_t *attr) -{ - int error; - if (!attr) - error=pthread_mutex_init(mp,pthread_mutexattr_default); - else - error=pthread_mutex_init(mp,*attr); - return error; -} - -int my_pthread_cond_init(pthread_cond_t *mp, const pthread_condattr_t *attr) -{ - int error; - if (!attr) - error=pthread_cond_init(mp,pthread_condattr_default); - else - error=pthread_cond_init(mp,*attr); - return error; -} - -#endif - - -/***************************************************************************** Patches for HPUX We need these because the pthread_mutex.. code returns -1 on error, instead of the error code. @@ -392,7 +329,7 @@ int my_pthread_cond_init(pthread_cond_t *mp, const pthread_condattr_t *attr) this has to be added here. ****************************************************************************/ -#if defined(HPUX10) || defined(HAVE_BROKEN_PTHREAD_COND_TIMEDWAIT) +#if defined(HPUX10) int my_pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, struct timespec *abstime) diff --git a/mysys/my_read.c b/mysys/my_read.c index 680e13b8391..883a1c5fdc7 100644 --- a/mysys/my_read.c +++ b/mysys/my_read.c @@ -40,6 +40,8 @@ size_t my_read(File Filedes, uchar *Buffer, size_t Count, myf MyFlags) DBUG_PRINT("my",("fd: %d Buffer: %p Count: %lu MyFlags: %d", Filedes, Buffer, (ulong) Count, MyFlags)); save_count= Count; + if (!(MyFlags & (MY_WME | MY_FAE | MY_FNABP))) + MyFlags|= my_global_flags; for (;;) { @@ -77,10 +79,12 @@ size_t my_read(File Filedes, uchar *Buffer, size_t Count, myf MyFlags) if (MyFlags & (MY_WME | MY_FAE | MY_FNABP)) { if (readbytes == (size_t) -1) - my_error(EE_READ, MYF(ME_BELL+ME_WAITTANG), + my_error(EE_READ, + MYF(ME_BELL | ME_WAITTANG | (MyFlags & (ME_JUST_INFO | ME_NOREFRESH))), my_filename(Filedes),my_errno); else if (MyFlags & (MY_NABP | MY_FNABP)) - my_error(EE_EOFERR, MYF(ME_BELL+ME_WAITTANG), + my_error(EE_EOFERR, + MYF(ME_BELL | ME_WAITTANG | (MyFlags & (ME_JUST_INFO | ME_NOREFRESH))), my_filename(Filedes),my_errno); } if (readbytes == (size_t) -1 || diff --git a/mysys/my_redel.c b/mysys/my_redel.c index a47df8265c8..b285bb25e2e 100644 --- a/mysys/my_redel.c +++ b/mysys/my_redel.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2000, 2010, Oracle and/or its affiliates This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -14,6 +15,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mysys_priv.h" +#include "mysys_err.h" #include <my_dir.h> #include <m_string.h> #include "mysys_err.h" @@ -39,7 +41,8 @@ struct utimbuf { #define REDEL_EXT ".BAK" -int my_redel(const char *org_name, const char *tmp_name, myf MyFlags) +int my_redel(const char *org_name, const char *tmp_name, + time_t backup_time_stamp, myf MyFlags) { int error=1; DBUG_ENTER("my_redel"); @@ -50,16 +53,12 @@ int my_redel(const char *org_name, const char *tmp_name, myf MyFlags) goto end; if (MyFlags & MY_REDEL_MAKE_BACKUP) { - char name_buff[FN_REFLEN+20]; - char ext[20]; - ext[0]='-'; - get_date(ext+1,2+4,(time_t) 0); - strmov(strend(ext),REDEL_EXT); - if (my_rename(org_name, fn_format(name_buff, org_name, "", ext, 2), - MyFlags)) + char name_buff[FN_REFLEN + MY_BACKUP_NAME_EXTRA_LENGTH]; + my_create_backup_name(name_buff, org_name, backup_time_stamp); + if (my_rename(org_name, name_buff, MyFlags)) goto end; } - else if (my_delete_allow_opened(org_name, MyFlags)) + else if (my_delete(org_name, MyFlags)) goto end; if (my_rename(tmp_name,org_name,MyFlags)) goto end; @@ -70,20 +69,30 @@ end: } /* my_redel */ - /* Copy stat from one file to another */ - /* Return -1 if can't get stat, 1 if wrong type of file */ +/** + Copy stat from one file to another + @fn my_copystat() + @param from Copy stat from this file + @param to Copy stat to this file + @param MyFlags Flags: + MY_WME Give error if something goes wrong + MY_FAE Abort operation if something goes wrong + If MY_FAE is not given, we don't return -1 for + errors from chown (which normally require root + privilege) + + @return 0 ok + -1 if can't get stat, + 1 if wrong type of file +*/ int my_copystat(const char *from, const char *to, int MyFlags) { - struct stat statbuf; + MY_STAT statbuf; - if (stat(from, &statbuf)) - { - my_errno=errno; - if (MyFlags & (MY_FAE+MY_WME)) - my_error(EE_STAT, MYF(ME_BELL+ME_WAITTANG),from,errno); + if (my_stat(from, &statbuf, MyFlags) == NULL) return -1; /* Can't get stat on input file */ - } + if ((statbuf.st_mode & S_IFMT) != S_IFREG) return 1; @@ -106,9 +115,10 @@ int my_copystat(const char *from, const char *to, int MyFlags) if (chown(to, statbuf.st_uid, statbuf.st_gid)) { my_errno= errno; - if (MyFlags & (MY_FAE+MY_WME)) + if (MyFlags & MY_WME) my_error(EE_CHANGE_OWNERSHIP, MYF(ME_BELL+ME_WAITTANG), from, errno); - return -1; + if (MyFlags & MY_FAE) + return -1; } #endif /* !__WIN__ */ @@ -122,3 +132,23 @@ int my_copystat(const char *from, const char *to, int MyFlags) return 0; } /* my_copystat */ + + +/** + Create a backup file name. + @fn my_create_backup_name() + @param to Store new file name here + @param from Original name + + @info + The backup name is made by adding -YYMMDDHHMMSS.BAK to the file name +*/ + +void my_create_backup_name(char *to, const char *from, time_t backup_start) +{ + char ext[MY_BACKUP_NAME_EXTRA_LENGTH+1]; + ext[0]='-'; + get_date(ext+1, GETDATE_SHORT_DATE | GETDATE_HHMMSSTIME, backup_start); + strmov(strend(ext),REDEL_EXT); + strmov(strmov(to, from), ext); +} diff --git a/mysys/my_rename.c b/mysys/my_rename.c index 6704d7c87d0..b89bc4c8fbd 100644 --- a/mysys/my_rename.c +++ b/mysys/my_rename.c @@ -27,22 +27,6 @@ int my_rename(const char *from, const char *to, myf MyFlags) DBUG_ENTER("my_rename"); DBUG_PRINT("my",("from %s to %s MyFlags %d", from, to, MyFlags)); -#if defined(HAVE_FILE_VERSIONS) - { /* Check that there isn't a old file */ - int save_errno; - MY_STAT my_stat_result; - save_errno=my_errno; - if (my_stat(to,&my_stat_result,MYF(0))) - { - my_errno=EEXIST; - error= -1; - if (MyFlags & MY_FAE+MY_WME) - my_error(EE_LINK, MYF(ME_BELL+ME_WAITTANG),from,to,my_errno); - DBUG_RETURN(error); - } - my_errno=save_errno; - } -#endif #if defined(HAVE_RENAME) #if defined(__WIN__) /* diff --git a/mysys/my_rnd.c b/mysys/my_rnd.c new file mode 100644 index 00000000000..178bcd9c539 --- /dev/null +++ b/mysys/my_rnd.c @@ -0,0 +1,55 @@ +/* Copyright (C) 2007 MySQL AB & Michael Widenius + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "mysys_priv.h" +#include <m_string.h> + +/* + Initialize random generator + + NOTES + MySQL's password checks depends on this, so don't do any changes + that changes the random numbers that are generated! +*/ + +void my_rnd_init(struct my_rnd_struct *rand_st, ulong seed1, ulong seed2) +{ +#ifdef HAVE_valgrind + bzero((char*) rand_st,sizeof(*rand_st)); /* Avoid UMC varnings */ +#endif + rand_st->max_value= 0x3FFFFFFFL; + rand_st->max_value_dbl=(double) rand_st->max_value; + rand_st->seed1=seed1%rand_st->max_value ; + rand_st->seed2=seed2%rand_st->max_value; +} + + +/* + Generate random number. + + SYNOPSIS + my_rnd() + rand_st INOUT Structure used for number generation + + RETURN VALUE + generated pseudo random number +*/ + +double my_rnd(struct my_rnd_struct *rand_st) +{ + rand_st->seed1=(rand_st->seed1*3+rand_st->seed2) % rand_st->max_value; + rand_st->seed2=(rand_st->seed1+rand_st->seed2+33) % rand_st->max_value; + return (((double) rand_st->seed1)/rand_st->max_value_dbl); +} diff --git a/mysys/my_safehash.c b/mysys/my_safehash.c new file mode 100644 index 00000000000..1417b8ea94e --- /dev/null +++ b/mysys/my_safehash.c @@ -0,0 +1,297 @@ +/* Copyright (C) 2003-2007 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + Handling of multiple key caches + + The idea is to have a thread safe hash on the table name, + with a default key cache value that is returned if the table name is not in + the cache. +*/ + +#include "mysys_priv.h" +#include <m_string.h> +#include "my_safehash.h" + +/***************************************************************************** + General functions to handle SAFE_HASH objects. + + A SAFE_HASH object is used to store the hash, the mutex and default value + needed by the rest of the key cache code. + This is a separate struct to make it easy to later reuse the code for other + purposes + + All entries are linked in a list to allow us to traverse all elements + and delete selected ones. (HASH doesn't allow any easy ways to do this). +*****************************************************************************/ + + +/* + Free a SAFE_HASH_ENTRY + + SYNOPSIS + safe_hash_entry_free() + entry The entry which should be freed + + NOTE + This function is called by the hash object on delete +*/ + +static void safe_hash_entry_free(SAFE_HASH_ENTRY *entry) +{ + DBUG_ENTER("safe_hash_entry_free"); + my_free(entry); + DBUG_VOID_RETURN; +} + + +/* + Get key and length for a SAFE_HASH_ENTRY + + SYNOPSIS + safe_hash_entry_get() + entry The entry for which the key should be returned + length Length of the key + + RETURN + # reference on the key +*/ + +static uchar *safe_hash_entry_get(SAFE_HASH_ENTRY *entry, size_t *length, + my_bool not_used __attribute__((unused))) +{ + *length= entry->length; + return (uchar*) entry->key; +} + + +/* + Init a SAFE_HASH object + + SYNOPSIS + safe_hash_init() + hash safe_hash handler + elements Expected max number of elements + default_value default value + + NOTES + In case of error we set hash->default_value to 0 to allow one to call + safe_hash_free on an object that couldn't be initialized. + + RETURN + 0 OK + 1 error +*/ + +my_bool safe_hash_init(SAFE_HASH *hash, uint elements, + uchar *default_value) +{ + DBUG_ENTER("safe_hash_init"); + if (my_hash_init(&hash->hash, &my_charset_bin, elements, + 0, 0, (my_hash_get_key) safe_hash_entry_get, + (void (*)(void*)) safe_hash_entry_free, 0)) + { + hash->default_value= 0; + DBUG_RETURN(1); + } + mysql_rwlock_init(key_SAFEHASH_mutex, &hash->mutex); + hash->default_value= default_value; + hash->root= 0; + DBUG_RETURN(0); +} + + +/* + Free a SAFE_HASH object + + SYNOPSIS + safe_hash_free() + hash Hash handle + + NOTES + This is safe to call on any object that has been sent to safe_hash_init() +*/ + +void safe_hash_free(SAFE_HASH *hash) +{ + /* + Test if safe_hash_init succeeded. This will also guard us against multiple + free calls. + */ + if (hash->default_value) + { + my_hash_free(&hash->hash); + mysql_rwlock_destroy(&hash->mutex); + hash->default_value=0; + } +} + + +/* + Return the value stored for a key or default value if no key + + SYNOPSIS + safe_hash_search() + hash Hash handle + key key (path to table etc..) + length Length of key + def Default value of data + + RETURN + # data associated with the key of default value if data was not found +*/ + +uchar *safe_hash_search(SAFE_HASH *hash, const uchar *key, uint length, + uchar *def) +{ + uchar *result; + DBUG_ENTER("safe_hash_search"); + mysql_rwlock_rdlock(&hash->mutex); + result= my_hash_search(&hash->hash, key, length); + mysql_rwlock_unlock(&hash->mutex); + if (!result) + result= def; + else + result= ((SAFE_HASH_ENTRY*) result)->data; + DBUG_PRINT("exit",("data: 0x%lx", (long) result)); + DBUG_RETURN(result); +} + + +/* + Associate a key with some data + + SYNOPSIS + safe_hash_set() + hash Hash handle + key key (path to table etc..) + length Length of key + data data to to associate with the data + + NOTES + This can be used both to insert a new entry and change an existing + entry. + If one associates a key with the default key cache, the key is deleted + + RETURN + 0 OK + 1 error (Can only be EOM). In this case my_message() is called. +*/ + +my_bool safe_hash_set(SAFE_HASH *hash, const uchar *key, uint length, + uchar *data) +{ + SAFE_HASH_ENTRY *entry; + my_bool error= 0; + DBUG_ENTER("safe_hash_set"); + DBUG_PRINT("enter",("key: %.*s data: 0x%lx", length, key, (long) data)); + + mysql_rwlock_wrlock(&hash->mutex); + entry= (SAFE_HASH_ENTRY*) my_hash_search(&hash->hash, key, length); + + if (data == hash->default_value) + { + /* + The key is to be associated with the default entry. In this case + we can just delete the entry (if it existed) from the hash as a + search will return the default entry + */ + if (!entry) /* nothing to do */ + goto end; + /* unlink entry from list */ + if ((*entry->prev= entry->next)) + entry->next->prev= entry->prev; + my_hash_delete(&hash->hash, (uchar*) entry); + goto end; + } + if (entry) + { + /* Entry existed; Just change the pointer to point at the new data */ + entry->data= data; + } + else + { + if (!(entry= (SAFE_HASH_ENTRY *) my_malloc(sizeof(*entry) + length, + MYF(MY_WME)))) + { + error= 1; + goto end; + } + entry->key= (uchar*) (entry +1); + memcpy((char*) entry->key, (char*) key, length); + entry->length= length; + entry->data= data; + /* Link entry to list */ + if ((entry->next= hash->root)) + entry->next->prev= &entry->next; + entry->prev= &hash->root; + hash->root= entry; + if (my_hash_insert(&hash->hash, (uchar*) entry)) + { + /* This can only happen if hash got out of memory */ + my_free(entry); + error= 1; + goto end; + } + } + +end: + mysql_rwlock_unlock(&hash->mutex); + DBUG_RETURN(error); +} + + +/* + Change all entries with one data value to another data value + + SYNOPSIS + safe_hash_change() + hash Hash handle + old_data Old data + new_data Change all 'old_data' to this + + NOTES + We use the linked list to traverse all elements in the hash as + this allows us to delete elements in the case where 'new_data' is the + default value. +*/ + +void safe_hash_change(SAFE_HASH *hash, uchar *old_data, uchar *new_data) +{ + SAFE_HASH_ENTRY *entry, *next; + DBUG_ENTER("safe_hash_change"); + + mysql_rwlock_wrlock(&hash->mutex); + + for (entry= hash->root ; entry ; entry= next) + { + next= entry->next; + if (entry->data == old_data) + { + if (new_data == hash->default_value) + { + if ((*entry->prev= entry->next)) + entry->next->prev= entry->prev; + my_hash_delete(&hash->hash, (uchar*) entry); + } + else + entry->data= new_data; + } + } + + mysql_rwlock_unlock(&hash->mutex); + DBUG_VOID_RETURN; +} diff --git a/mysys/my_safehash.h b/mysys/my_safehash.h new file mode 100644 index 00000000000..e52fee68b57 --- /dev/null +++ b/mysys/my_safehash.h @@ -0,0 +1,56 @@ +/* Copyright (C) 2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + Handling of multiple key caches + + The idea is to have a thread safe hash on the table name, + with a default key cache value that is returned if the table name is not in + the cache. +*/ + +#include <hash.h> + +/* + Struct to store a key and pointer to object +*/ + +typedef struct st_safe_hash_entry +{ + uchar *key; + uint length; + uchar *data; + struct st_safe_hash_entry *next, **prev; +} SAFE_HASH_ENTRY; + + +typedef struct st_safe_hash_with_default +{ + mysql_rwlock_t mutex; + HASH hash; + uchar *default_value; + SAFE_HASH_ENTRY *root; +} SAFE_HASH; + + +my_bool safe_hash_init(SAFE_HASH *hash, uint elements, + uchar *default_value); +void safe_hash_free(SAFE_HASH *hash); +uchar *safe_hash_search(SAFE_HASH *hash, const uchar *key, uint length, + uchar *def); +my_bool safe_hash_set(SAFE_HASH *hash, const uchar *key, uint length, + uchar *data); +void safe_hash_change(SAFE_HASH *hash, uchar *old_data, uchar *new_data); diff --git a/mysys/my_seek.c b/mysys/my_seek.c index 7d2c753ef3c..1033d7ac806 100644 --- a/mysys/my_seek.c +++ b/mysys/my_seek.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2000, 2010, Oracle and/or its affiliates This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -55,7 +56,7 @@ my_off_t my_seek(File fd, my_off_t pos, int whence, myf MyFlags) Make sure we are using a valid file descriptor! */ DBUG_ASSERT(fd != -1); -#if defined (_WIN32) +#ifdef _WIN32 newpos= my_win_lseek(fd, pos, whence); #else newpos= lseek(fd, pos, whence); diff --git a/mysys/my_sleep.c b/mysys/my_sleep.c index 52ae6af7dee..77eb466aee8 100644 --- a/mysys/my_sleep.c +++ b/mysys/my_sleep.c @@ -28,7 +28,7 @@ void my_sleep(ulong m_seconds) t.tv_usec= m_seconds % 1000000L; select(0,0,0,0,&t); /* sleep */ #else - uint sec= (uint) (m_seconds / 1000000L); + uint sec= (uint) ((m_seconds + 999999L) / 1000000L); ulong start= (ulong) time((time_t*) 0); while ((ulong) time((time_t*) 0) < start+sec); #endif diff --git a/mysys/my_static.c b/mysys/my_static.c index a6259466072..fdc01b1248b 100644 --- a/mysys/my_static.c +++ b/mysys/my_static.c @@ -1,4 +1,6 @@ -/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2000-2008 MySQL AB, 2009 Sun Microsystems, Inc. + Use is subject to license terms. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,13 +28,16 @@ my_bool timed_mutexes= 0; /* from my_init */ char * home_dir=0; -const char *my_progname=0; +char *mysql_data_home= (char*) "."; +const char *my_progname= NULL, *my_progname_short= NULL; char curr_dir[FN_REFLEN]= {0}, home_dir_buff[FN_REFLEN]= {0}; ulong my_stream_opened=0,my_file_opened=0, my_tmp_file_created=0; ulong my_file_total_opened= 0; int my_umask=0664, my_umask_dir=0777; +myf my_global_flags= 0; +my_bool my_assert_on_error= 0; struct st_my_file_info my_file_info_default[MY_NFILE]; uint my_file_limit= MY_NFILE; struct st_my_file_info *my_file_info= my_file_info_default; @@ -86,22 +91,11 @@ static const char *proc_info_dummy(void *a __attribute__((unused)), /* this is to be able to call set_thd_proc_info from the C code */ const char *(*proc_info_hook)(void *, const char *, const char *, const char *, const unsigned int)= proc_info_dummy; - -#if defined(ENABLED_DEBUG_SYNC) -/** - Global pointer to be set if callback function is defined - (e.g. in mysqld). See sql/debug_sync.cc. -*/ -void (*debug_sync_C_callback_ptr)(const char *, size_t); -#endif /* defined(ENABLED_DEBUG_SYNC) */ - -#ifdef __WIN__ -/* from my_getsystime.c */ -ulonglong query_performance_frequency, query_performance_offset; -#endif +void (*debug_sync_C_callback_ptr)(MYSQL_THD, const char *, size_t)= 0; /* How to disable options */ my_bool my_disable_locking=0; +my_bool my_disable_sync=0; my_bool my_disable_async_io=0; my_bool my_disable_flush_key_blocks=0; my_bool my_disable_symlinks=0; diff --git a/mysys/my_static.h b/mysys/my_static.h index 52142dd2ec2..a44a4b26c3d 100644 --- a/mysys/my_static.h +++ b/mysys/my_static.h @@ -43,8 +43,6 @@ extern uint my_once_extra; extern struct st_my_file_info my_file_info_default[MY_NFILE]; -extern ulonglong query_performance_frequency, query_performance_offset; - C_MODE_END #endif /* MYSYS_MY_STATIC_INCLUDED */ diff --git a/mysys/my_symlink.c b/mysys/my_symlink.c index cdb16a7422b..b0e910f7ba0 100644 --- a/mysys/my_symlink.c +++ b/mysys/my_symlink.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2001, 2011, Oracle and/or its affiliates This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -116,6 +117,9 @@ int my_is_symlink(const char *filename __attribute__((unused))) /* Resolve all symbolic links in path 'to' may be equal to 'filename' + + to is guaranteed to never set to a string longer than FN_REFLEN + (including the end \0) */ int my_realpath(char *to, const char *filename, myf MyFlags) @@ -128,7 +132,7 @@ int my_realpath(char *to, const char *filename, myf MyFlags) DBUG_PRINT("info",("executing realpath")); if ((ptr=realpath(filename,buff))) - strmake(to,ptr,FN_REFLEN-1); + strmake(to, ptr, FN_REFLEN-1); else { /* diff --git a/mysys/my_symlink2.c b/mysys/my_symlink2.c index dd94b32b53e..fcaf78ccff6 100644 --- a/mysys/my_symlink2.c +++ b/mysys/my_symlink2.c @@ -35,8 +35,8 @@ File my_create_with_symlink(const char *linkname, const char *filename, char abs_linkname[FN_REFLEN]; DBUG_ENTER("my_create_with_symlink"); DBUG_PRINT("enter", ("linkname: %s filename: %s", - linkname ? linkname : "(null)", - filename ? filename : "(null)")); + linkname ? linkname : "(NULL)", + filename ? filename : "(NULL)")); if (my_disable_symlinks) { diff --git a/mysys/my_sync.c b/mysys/my_sync.c index 65775f1ede5..88bcb685271 100644 --- a/mysys/my_sync.c +++ b/mysys/my_sync.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2003, 2011, Oracle and/or its affiliates This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,6 +18,9 @@ #include "mysys_err.h" #include <errno.h> + +ulong my_sync_count; /* Count number of sync calls */ + static void (*before_sync_wait)(void)= 0; static void (*after_sync_wait)(void)= 0; @@ -45,6 +49,13 @@ void thr_set_sync_wait_callback(void (*before_wait)(void), (which is correct behaviour, if we know that the other thread synced the file before closing) + MY_SYNC_FILESIZE is useful when syncing a file after it has been extended. + On Linux, fdatasync() on ext3/ext4 file systems does not properly flush + to disk the inode data required to preserve the added data across a crash + (this looks to be a bug). But when a file is extended, inode data will most + likely need flushing in any case, so passing MY_SYNC_FILESIZE as flags + is not likely to be any slower, and will be crash safe on Linux ext3/ext4. + RETURN 0 ok -1 error @@ -54,10 +65,19 @@ int my_sync(File fd, myf my_flags) { int res; DBUG_ENTER("my_sync"); + + DBUG_PRINT("my",("fd: %d my_flags: %d", fd, my_flags)); + + if (my_disable_sync) + DBUG_RETURN(0); + + statistic_increment(my_sync_count,&THR_LOCK_open); + DBUG_PRINT("my",("Fd: %d my_flags: %d", fd, my_flags)); if (before_sync_wait) (*before_sync_wait)(); + do { #if defined(F_FULLFSYNC) @@ -71,15 +91,24 @@ int my_sync(File fd, myf my_flags) DBUG_PRINT("info",("fcntl(F_FULLFSYNC) failed, falling back")); #endif #if defined(HAVE_FDATASYNC) && HAVE_DECL_FDATASYNC - res= fdatasync(fd); -#elif defined(HAVE_FSYNC) + if (!(my_flags & MY_SYNC_FILESIZE)) + res= fdatasync(fd); + else + { +#endif +#if defined(HAVE_FSYNC) res= fsync(fd); + if (res == -1 && errno == ENOLCK) + res= 0; /* Result Bug in Old FreeBSD */ #elif defined(_WIN32) res= my_win_fsync(fd); #else #error Cannot find a way to sync a file, durability in danger res= 0; /* No sync (strange OS) */ #endif +#if defined(HAVE_FDATASYNC) && HAVE_DECL_FDATASYNC + } +#endif } while (res == -1 && errno == EINTR); if (res) @@ -109,7 +138,6 @@ int my_sync(File fd, myf my_flags) static const char cur_dir_name[]= {FN_CURLIB, 0}; - /* Force directory information to disk. @@ -122,10 +150,10 @@ static const char cur_dir_name[]= {FN_CURLIB, 0}; 0 if ok, !=0 if error */ -#ifdef NEED_EXPLICIT_SYNC_DIR - -int my_sync_dir(const char *dir_name, myf my_flags) +int my_sync_dir(const char *dir_name __attribute__((unused)), + myf my_flags __attribute__((unused))) { +#ifdef NEED_EXPLICIT_SYNC_DIR File dir_fd; int res= 0; const char *correct_dir_name; @@ -147,19 +175,11 @@ int my_sync_dir(const char *dir_name, myf my_flags) else res= 1; DBUG_RETURN(res); -} - -#else /* NEED_EXPLICIT_SYNC_DIR */ - -int my_sync_dir(const char *dir_name __attribute__((unused)), - myf my_flags __attribute__((unused))) -{ +#else return 0; +#endif } -#endif /* NEED_EXPLICIT_SYNC_DIR */ - - /* Force directory information to disk. @@ -172,23 +192,15 @@ int my_sync_dir(const char *dir_name __attribute__((unused)), 0 if ok, !=0 if error */ -#ifdef NEED_EXPLICIT_SYNC_DIR - -int my_sync_dir_by_file(const char *file_name, myf my_flags) +int my_sync_dir_by_file(const char *file_name __attribute__((unused)), + myf my_flags __attribute__((unused))) { +#ifdef NEED_EXPLICIT_SYNC_DIR char dir_name[FN_REFLEN]; size_t dir_name_length; dirname_part(dir_name, file_name, &dir_name_length); return my_sync_dir(dir_name, my_flags); -} - -#else /* NEED_EXPLICIT_SYNC_DIR */ - -int my_sync_dir_by_file(const char *file_name __attribute__((unused)), - myf my_flags __attribute__((unused))) -{ +#else return 0; +#endif } - -#endif /* NEED_EXPLICIT_SYNC_DIR */ - diff --git a/mysys/my_thr_init.c b/mysys/my_thr_init.c index d213b67377b..b9ad379f4c4 100644 --- a/mysys/my_thr_init.c +++ b/mysys/my_thr_init.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2011 Oracle and/or its affiliates. + Copyright 2008-2011 Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -34,12 +35,6 @@ uint my_thread_end_wait_time= 5; #if !defined(HAVE_LOCALTIME_R) || !defined(HAVE_GMTIME_R) mysql_mutex_t LOCK_localtime_r; #endif -#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP -pthread_mutexattr_t my_fast_mutexattr; -#endif -#ifdef PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP -pthread_mutexattr_t my_errorcheck_mutexattr; -#endif #ifdef _MSC_VER static void install_sigabrt_handler(); #endif @@ -67,6 +62,77 @@ static uint get_thread_lib(void); static my_bool my_thread_global_init_done= 0; +/* + These are mutexes not used by safe_mutex or my_thr_init.c + + We want to free these earlier than other mutex so that safe_mutex + can detect if all mutex and memory is freed properly. +*/ + +static void my_thread_init_common_mutex(void) +{ + mysql_mutex_init(key_THR_LOCK_open, &THR_LOCK_open, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_THR_LOCK_lock, &THR_LOCK_lock, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_THR_LOCK_isam, &THR_LOCK_isam, MY_MUTEX_INIT_SLOW); + mysql_mutex_init(key_THR_LOCK_myisam, &THR_LOCK_myisam, MY_MUTEX_INIT_SLOW); + mysql_mutex_init(key_THR_LOCK_myisam_mmap, &THR_LOCK_myisam_mmap, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_THR_LOCK_heap, &THR_LOCK_heap, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_THR_LOCK_net, &THR_LOCK_net, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_THR_LOCK_charset, &THR_LOCK_charset, MY_MUTEX_INIT_FAST); +#if !defined(HAVE_LOCALTIME_R) || !defined(HAVE_GMTIME_R) + mysql_mutex_init(key_LOCK_localtime_r, &LOCK_localtime_r, MY_MUTEX_INIT_SLOW); +#endif +} + +void my_thread_destroy_common_mutex(void) +{ + mysql_mutex_destroy(&THR_LOCK_open); + mysql_mutex_destroy(&THR_LOCK_lock); + mysql_mutex_destroy(&THR_LOCK_isam); + mysql_mutex_destroy(&THR_LOCK_myisam); + mysql_mutex_destroy(&THR_LOCK_myisam_mmap); + mysql_mutex_destroy(&THR_LOCK_heap); + mysql_mutex_destroy(&THR_LOCK_net); + mysql_mutex_destroy(&THR_LOCK_charset); +#if !defined(HAVE_LOCALTIME_R) || !defined(HAVE_GMTIME_R) + mysql_mutex_destroy(&LOCK_localtime_r); +#endif +} + + +/* + These mutexes are used by my_thread_init() and after + my_thread_destroy_mutex() +*/ + +static void my_thread_init_internal_mutex(void) +{ + mysql_mutex_init(key_THR_LOCK_threads, &THR_LOCK_threads, MY_MUTEX_INIT_FAST); + mysql_mutex_init(key_THR_LOCK_malloc, &THR_LOCK_malloc, MY_MUTEX_INIT_FAST); + mysql_cond_init(key_THR_COND_threads, &THR_COND_threads, NULL); +} + + +void my_thread_destroy_internal_mutex(void) +{ + mysql_mutex_destroy(&THR_LOCK_threads); + mysql_mutex_destroy(&THR_LOCK_malloc); + mysql_cond_destroy(&THR_COND_threads); +} + +static void my_thread_init_thr_mutex(struct st_my_thread_var *var) +{ + mysql_mutex_init(key_my_thread_var_mutex, &var->mutex, MY_MUTEX_INIT_FAST); + mysql_cond_init(key_my_thread_var_suspend, &var->suspend, NULL); +} + +static void my_thread_destory_thr_mutex(struct st_my_thread_var *var) +{ + mysql_mutex_destroy(&var->mutex); + mysql_cond_destroy(&var->suspend); +} + + /** Re-initialize components initialized early with @c my_thread_global_init. Some mutexes were initialized before the instrumentation. @@ -85,41 +151,17 @@ void my_thread_global_reinit(void) my_init_mysys_psi_keys(); #endif - mysql_mutex_destroy(&THR_LOCK_isam); - mysql_mutex_init(key_THR_LOCK_isam, &THR_LOCK_isam, MY_MUTEX_INIT_SLOW); - - mysql_mutex_destroy(&THR_LOCK_heap); - mysql_mutex_init(key_THR_LOCK_heap, &THR_LOCK_heap, MY_MUTEX_INIT_FAST); - - mysql_mutex_destroy(&THR_LOCK_net); - mysql_mutex_init(key_THR_LOCK_net, &THR_LOCK_net, MY_MUTEX_INIT_FAST); - - mysql_mutex_destroy(&THR_LOCK_myisam); - mysql_mutex_init(key_THR_LOCK_myisam, &THR_LOCK_myisam, MY_MUTEX_INIT_SLOW); + my_thread_destroy_common_mutex(); + my_thread_init_common_mutex(); - mysql_mutex_destroy(&THR_LOCK_malloc); - mysql_mutex_init(key_THR_LOCK_malloc, &THR_LOCK_malloc, MY_MUTEX_INIT_FAST); - - mysql_mutex_destroy(&THR_LOCK_open); - mysql_mutex_init(key_THR_LOCK_open, &THR_LOCK_open, MY_MUTEX_INIT_FAST); - - mysql_mutex_destroy(&THR_LOCK_charset); - mysql_mutex_init(key_THR_LOCK_charset, &THR_LOCK_charset, MY_MUTEX_INIT_FAST); - - mysql_mutex_destroy(&THR_LOCK_threads); - mysql_mutex_init(key_THR_LOCK_threads, &THR_LOCK_threads, MY_MUTEX_INIT_FAST); - - mysql_cond_destroy(&THR_COND_threads); - mysql_cond_init(key_THR_COND_threads, &THR_COND_threads, NULL); + my_thread_destroy_internal_mutex(); + my_thread_init_internal_mutex(); tmp= my_pthread_getspecific(struct st_my_thread_var*, THR_KEY_mysys); DBUG_ASSERT(tmp); - mysql_mutex_destroy(&tmp->mutex); - mysql_mutex_init(key_my_thread_var_mutex, &tmp->mutex, MY_MUTEX_INIT_FAST); - - mysql_cond_destroy(&tmp->suspend); - mysql_cond_init(key_my_thread_var_suspend, &tmp->suspend, NULL); + my_thread_destory_thr_mutex(tmp); + my_thread_init_thr_mutex(tmp); } /* @@ -137,44 +179,20 @@ my_bool my_thread_global_init(void) { int pth_ret; + /* Normally this should never be called twice */ + DBUG_ASSERT(my_thread_global_init_done == 0); if (my_thread_global_init_done) return 0; my_thread_global_init_done= 1; -#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP - /* - Set mutex type to "fast" a.k.a "adaptive" - - In this case the thread may steal the mutex from some other thread - that is waiting for the same mutex. This will save us some - context switches but may cause a thread to 'starve forever' while - waiting for the mutex (not likely if the code within the mutex is - short). - */ - pthread_mutexattr_init(&my_fast_mutexattr); - pthread_mutexattr_settype(&my_fast_mutexattr, - PTHREAD_MUTEX_ADAPTIVE_NP); -#endif - -#ifdef PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP - /* - Set mutex type to "errorcheck" - */ - pthread_mutexattr_init(&my_errorcheck_mutexattr); - pthread_mutexattr_settype(&my_errorcheck_mutexattr, - PTHREAD_MUTEX_ERRORCHECK); -#endif - if ((pth_ret= pthread_key_create(&THR_KEY_mysys, NULL)) != 0) { fprintf(stderr, "Can't initialize threads: error %d\n", pth_ret); return 1; } - mysql_mutex_init(key_THR_LOCK_malloc, &THR_LOCK_malloc, MY_MUTEX_INIT_FAST); - mysql_mutex_init(key_THR_LOCK_open, &THR_LOCK_open, MY_MUTEX_INIT_FAST); - mysql_mutex_init(key_THR_LOCK_charset, &THR_LOCK_charset, MY_MUTEX_INIT_FAST); - mysql_mutex_init(key_THR_LOCK_threads, &THR_LOCK_threads, MY_MUTEX_INIT_FAST); + /* Mutex used by my_thread_init() and after my_thread_destroy_mutex() */ + my_thread_init_internal_mutex(); if (my_thread_init()) return 1; @@ -208,26 +226,16 @@ my_bool my_thread_global_init(void) } #endif /* TARGET_OS_LINUX */ - mysql_mutex_init(key_THR_LOCK_lock, &THR_LOCK_lock, MY_MUTEX_INIT_FAST); - mysql_mutex_init(key_THR_LOCK_isam, &THR_LOCK_isam, MY_MUTEX_INIT_SLOW); - mysql_mutex_init(key_THR_LOCK_myisam, &THR_LOCK_myisam, MY_MUTEX_INIT_SLOW); - mysql_mutex_init(key_THR_LOCK_myisam_mmap, &THR_LOCK_myisam_mmap, MY_MUTEX_INIT_FAST); - mysql_mutex_init(key_THR_LOCK_heap, &THR_LOCK_heap, MY_MUTEX_INIT_FAST); - mysql_mutex_init(key_THR_LOCK_net, &THR_LOCK_net, MY_MUTEX_INIT_FAST); - mysql_cond_init(key_THR_COND_threads, &THR_COND_threads, NULL); - -#if !defined(HAVE_LOCALTIME_R) || !defined(HAVE_GMTIME_R) - mysql_mutex_init(key_LOCK_localtime_r, &LOCK_localtime_r, MY_MUTEX_INIT_SLOW); -#endif - -#ifdef _MSC_VER - install_sigabrt_handler(); -#endif + my_thread_init_common_mutex(); return 0; } +/** + End the mysys thread system. Called when ending the last thread +*/ + void my_thread_global_end(void) { struct timespec abstime; @@ -258,31 +266,16 @@ void my_thread_global_end(void) } mysql_mutex_unlock(&THR_LOCK_threads); - pthread_key_delete(THR_KEY_mysys); -#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP - pthread_mutexattr_destroy(&my_fast_mutexattr); -#endif -#ifdef PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP - pthread_mutexattr_destroy(&my_errorcheck_mutexattr); -#endif - mysql_mutex_destroy(&THR_LOCK_malloc); - mysql_mutex_destroy(&THR_LOCK_open); - mysql_mutex_destroy(&THR_LOCK_lock); - mysql_mutex_destroy(&THR_LOCK_isam); - mysql_mutex_destroy(&THR_LOCK_myisam); - mysql_mutex_destroy(&THR_LOCK_myisam_mmap); - mysql_mutex_destroy(&THR_LOCK_heap); - mysql_mutex_destroy(&THR_LOCK_net); - mysql_mutex_destroy(&THR_LOCK_charset); + my_thread_destroy_common_mutex(); + + /* + Only destroy the mutex & conditions if we don't have other threads around + that could use them. + */ if (all_threads_killed) { - mysql_mutex_destroy(&THR_LOCK_threads); - mysql_cond_destroy(&THR_COND_threads); + my_thread_destroy_internal_mutex(); } -#if !defined(HAVE_LOCALTIME_R) || !defined(HAVE_GMTIME_R) - mysql_mutex_destroy(&LOCK_localtime_r); -#endif - my_thread_global_init_done= 0; } @@ -314,8 +307,7 @@ my_bool my_thread_init(void) my_bool error=0; #ifdef EXTRA_DEBUG_THREADS - fprintf(stderr,"my_thread_init(): thread_id: 0x%lx\n", - (ulong) pthread_self()); + fprintf(stderr,"my_thread_init(): pthread_self: %p\n", pthread_self()); #endif if (my_pthread_getspecific(struct st_my_thread_var *,THR_KEY_mysys)) @@ -338,8 +330,7 @@ my_bool my_thread_init(void) } pthread_setspecific(THR_KEY_mysys,tmp); tmp->pthread_self= pthread_self(); - mysql_mutex_init(key_my_thread_var_mutex, &tmp->mutex, MY_MUTEX_INIT_FAST); - mysql_cond_init(key_my_thread_var_suspend, &tmp->suspend, NULL); + my_thread_init_thr_mutex(tmp); tmp->stack_ends_here= (char*)&tmp + STACK_DIRECTION * (long)my_thread_stack_size; @@ -377,8 +368,8 @@ void my_thread_end(void) tmp= my_pthread_getspecific(struct st_my_thread_var*,THR_KEY_mysys); #ifdef EXTRA_DEBUG_THREADS - fprintf(stderr,"my_thread_end(): tmp: 0x%lx pthread_self: 0x%lx thread_id: %ld\n", - (long) tmp, (long) pthread_self(), tmp ? (long) tmp->id : 0L); + fprintf(stderr,"my_thread_end(): tmp: %p pthread_self: %p thread_id: %ld\n", + tmp, pthread_self(), tmp ? (long) tmp->id : 0L); #endif #ifdef HAVE_PSI_INTERFACE @@ -391,23 +382,21 @@ void my_thread_end(void) PSI_server->delete_current_thread(); #endif + DBUG_POP(); + + pthread_setspecific(THR_KEY_mysys,0); + if (tmp && tmp->init) { #if !defined(DBUG_OFF) /* tmp->dbug is allocated inside DBUG library */ if (tmp->dbug) { - DBUG_POP(); free(tmp->dbug); tmp->dbug=0; } #endif -#if !defined(__bsdi__) && !defined(__OpenBSD__) - /* bsdi and openbsd 3.5 dumps core here */ - mysql_cond_destroy(&tmp->suspend); -#endif - mysql_mutex_destroy(&tmp->mutex); - free(tmp); + my_thread_destory_thr_mutex(tmp); /* Decrement counter for number of running threads. We are using this @@ -420,8 +409,10 @@ void my_thread_end(void) if (--THR_thread_count == 0) mysql_cond_signal(&THR_COND_threads); mysql_mutex_unlock(&THR_LOCK_threads); + + TRASH(tmp, sizeof(*tmp)); + free(tmp); } - pthread_setspecific(THR_KEY_mysys,0); } struct st_my_thread_var *_my_thread_var(void) @@ -455,7 +446,7 @@ const char *my_thread_name(void) { my_thread_id id= my_thread_dbug_id(); sprintf(name_buff,"T@%lu", (ulong) id); - strmake(tmp->name,name_buff,THREAD_NAME_SIZE); + strmake_buf(tmp->name, name_buff); } return tmp->name; } @@ -464,12 +455,24 @@ const char *my_thread_name(void) extern void **my_thread_var_dbug() { - struct st_my_thread_var *tmp= - my_pthread_getspecific(struct st_my_thread_var*,THR_KEY_mysys); + struct st_my_thread_var *tmp; + if (!my_thread_global_init_done) + return NULL; + tmp= my_pthread_getspecific(struct st_my_thread_var*,THR_KEY_mysys); return tmp && tmp->init ? &tmp->dbug : 0; } #endif /* DBUG_OFF */ +/* Return pointer to mutex_in_use */ + +safe_mutex_t **my_thread_var_mutex_in_use() +{ + struct st_my_thread_var *tmp; + if (!my_thread_global_init_done) + return NULL; + tmp= my_pthread_getspecific(struct st_my_thread_var*,THR_KEY_mysys); + return tmp ? &tmp->mutex_in_use : 0; +} static uint get_thread_lib(void) { diff --git a/mysys/my_uuid.c b/mysys/my_uuid.c new file mode 100644 index 00000000000..ab1b259ae0f --- /dev/null +++ b/mysys/my_uuid.c @@ -0,0 +1,245 @@ +/* Copyright (C) 2007 MySQL AB, Sergei Golubchik & Michael Widenius + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + implements Universal Unique Identifiers (UUIDs), as in + DCE 1.1: Remote Procedure Call, + Open Group Technical Standard Document Number C706, October 1997, + (supersedes C309 DCE: Remote Procedure Call 8/1994, + which was basis for ISO/IEC 11578:1996 specification) + + A UUID has the following structure: + + Field NDR Data Type Octet # Note + time_low unsigned long 0-3 The low field of the + timestamp. + time_mid unsigned short 4-5 The middle field of + the timestamp. + time_hi_and_version unsigned short 6-7 The high field of the + timestamp multiplexed + with the version number. + clock_seq_hi_and_reserved unsigned small 8 The high field of the + clock sequence multi- + plexed with the variant. + clock_seq_low unsigned small 9 The low field of the + clock sequence. + node character 10-15 The spatially unique node + identifier. +*/ + +#include "mysys_priv.h" +#include <m_string.h> +#include <myisampack.h> /* mi_int2store, mi_int4store */ + +static my_bool my_uuid_inited= 0; +static struct my_rnd_struct uuid_rand; +static uint nanoseq; +static ulonglong uuid_time= 0; +static longlong interval_timer_offset; +static uchar uuid_suffix[2+6]; /* clock_seq and node */ + +static mysql_mutex_t LOCK_uuid_generator; + +/* + Number of 100-nanosecond intervals between + 1582-10-15 00:00:00.00 and 1970-01-01 00:00:00.00 +*/ + +#define UUID_TIME_OFFSET ((ulonglong) 141427 * 24 * 60 * 60 * \ + 1000 * 1000 * 10) +#define UUID_VERSION 0x1000 +#define UUID_VARIANT 0x8000 + + +/* Helper function */ + +static void set_clock_seq() +{ + uint16 clock_seq= ((uint)(my_rnd(&uuid_rand)*16383)) | UUID_VARIANT; + mi_int2store(uuid_suffix, clock_seq); + interval_timer_offset= (my_hrtime().val * 10 - my_interval_timer()/100 + + UUID_TIME_OFFSET); +} + + +/** + Init structures needed for my_uuid + + @func my_uuid_init() + @param seed1 Seed for random generator + @param seed2 Seed for random generator + + @note + Seed1 & seed2 should NOT depend on clock. This is to be able to + generate a random mac address according to UUID specs. +*/ + +void my_uuid_init(ulong seed1, ulong seed2) +{ + uchar *mac= uuid_suffix+2; + ulonglong now; + + if (my_uuid_inited) + return; + my_uuid_inited= 1; + now= my_interval_timer()/100 + interval_timer_offset; + nanoseq= 0; + + if (my_gethwaddr(mac)) + { + uint i; + /* + Generating random "hardware addr" + + Specs explicitly specify that node identifier should NOT + correlate with a clock_seq value, so we use a separate + randominit() here. + */ + /* purecov: begin inspected */ + my_rnd_init(&uuid_rand, (ulong) (seed2+ now/2), (ulong) (now+rand())); + for (i=0; i < array_elements(uuid_suffix) -2 ; i++) + mac[i]= (uchar)(my_rnd(&uuid_rand)*255); + /* purecov: end */ + } + my_rnd_init(&uuid_rand, (ulong) (seed1 + now), (ulong) (now/2+ getpid())); + set_clock_seq(); + mysql_mutex_init(key_LOCK_uuid_generator, &LOCK_uuid_generator, MY_MUTEX_INIT_FAST); +} + + +/** + Create a global unique identifier (uuid) + + @func my_uuid() + @param to Store uuid here. Must be of size MY_uuid_SIZE (16) +*/ + +void my_uuid(uchar *to) +{ + ulonglong tv; + uint32 time_low; + uint16 time_mid, time_hi_and_version; + + DBUG_ASSERT(my_uuid_inited); + + mysql_mutex_lock(&LOCK_uuid_generator); + tv= my_interval_timer()/100 + interval_timer_offset + nanoseq; + + if (likely(tv > uuid_time)) + { + /* + Current time is ahead of last timestamp, as it should be. + If we "borrowed time", give it back, just as long as we + stay ahead of the previous timestamp. + */ + if (nanoseq) + { + ulong delta; + DBUG_ASSERT((tv > uuid_time) && (nanoseq > 0)); + /* + -1 so we won't make tv= uuid_time for nanoseq >= (tv - uuid_time) + */ + delta= min(nanoseq, (ulong)(tv - uuid_time -1)); + tv-= delta; + nanoseq-= delta; + } + } + else + { + if (unlikely(tv == uuid_time)) + { + /* + For low-res system clocks. If several requests for UUIDs + end up on the same tick, we add a nano-second to make them + different. + ( current_timestamp + nanoseq * calls_in_this_period ) + may end up > next_timestamp; this is OK. Nonetheless, we'll + try to unwind nanoseq when we get a chance to. + If nanoseq overflows, we'll start over with a new numberspace + (so the if() below is needed so we can avoid the ++tv and thus + match the follow-up if() if nanoseq overflows!). + */ + if (likely(++nanoseq)) + ++tv; + } + + if (unlikely(tv <= uuid_time)) + { + /* + If the admin changes the system clock (or due to Daylight + Saving Time), the system clock may be turned *back* so we + go through a period once more for which we already gave out + UUIDs. To avoid duplicate UUIDs despite potentially identical + times, we make a new random component. + We also come here if the nanoseq "borrowing" overflows. + In either case, we throw away any nanoseq borrowing since it's + irrelevant in the new numberspace. + */ + set_clock_seq(); + tv= my_interval_timer()/100 + interval_timer_offset; + nanoseq= 0; + DBUG_PRINT("uuid",("making new numberspace")); + } + } + + uuid_time=tv; + mysql_mutex_unlock(&LOCK_uuid_generator); + + time_low= (uint32) (tv & 0xFFFFFFFF); + time_mid= (uint16) ((tv >> 32) & 0xFFFF); + time_hi_and_version= (uint16) ((tv >> 48) | UUID_VERSION); + + /* + Note, that the standard does NOT specify byte ordering in + multi-byte fields. it's implementation defined (but must be + the same for all fields). + We use big-endian, so we can use memcmp() to compare UUIDs + and for straightforward UUID to string conversion. + */ + mi_int4store(to, time_low); + mi_int2store(to+4, time_mid); + mi_int2store(to+6, time_hi_and_version); + bmove(to+8, uuid_suffix, sizeof(uuid_suffix)); +} + + +/** + Convert uuid to string representation + + @func my_uuid2str() + @param guid uuid + @param s Output buffer.Must be at least MY_UUID_STRING_LENGTH+1 large. +*/ +void my_uuid2str(const uchar *guid, char *s) +{ + int i; + for (i=0; i < MY_UUID_SIZE; i++) + { + *s++= _dig_vec_lower[guid[i] >>4]; + *s++= _dig_vec_lower[guid[i] & 15]; + /* Set '-' at intervals 3, 5, 7 and 9 */ + if ((1 << i) & ((1 << 3) | (1 << 5) | (1 << 7) | (1 << 9))) + *s++= '-'; + } +} + +void my_uuid_end() +{ + if (my_uuid_inited) + { + my_uuid_inited= 0; + mysql_mutex_destroy(&LOCK_uuid_generator); + } +} diff --git a/mysys/my_wincond.c b/mysys/my_wincond.c index 4794bb391b6..6674a5d394d 100644 --- a/mysys/my_wincond.c +++ b/mysys/my_wincond.c @@ -31,7 +31,7 @@ */ /* Prototypes and function pointers for condition variable functions */ -typedef VOID (WINAPI * InitializeConditionVariableProc) +typedef void (WINAPI * InitializeConditionVariableProc) (PCONDITION_VARIABLE ConditionVariable); typedef BOOL (WINAPI * SleepConditionVariableCSProc) @@ -39,10 +39,10 @@ typedef BOOL (WINAPI * SleepConditionVariableCSProc) PCRITICAL_SECTION CriticalSection, DWORD dwMilliseconds); -typedef VOID (WINAPI * WakeAllConditionVariableProc) +typedef void (WINAPI * WakeAllConditionVariableProc) (PCONDITION_VARIABLE ConditionVariable); -typedef VOID (WINAPI * WakeConditionVariableProc) +typedef void (WINAPI * WakeConditionVariableProc) (PCONDITION_VARIABLE ConditionVariable); static InitializeConditionVariableProc my_InitializeConditionVariable; @@ -88,36 +88,20 @@ static void check_native_cond_availability(void) static DWORD get_milliseconds(const struct timespec *abstime) { - long long millis; - union ft64 now; + struct timespec current_time; + long long ms; if (abstime == NULL) - return INFINITE; - - GetSystemTimeAsFileTime(&now.ft); - - /* - Calculate time left to abstime - - subtract start time from current time(values are in 100ns units) - - convert to millisec by dividing with 10000 - */ - millis= (abstime->tv.i64 - now.i64) / 10000; - - /* Don't allow the timeout to be negative */ - if (millis < 0) - return 0; - - /* - Make sure the calculated timeout does not exceed original timeout - value which could cause "wait for ever" if system time changes - */ - if (millis > abstime->max_timeout_msec) - millis= abstime->max_timeout_msec; - - if (millis > UINT_MAX) - millis= UINT_MAX; - - return (DWORD)millis; + return INFINITE; + + set_timespec_nsec(current_time, 0); + ms= (abstime->tv_sec - current_time.tv_sec)*1000LL + + (abstime->tv_nsec - current_time.tv_nsec)/1000000LL; + if(ms < 0 ) + ms= 0; + if(ms > UINT_MAX) + ms= INFINITE; + return (DWORD)ms; } diff --git a/mysys/my_winthread.c b/mysys/my_winthread.c index fcb6e57ee53..81fd0e7277c 100644 --- a/mysys/my_winthread.c +++ b/mysys/my_winthread.c @@ -18,13 +18,12 @@ *****************************************************************************/ #if defined (_WIN32) /* SAFE_MUTEX will not work until the thread structure is up to date */ + #undef SAFE_MUTEX #include "mysys_priv.h" #include <process.h> #include <signal.h> -static void install_sigabrt_handler(void); - struct thread_start_parameter { pthread_handler func; diff --git a/mysys/my_write.c b/mysys/my_write.c index ef15e9a55b6..10a500c3fb3 100644 --- a/mysys/my_write.c +++ b/mysys/my_write.c @@ -28,25 +28,36 @@ size_t my_write(File Filedes, const uchar *Buffer, size_t Count, myf MyFlags) DBUG_PRINT("my",("fd: %d Buffer: %p Count: %lu MyFlags: %d", Filedes, Buffer, (ulong) Count, MyFlags)); errors= 0; written= 0; + if (!(MyFlags & (MY_WME | MY_FAE | MY_FNABP))) + MyFlags|= my_global_flags; /* The behavior of write(fd, buf, 0) is not portable */ if (unlikely(!Count)) DBUG_RETURN(0); - DBUG_EXECUTE_IF ("simulate_no_free_space_error", - { DBUG_SET("+d,simulate_file_write_error");}); for (;;) { #ifdef _WIN32 + if(Filedes < 0) + { + my_errno= errno= EBADF; + DBUG_RETURN((size_t)-1); + } writtenbytes= my_win_write(Filedes, Buffer, Count); #else writtenbytes= write(Filedes, Buffer, Count); #endif - DBUG_EXECUTE_IF("simulate_file_write_error", - { - errno= ENOSPC; - writtenbytes= (size_t) -1; - }); + + /** + To simulate the write error set the errno = error code + and the number pf written bytes to -1. + */ + DBUG_EXECUTE_IF ("simulate_file_write_error", + if (!errors) { + errno= ENOSPC; + writtenbytes= (size_t) -1; + }); + if (writtenbytes == Count) break; if (writtenbytes != (size_t) -1) @@ -67,8 +78,6 @@ size_t my_write(File Filedes, const uchar *Buffer, size_t Count, myf MyFlags) { wait_for_free_space(my_filename(Filedes), errors); errors++; - DBUG_EXECUTE_IF("simulate_no_free_space_error", - { DBUG_SET("-d,simulate_file_write_error");}); continue; } @@ -91,19 +100,20 @@ size_t my_write(File Filedes, const uchar *Buffer, size_t Count, myf MyFlags) else continue; /* Retry */ #endif + + /* Don't give a warning if it's ok that we only write part of the data */ if (MyFlags & (MY_NABP | MY_FNABP)) { if (MyFlags & (MY_WME | MY_FAE | MY_FNABP)) { - my_error(EE_WRITE, MYF(ME_BELL+ME_WAITTANG), + my_error(EE_WRITE, MYF(ME_BELL | ME_WAITTANG | (MyFlags & (ME_JUST_INFO | ME_NOREFRESH))), my_filename(Filedes),my_errno); } DBUG_RETURN(MY_FILE_ERROR); /* Error on read */ } - else - break; /* Return bytes written */ + break; /* Return bytes written */ } if (MyFlags & (MY_NABP | MY_FNABP)) - DBUG_RETURN(0); /* Want only errors */ + DBUG_RETURN(0); /* Want only errors */ DBUG_RETURN(writtenbytes+written); } /* my_write */ diff --git a/mysys/mysys_priv.h b/mysys/mysys_priv.h index b85bb1a4be0..f5d2f301837 100644 --- a/mysys/mysys_priv.h +++ b/mysys/mysys_priv.h @@ -41,7 +41,7 @@ extern PSI_mutex_key key_BITMAP_mutex, key_IO_CACHE_append_buffer_lock, key_my_thread_var_mutex, key_THR_LOCK_charset, key_THR_LOCK_heap, key_THR_LOCK_isam, key_THR_LOCK_lock, key_THR_LOCK_malloc, key_THR_LOCK_mutex, key_THR_LOCK_myisam, key_THR_LOCK_net, - key_THR_LOCK_open, key_THR_LOCK_threads, + key_THR_LOCK_open, key_THR_LOCK_threads, key_LOCK_uuid_generator, key_TMPDIR_mutex, key_THR_LOCK_myisam_mmap; extern PSI_cond_key key_COND_alarm, key_IO_CACHE_SHARE_cond, @@ -52,6 +52,8 @@ extern PSI_cond_key key_COND_alarm, key_IO_CACHE_SHARE_cond, extern PSI_thread_key key_thread_alarm; #endif /* USE_ALARM_THREAD */ +extern PSI_rwlock_key key_SAFEHASH_mutex; + #endif /* HAVE_PSI_INTERFACE */ extern mysql_mutex_t THR_LOCK_malloc, THR_LOCK_open, THR_LOCK_keycache; @@ -67,6 +69,16 @@ extern PSI_file_key key_file_proc_meminfo; extern PSI_file_key key_file_charset, key_file_cnf; #endif /* HAVE_PSI_INTERFACE */ +#ifdef SAFEMALLOC +void *sf_malloc(size_t size); +void *sf_realloc(void *ptr, size_t size); +void sf_free(void *ptr); +#else +#define sf_malloc(X) malloc(X) +#define sf_realloc(X,Y) realloc(X,Y) +#define sf_free(X) free(X) +#endif + /* EDQUOT is used only in 3 C files only in mysys/. If it does not exist on system, we set it to some value which can never happen. diff --git a/mysys/ptr_cmp.c b/mysys/ptr_cmp.c index 6f9ab13c82b..a481b4d961c 100644 --- a/mysys/ptr_cmp.c +++ b/mysys/ptr_cmp.c @@ -21,17 +21,23 @@ #include "mysys_priv.h" #include <myisampack.h> - -#ifdef __sun /* - * On Solaris, memcmp() is normally faster than the unrolled ptr_compare_N + * On some platforms, memcmp() is faster than the unrolled ptr_compare_N * functions, as memcmp() is usually a platform-specific implementation - * written in assembler, provided in /usr/lib/libc/libc_hwcap*.so.1. - * This implementation is also usually faster than the built-in memcmp - * supplied by GCC, so it is recommended to build with "-fno-builtin-memcmp" - * in CFLAGS if building with GCC on Solaris. + * written in assembler. for example one in /usr/lib/libc/libc_hwcap*.so.1. + * on Solaris, or on Windows inside C runtime linrary. + * + * On Solaris, native implementation is also usually faster than the + * built-in memcmp supplied by GCC, so it is recommended to build + * with "-fno-builtin-memcmp"in CFLAGS if building with GCC on Solaris. */ +#if defined (__sun) || defined (_WIN32) +#define USE_NATIVE_MEMCMP 1 +#endif + +#ifdef USE_NATIVE_MEMCMP + #include <string.h> static int native_compare(size_t *length, unsigned char **a, unsigned char **b) @@ -39,7 +45,7 @@ static int native_compare(size_t *length, unsigned char **a, unsigned char **b) return memcmp(*a, *b, *length); } -#else /* __sun */ +#else /* USE_NATIVE_MEMCMP */ static int ptr_compare(size_t *compare_length, uchar **a, uchar **b); static int ptr_compare_0(size_t *compare_length, uchar **a, uchar **b); @@ -50,7 +56,7 @@ static int ptr_compare_3(size_t *compare_length, uchar **a, uchar **b); /* Get a pointer to a optimal byte-compare function for a given size */ -#ifdef __sun +#ifdef USE_NATIVE_MEMCMP qsort2_cmp get_ptr_compare (size_t size __attribute__((unused))) { return (qsort2_cmp) native_compare; @@ -68,7 +74,7 @@ qsort2_cmp get_ptr_compare (size_t size) } return 0; /* Impossible */ } -#endif /* __sun */ +#endif /* USE_NATIVE_MEMCMP */ /* @@ -78,7 +84,7 @@ qsort2_cmp get_ptr_compare (size_t size) #define cmp(N) if (first[N] != last[N]) return (int) first[N] - (int) last[N] -#ifndef __sun +#ifndef USE_NATIVE_MEMCMP static int ptr_compare(size_t *compare_length, uchar **a, uchar **b) { diff --git a/mysys/queues.c b/mysys/queues.c index 801c5a68468..418163d7c58 100644 --- a/mysys/queues.c +++ b/mysys/queues.c @@ -1,25 +1,42 @@ -/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; version 2 of the License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +/* Copyright (C) 2010 Monty Program Ab + All Rights reserved + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. +*/ /* + This code originates from the Unireg project. + Code for generell handling of priority Queues. Implemention of queues from "Algoritms in C" by Robert Sedgewick. - An optimisation of _downheap suggested in Exercise 7.51 in "Data - Structures & Algorithms in C++" by Mark Allen Weiss, Second Edition - was implemented by Mikael Ronstrom 2005. Also the O(N) algorithm - of queue_fix was implemented. + + The queue can optionally store the position in queue in the element + that is in the queue. This allows one to remove any element from the queue + in O(1) time. + + Optimisation of _downheap() and queue_fix() is inspired by code done + by Mikael Ronström, based on an optimisation of _downheap from + Exercise 7.51 in "Data Structures & Algorithms in C++" by Mark Allen + Weiss, Second Edition. */ #include "mysys_priv.h" @@ -39,6 +56,10 @@ max_at_top Set to 1 if you want biggest element on top. compare Compare function for elements, takes 3 arguments. first_cmp_arg First argument to compare function + offset_to_queue_pos If <> 0, then offset+1 in element to store position + in queue (for fast delete of element in queue) + auto_extent When the queue is full and there is insert operation + extend the queue. NOTES Will allocate max_element pointers for queue array @@ -50,74 +71,33 @@ int init_queue(QUEUE *queue, uint max_elements, uint offset_to_key, pbool max_at_top, int (*compare) (void *, uchar *, uchar *), - void *first_cmp_arg) + void *first_cmp_arg, uint offset_to_queue_pos, + uint auto_extent) + { DBUG_ENTER("init_queue"); - if ((queue->root= (uchar **) my_malloc((max_elements+1)*sizeof(void*), + if ((queue->root= (uchar **) my_malloc((max_elements + 1) * sizeof(void*), MYF(MY_WME))) == 0) DBUG_RETURN(1); - queue->elements=0; - queue->compare=compare; - queue->first_cmp_arg=first_cmp_arg; - queue->max_elements=max_elements; - queue->offset_to_key=offset_to_key; + queue->elements= 0; + queue->compare= compare; + queue->first_cmp_arg= first_cmp_arg; + queue->max_elements= max_elements; + queue->offset_to_key= offset_to_key; + queue->offset_to_queue_pos= offset_to_queue_pos; + queue->auto_extent= auto_extent; queue_set_max_at_top(queue, max_at_top); DBUG_RETURN(0); } - -/* - Init queue, uses init_queue internally for init work but also accepts - auto_extent as parameter - - SYNOPSIS - init_queue_ex() - queue Queue to initialise - max_elements Max elements that will be put in queue - offset_to_key Offset to key in element stored in queue - Used when sending pointers to compare function - max_at_top Set to 1 if you want biggest element on top. - compare Compare function for elements, takes 3 arguments. - first_cmp_arg First argument to compare function - auto_extent When the queue is full and there is insert operation - extend the queue. - - NOTES - Will allocate max_element pointers for queue array - - RETURN - 0 ok - 1 Could not allocate memory -*/ - -int init_queue_ex(QUEUE *queue, uint max_elements, uint offset_to_key, - pbool max_at_top, int (*compare) (void *, uchar *, uchar *), - void *first_cmp_arg, uint auto_extent) -{ - int ret; - DBUG_ENTER("init_queue_ex"); - - if ((ret= init_queue(queue, max_elements, offset_to_key, max_at_top, compare, - first_cmp_arg))) - DBUG_RETURN(ret); - - queue->auto_extent= auto_extent; - DBUG_RETURN(0); -} - /* Reinitialize queue for other usage SYNOPSIS reinit_queue() queue Queue to initialise - max_elements Max elements that will be put in queue - offset_to_key Offset to key in element stored in queue - Used when sending pointers to compare function - max_at_top Set to 1 if you want biggest element on top. - compare Compare function for elements, takes 3 arguments. - first_cmp_arg First argument to compare function + For rest of arguments, see init_queue() above NOTES This will delete all elements from the queue. If you don't want this, @@ -125,21 +105,23 @@ int init_queue_ex(QUEUE *queue, uint max_elements, uint offset_to_key, RETURN 0 ok - EE_OUTOFMEMORY Wrong max_elements + 1 Wrong max_elements; Queue has old size */ int reinit_queue(QUEUE *queue, uint max_elements, uint offset_to_key, pbool max_at_top, int (*compare) (void *, uchar *, uchar *), - void *first_cmp_arg) + void *first_cmp_arg, uint offset_to_queue_pos, + uint auto_extent) { DBUG_ENTER("reinit_queue"); - queue->elements=0; - queue->compare=compare; - queue->first_cmp_arg=first_cmp_arg; - queue->offset_to_key=offset_to_key; + queue->elements= 0; + queue->compare= compare; + queue->first_cmp_arg= first_cmp_arg; + queue->offset_to_key= offset_to_key; + queue->offset_to_queue_pos= offset_to_queue_pos; + queue->auto_extent= auto_extent; queue_set_max_at_top(queue, max_at_top); - resize_queue(queue, max_elements); - DBUG_RETURN(0); + DBUG_RETURN(resize_queue(queue, max_elements)); } @@ -167,8 +149,8 @@ int resize_queue(QUEUE *queue, uint max_elements) if (queue->max_elements == max_elements) DBUG_RETURN(0); if ((new_root= (uchar **) my_realloc((void *)queue->root, - (max_elements+1)*sizeof(void*), - MYF(MY_WME))) == 0) + (max_elements + 1)* sizeof(void*), + MYF(MY_WME))) == 0) DBUG_RETURN(1); set_if_smaller(queue->elements, max_elements); queue->max_elements= max_elements; @@ -195,38 +177,57 @@ void delete_queue(QUEUE *queue) { DBUG_ENTER("delete_queue"); my_free(queue->root); - queue->root= NULL; + queue->root=0; /* Allow multiple calls */ DBUG_VOID_RETURN; } - /* Code for insert, search and delete of elements */ +/* + Insert element in queue + + SYNOPSIS + queue_insert() + queue Queue to use + element Element to insert +*/ void queue_insert(register QUEUE *queue, uchar *element) { reg2 uint idx, next; + uint offset_to_queue_pos= queue->offset_to_queue_pos; DBUG_ASSERT(queue->elements < queue->max_elements); - queue->root[0]= element; + idx= ++queue->elements; /* max_at_top swaps the comparison if we want to order by desc */ - while ((queue->compare(queue->first_cmp_arg, + while (idx > 1 && + (queue->compare(queue->first_cmp_arg, element + queue->offset_to_key, queue->root[(next= idx >> 1)] + queue->offset_to_key) * queue->max_at_top) < 0) { queue->root[idx]= queue->root[next]; + if (offset_to_queue_pos) + (*(uint*) (queue->root[idx] + offset_to_queue_pos-1))= idx; idx= next; } queue->root[idx]= element; + if (offset_to_queue_pos) + (*(uint*) (element+ offset_to_queue_pos-1))= idx; } + /* - Does safe insert. If no more space left on the queue resize it. - Return codes: - 0 - OK - 1 - Cannot allocate more memory - 2 - auto_extend is 0, the operation would - + Like queue_insert, but resize queue if queue is full + + SYNOPSIS + queue_insert_safe() + queue Queue to use + element Element to insert + + RETURN + 0 OK + 1 Cannot allocate more memory + 2 auto_extend is 0; No insertion done */ int queue_insert_safe(register QUEUE *queue, uchar *element) @@ -236,7 +237,7 @@ int queue_insert_safe(register QUEUE *queue, uchar *element) { if (!queue->auto_extent) return 2; - else if (resize_queue(queue, queue->max_elements + queue->auto_extent)) + if (resize_queue(queue, queue->max_elements + queue->auto_extent)) return 1; } @@ -245,40 +246,51 @@ int queue_insert_safe(register QUEUE *queue, uchar *element) } - /* Remove item from queue */ - /* Returns pointer to removed element */ +/* + Remove item from queue + + SYNOPSIS + queue_remove() + queue Queue to use + element Index of element to remove. + First element in queue is 'queue_first_element(queue)' + + RETURN + pointer to removed element +*/ uchar *queue_remove(register QUEUE *queue, uint idx) { uchar *element; - DBUG_ASSERT(idx < queue->max_elements); - element= queue->root[++idx]; /* Intern index starts from 1 */ - queue->root[idx]= queue->root[queue->elements--]; - _downheap(queue, idx); + DBUG_ASSERT(idx >= 1 && idx <= queue->elements); + element= queue->root[idx]; + _downheap(queue, idx, queue->root[queue->elements--]); return element; } - /* Fix when element on top has been replaced */ -#ifndef queue_replaced -void queue_replaced(QUEUE *queue) -{ - _downheap(queue,1); -} -#endif +/* + Add element to fixed position and update heap -#ifndef OLD_VERSION + SYNOPSIS + _downheap() + queue Queue to use + idx Index of element to change + element Element to store at 'idx' + + NOTE + This only works if element is >= all elements <= start_idx +*/ -void _downheap(register QUEUE *queue, uint idx) +void _downheap(register QUEUE *queue, uint start_idx, uchar *element) { - uchar *element; - uint elements,half_queue,offset_to_key, next_index; + uint elements,half_queue,offset_to_key, next_index, offset_to_queue_pos; + register uint idx= start_idx; my_bool first= TRUE; - uint start_idx= idx; offset_to_key=queue->offset_to_key; - element=queue->root[idx]; - half_queue=(elements=queue->elements) >> 1; + offset_to_queue_pos= queue->offset_to_queue_pos; + half_queue= (elements= queue->elements) >> 1; while (idx <= half_queue) { @@ -295,393 +307,68 @@ void _downheap(register QUEUE *queue, uint idx) element+offset_to_key) * queue->max_at_top) >= 0))) { queue->root[idx]= element; + if (offset_to_queue_pos) + (*(uint*) (element + offset_to_queue_pos-1))= idx; return; } - queue->root[idx]=queue->root[next_index]; - idx=next_index; first= FALSE; - } - - next_index= idx >> 1; - while (next_index > start_idx) - { - if ((queue->compare(queue->first_cmp_arg, - queue->root[next_index]+offset_to_key, - element+offset_to_key) * - queue->max_at_top) < 0) - break; - queue->root[idx]=queue->root[next_index]; + queue->root[idx]= queue->root[next_index]; + if (offset_to_queue_pos) + (*(uint*) (queue->root[idx] + offset_to_queue_pos-1))= idx; idx=next_index; - next_index= idx >> 1; } - queue->root[idx]=element; -} -#else /* - The old _downheap version is kept for comparisons with the benchmark - suit or new benchmarks anyone wants to run for comparisons. + Insert the element into the right position. This is the same code + as we have in queue_insert() */ - /* Fix heap when index have changed */ -void _downheap(register QUEUE *queue, uint idx) -{ - uchar *element; - uint elements,half_queue,next_index,offset_to_key; - - offset_to_key=queue->offset_to_key; - element=queue->root[idx]; - half_queue=(elements=queue->elements) >> 1; - - while (idx <= half_queue) + while ((next_index= (idx >> 1)) > start_idx && + queue->compare(queue->first_cmp_arg, + element+offset_to_key, + queue->root[next_index]+offset_to_key)* + queue->max_at_top < 0) { - next_index=idx+idx; - if (next_index < elements && - (queue->compare(queue->first_cmp_arg, - queue->root[next_index]+offset_to_key, - queue->root[next_index+1]+offset_to_key) * - queue->max_at_top) > 0) - next_index++; - if ((queue->compare(queue->first_cmp_arg, - queue->root[next_index]+offset_to_key, - element+offset_to_key) * queue->max_at_top) >= 0) - break; - queue->root[idx]=queue->root[next_index]; - idx=next_index; + queue->root[idx]= queue->root[next_index]; + if (offset_to_queue_pos) + (*(uint*) (queue->root[idx] + offset_to_queue_pos-1))= idx; + idx= next_index; } - queue->root[idx]=element; + queue->root[idx]= element; + if (offset_to_queue_pos) + (*(uint*) (element + offset_to_queue_pos-1))= idx; } -#endif - /* Fix heap when every element was changed. + + SYNOPSIS + queue_fix() + queue Queue to use */ void queue_fix(QUEUE *queue) { uint i; for (i= queue->elements >> 1; i > 0; i--) - _downheap(queue, i); + _downheap(queue, i, queue_element(queue, i)); } -#ifdef MAIN - /* - A test program for the priority queue implementation. - It can also be used to benchmark changes of the implementation - Build by doing the following in the directory mysys - make test_priority_queue - ./test_priority_queue - - Written by Mikael Ronström, 2005 - */ - -static uint num_array[1025]; -static uint tot_no_parts= 0; -static uint tot_no_loops= 0; -static uint expected_part= 0; -static uint expected_num= 0; -static bool max_ind= 0; -static bool fix_used= 0; -static ulonglong start_time= 0; - -static bool is_divisible_by(uint num, uint divisor) -{ - uint quotient= num / divisor; - if (quotient * divisor == num) - return TRUE; - return FALSE; -} - -void calculate_next() -{ - uint part= expected_part, num= expected_num; - uint no_parts= tot_no_parts; - if (max_ind) - { - do - { - while (++part <= no_parts) - { - if (is_divisible_by(num, part) && - (num <= ((1 << 21) + part))) - { - expected_part= part; - expected_num= num; - return; - } - } - part= 0; - } while (--num); - } - else - { - do - { - while (--part > 0) - { - if (is_divisible_by(num, part)) - { - expected_part= part; - expected_num= num; - return; - } - } - part= no_parts + 1; - } while (++num); - } -} -void calculate_end_next(uint part) -{ - uint no_parts= tot_no_parts, num; - num_array[part]= 0; - if (max_ind) - { - expected_num= 0; - for (part= no_parts; part > 0 ; part--) - { - if (num_array[part]) - { - num= num_array[part] & 0x3FFFFF; - if (num >= expected_num) - { - expected_num= num; - expected_part= part; - } - } - } - if (expected_num == 0) - expected_part= 0; - } - else - { - expected_num= 0xFFFFFFFF; - for (part= 1; part <= no_parts; part++) - { - if (num_array[part]) - { - num= num_array[part] & 0x3FFFFF; - if (num <= expected_num) - { - expected_num= num; - expected_part= part; - } - } - } - if (expected_num == 0xFFFFFFFF) - expected_part= 0; - } - return; -} -static int test_compare(void *null_arg, uchar *a, uchar *b) -{ - uint a_num= (*(uint*)a) & 0x3FFFFF; - uint b_num= (*(uint*)b) & 0x3FFFFF; - uint a_part, b_part; - if (a_num > b_num) - return +1; - if (a_num < b_num) - return -1; - a_part= (*(uint*)a) >> 22; - b_part= (*(uint*)b) >> 22; - if (a_part < b_part) - return +1; - if (a_part > b_part) - return -1; - return 0; -} - -bool check_num(uint num_part) -{ - uint part= num_part >> 22; - uint num= num_part & 0x3FFFFF; - if (part == expected_part) - if (num == expected_num) - return FALSE; - printf("Expect part %u Expect num 0x%x got part %u num 0x%x max_ind %u fix_used %u \n", - expected_part, expected_num, part, num, max_ind, fix_used); - return TRUE; -} - - -void perform_insert(QUEUE *queue) -{ - uint i= 1, no_parts= tot_no_parts; - uint backward_start= 0; - - expected_part= 1; - expected_num= 1; - - if (max_ind) - backward_start= 1 << 21; - - do - { - uint num= (i + backward_start); - if (max_ind) - { - while (!is_divisible_by(num, i)) - num--; - if (max_ind && (num > expected_num || - (num == expected_num && i < expected_part))) - { - expected_num= num; - expected_part= i; - } - } - num_array[i]= num + (i << 22); - if (fix_used) - queue_element(queue, i-1)= (uchar*)&num_array[i]; - else - queue_insert(queue, (uchar*)&num_array[i]); - } while (++i <= no_parts); - if (fix_used) - { - queue->elements= no_parts; - queue_fix(queue); - } -} - -bool perform_ins_del(QUEUE *queue, bool max_ind) -{ - uint i= 0, no_loops= tot_no_loops, j= tot_no_parts; - do - { - uint num_part= *(uint*)queue_top(queue); - uint part= num_part >> 22; - if (check_num(num_part)) - return TRUE; - if (j++ >= no_loops) - { - calculate_end_next(part); - queue_remove(queue, (uint) 0); - } - else - { - calculate_next(); - if (max_ind) - num_array[part]-= part; - else - num_array[part]+= part; - queue_top(queue)= (uchar*)&num_array[part]; - queue_replaced(queue); - } - } while (++i < no_loops); - return FALSE; -} - -bool do_test(uint no_parts, uint l_max_ind, bool l_fix_used) -{ - QUEUE queue; - bool result; - max_ind= l_max_ind; - fix_used= l_fix_used; - init_queue(&queue, no_parts, 0, max_ind, test_compare, NULL); - tot_no_parts= no_parts; - tot_no_loops= 1024; - perform_insert(&queue); - if ((result= perform_ins_del(&queue, max_ind))) - delete_queue(&queue); - if (result) - { - printf("Error\n"); - return TRUE; - } - return FALSE; -} - -static void start_measurement() -{ - start_time= my_getsystime(); -} - -static void stop_measurement() -{ - ulonglong stop_time= my_getsystime(); - uint time_in_micros; - stop_time-= start_time; - stop_time/= 10; /* Convert to microseconds */ - time_in_micros= (uint)stop_time; - printf("Time expired is %u microseconds \n", time_in_micros); -} - -static void benchmark_test() -{ - QUEUE queue_real; - QUEUE *queue= &queue_real; - uint i, add; - fix_used= TRUE; - max_ind= FALSE; - tot_no_parts= 1024; - init_queue(queue, tot_no_parts, 0, max_ind, test_compare, NULL); - /* - First benchmark whether queue_fix is faster than using queue_insert - for sizes of 16 partitions. - */ - for (tot_no_parts= 2, add=2; tot_no_parts < 128; - tot_no_parts+= add, add++) - { - printf("Start benchmark queue_fix, tot_no_parts= %u \n", tot_no_parts); - start_measurement(); - for (i= 0; i < 128; i++) - { - perform_insert(queue); - queue_remove_all(queue); - } - stop_measurement(); +/* + Change element at fixed position - fix_used= FALSE; - printf("Start benchmark queue_insert\n"); - start_measurement(); - for (i= 0; i < 128; i++) - { - perform_insert(queue); - queue_remove_all(queue); - } - stop_measurement(); - } - /* - Now benchmark insertion and deletion of 16400 elements. - Used in consecutive runs this shows whether the optimised _downheap - is faster than the standard implementation. - */ - printf("Start benchmarking _downheap \n"); - start_measurement(); - perform_insert(queue); - for (i= 0; i < 65536; i++) - { - uint num, part; - num= *(uint*)queue_top(queue); - num+= 16; - part= num >> 22; - num_array[part]= num; - queue_top(queue)= (uchar*)&num_array[part]; - queue_replaced(queue); - } - for (i= 0; i < 16; i++) - queue_remove(queue, (uint) 0); - queue_remove_all(queue); - stop_measurement(); -} + SYNOPSIS + queue_replace() + queue Queue to use + idx Index of element to change + element Element to store at 'idx' +*/ -int main() +void queue_replace(QUEUE *queue, uint idx) { - int i, add= 1; - for (i= 1; i < 1024; i+=add, add++) - { - printf("Start test for priority queue of size %u\n", i); - if (do_test(i, 0, 1)) - return -1; - if (do_test(i, 1, 1)) - return -1; - if (do_test(i, 0, 0)) - return -1; - if (do_test(i, 1, 0)) - return -1; - } - benchmark_test(); - printf("OK\n"); - return 0; + uchar *element= queue->root[idx]; + DBUG_ASSERT(idx >= 1 && idx <= queue->elements); + queue_remove(queue, idx); + queue_insert(queue, element); } -#endif diff --git a/mysys/safemalloc.c b/mysys/safemalloc.c new file mode 100644 index 00000000000..31f0333725f --- /dev/null +++ b/mysys/safemalloc.c @@ -0,0 +1,342 @@ +/* Copyright (C) 2000 MySQL AB, 2011 Monty Program Ab + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/******************************************************************** + memory debugger + based on safemalloc, memory sub-system, written by Bjorn Benson +********************************************************************/ + + +#include "mysys_priv.h" +#include <my_stacktrace.h> /* my_addr_resolve */ + +#if HAVE_EXECINFO_H +#include <execinfo.h> +#endif + +/* + this can be set to 1 if we leak memory and know it + (to disable memory leak tests on exit) +*/ +int sf_leaking_memory= 0; + +#ifdef SAFEMALLOC + +/* this mutex protects all sf_* variables, and nothing else*/ +static pthread_mutex_t sf_mutex; +static int init_done= 0; + +#ifndef SF_REMEMBER_FRAMES +#define SF_REMEMBER_FRAMES 8 +#endif + +/* ignore the first two frames (sf_malloc itself, and my_malloc) */ +#define SF_FRAMES_SKIP 2 + +/* + Structure that stores information of an allocated memory block + The data is at &struct_adr+sizeof(struct irem) + Note that sizeof(struct st_irem) % sizeof(double) == 0 +*/ +struct st_irem +{ + struct st_irem *next; /* Linked list of structures */ + struct st_irem *prev; /* Other link */ + size_t datasize; /* Size requested */ +#ifdef HAVE_BACKTRACE + void *frame[SF_REMEMBER_FRAMES]; /* call stack */ +#endif + uint32 marker; /* Underrun marker value */ +}; + +static int sf_malloc_count= 0; /* Number of allocated chunks */ + +static void *sf_min_adress= (void*) (intptr)~0ULL, + *sf_max_adress= 0; + +static struct st_irem *sf_malloc_root = 0; + +#define MAGICSTART 0x14235296 /* A magic value for underrun key */ + +#define MAGICEND0 0x68 /* Magic values for overrun keys */ +#define MAGICEND1 0x34 /* " */ +#define MAGICEND2 0x7A /* " */ +#define MAGICEND3 0x15 /* " */ + +static int bad_ptr(const char *where, void *ptr); +static void free_memory(void *ptr); +static void sf_terminate(); + +/** + allocates memory +*/ + +void *sf_malloc(size_t size) +{ + struct st_irem *irem; + uchar *data; + + /* + this style of initialization looks like race conditon prone, + but it is safe under the assumption that a program does + at least one malloc() while still being single threaded. + */ + if (!init_done) + { + pthread_mutex_init(&sf_mutex, NULL); + /* disable deadlock detector, because it calls my_malloc() */ + safe_mutex_setflags(&sf_mutex, MYF_NO_DEADLOCK_DETECTION); + atexit(sf_terminate); + init_done= 1; + } + + irem= (struct st_irem *) malloc (sizeof(struct st_irem) + size + 4); + + if (!irem) + return 0; + + /* we guarantee the alignment */ + compile_time_assert(sizeof(struct st_irem) % sizeof(double) == 0); + + /* Fill up the structure */ + data= (uchar*) (irem + 1); + irem->datasize= size; + irem->prev= 0; + irem->marker= MAGICSTART; + data[size + 0]= MAGICEND0; + data[size + 1]= MAGICEND1; + data[size + 2]= MAGICEND2; + data[size + 3]= MAGICEND3; + +#ifdef HAVE_BACKTRACE + { + void *frame[SF_REMEMBER_FRAMES + SF_FRAMES_SKIP]; + int frames= backtrace(frame, array_elements(frame)); + if (frames < SF_FRAMES_SKIP) + frames= 0; + else + { + frames-= SF_FRAMES_SKIP; + memcpy(irem->frame, frame + SF_FRAMES_SKIP, sizeof(void*)*frames); + } + if (frames < SF_REMEMBER_FRAMES) + irem->frame[frames]= 0; + } +#endif + + pthread_mutex_lock(&sf_mutex); + + /* Add this structure to the linked list */ + if ((irem->next= sf_malloc_root)) + sf_malloc_root->prev= irem; + sf_malloc_root= irem; + + /* Keep the statistics */ + sf_malloc_count++; + set_if_smaller(sf_min_adress, (void*)data); + set_if_bigger(sf_max_adress, (void*)data); + + pthread_mutex_unlock(&sf_mutex); + + TRASH_ALLOC(data, size); + return data; +} + +void *sf_realloc(void *ptr, size_t size) +{ + char *data; + + if (!ptr) + return sf_malloc(size); + + if (bad_ptr("Reallocating", ptr)) + return 0; + + if ((data= sf_malloc(size))) + { + struct st_irem *irem= (struct st_irem *)ptr - 1; + set_if_smaller(size, irem->datasize); + memcpy(data, ptr, size); + free_memory(ptr); + } + return data; +} + +void sf_free(void *ptr) +{ + if (!ptr || bad_ptr("Freeing", ptr)) + return; + + free_memory(ptr); +} + +static void free_memory(void *ptr) +{ + struct st_irem *irem= (struct st_irem *)ptr - 1; + + pthread_mutex_lock(&sf_mutex); + /* Remove this structure from the linked list */ + if (irem->prev) + irem->prev->next= irem->next; + else + sf_malloc_root= irem->next; + + if (irem->next) + irem->next->prev= irem->prev; + + /* Handle the statistics */ + sf_malloc_count--; + pthread_mutex_unlock(&sf_mutex); + + /* only trash the data and magic values, but keep the stack trace */ + TRASH_FREE((uchar*)(irem + 1) - 4, irem->datasize + 8); + free(irem); + return; +} + +#ifdef HAVE_BACKTRACE +static void print_stack(void **frame) +{ + const char *err; + int i; + + if ((err= my_addr_resolve_init())) + { + fprintf(stderr, "(my_addr_resolve failure: %s)\n", err); + return; + } + + for (i=0; i < SF_REMEMBER_FRAMES && frame[i]; i++) + { + my_addr_loc loc; + if (i) + fprintf(stderr, ", "); + + if (my_addr_resolve(frame[i], &loc)) + fprintf(stderr, "%p", frame[i]); + else + fprintf(stderr, "%s:%u", loc.file, loc.line); + } + fprintf(stderr, "\n"); +} +#else +#define print_stack(X) fprintf(stderr, "???\n") +#endif + +static void warn(const char *format,...) +{ + va_list args; + va_start(args,format); + vfprintf(stderr, format, args); + va_end(args); + +#ifdef HAVE_BACKTRACE + { + void *frame[SF_REMEMBER_FRAMES + SF_FRAMES_SKIP]; + int frames= backtrace(frame, array_elements(frame)); + fprintf(stderr, " "); + if (frames < SF_REMEMBER_FRAMES + SF_FRAMES_SKIP) + frame[frames]= 0; + print_stack(frame + SF_FRAMES_SKIP); + } +#endif +} + +static int bad_ptr(const char *where, void *ptr) +{ + struct st_irem *irem= (struct st_irem *)ptr - 1; + const uchar *magicend; + + if (((intptr) ptr) % sizeof(double)) + { + warn("Error: %s wrong aligned pointer", where); + return 1; + } + if (ptr < sf_min_adress || ptr > sf_max_adress) + { + warn("Error: %s pointer out of range", where); + return 1; + } + if (irem->marker != MAGICSTART) + { + warn("Error: %s unallocated data or underrun buffer", where); + return 1; + } + + magicend= (uchar*)ptr + irem->datasize; + if (magicend[0] != MAGICEND0 || + magicend[1] != MAGICEND1 || + magicend[2] != MAGICEND2 || + magicend[3] != MAGICEND3) + { + warn("Error: %s overrun buffer ", where); + fprintf(stderr, "Allocated at "); + print_stack(irem->frame); + return 1; + } + + return 0; +} + +/* check all allocated memory list for consistency */ +static int sf_sanity() +{ + struct st_irem *irem; + int flag= 0; + int count= 0; + + pthread_mutex_lock(&sf_mutex); + count= sf_malloc_count; + for (irem= sf_malloc_root; irem && count > 0; count--, irem= irem->next) + flag+= bad_ptr("Safemalloc", irem + 1); + pthread_mutex_unlock(&sf_mutex); + if (count || irem) + { + warn("Error: Safemalloc link list destroyed"); + return 1; + } + return 0; +} + +/** + report on all the memory pieces that have not been free'd +*/ + +static void sf_terminate() +{ + size_t total= 0; + struct st_irem *irem; + + sf_sanity(); + + /* Report on all the memory that was allocated but not free'd */ + if (!sf_leaking_memory && sf_malloc_root) + { + for (irem= sf_malloc_root; irem; irem= irem->next) + { + fprintf(stderr, "Warning: %4lu bytes lost, allocated at ", + (ulong) irem->datasize); + print_stack(irem->frame); + total+= irem->datasize; + } + fprintf(stderr, "Memory lost: %lu bytes in %d chunks\n", + (ulong) total, sf_malloc_count); + } + + pthread_mutex_destroy(&sf_mutex); + return; +} + +#endif diff --git a/mysys/stacktrace.c b/mysys/stacktrace.c index a3ccc3185ac..402520990b6 100644 --- a/mysys/stacktrace.c +++ b/mysys/stacktrace.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2001, 2011, Oracle and/or its affiliates This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -118,12 +119,8 @@ static int safe_print_str(const char *addr, int max_len) break; } - /* Output a new line if something was printed. */ - if (total != (size_t) max_len) - my_safe_printf_stderr("%s", "\n"); - if (nbytes == -1) - my_safe_printf_stderr("Can't read from address %p\n", addr); + my_safe_printf_stderr("Can't read from address %p", addr); close(fd); @@ -145,7 +142,7 @@ void my_safe_print_str(const char* val, int max_len) if (!PTR_SANE(val)) { - my_safe_printf_stderr("%s", "is an invalid pointer\n"); + my_safe_printf_stderr("%s", "is an invalid pointer"); return; } @@ -217,25 +214,52 @@ static void my_demangle_symbols(char **addrs, int n) #endif /* BACKTRACE_DEMANGLE */ +#if HAVE_MY_ADDR_RESOLVE +static int print_with_addr_resolve(void **addrs, int n) +{ + int i; + const char *err; + + if ((err= my_addr_resolve_init())) + { + my_safe_printf_stderr("(my_addr_resolve failure: %s)\n", err); + return 0; + } + + for (i= 0; i < n; i++) + { + my_addr_loc loc; + if (my_addr_resolve(addrs[i], &loc)) + backtrace_symbols_fd(addrs+i, 1, fileno(stderr)); + else + my_safe_printf_stderr("%s:%u(%s)[%p]\n", + loc.file, loc.line, loc.func, addrs[i]); + } + return 1; +} +#endif + void my_print_stacktrace(uchar* stack_bottom, ulong thread_stack) { void *addrs[128]; - char **strings= NULL; + char **strings __attribute__((unused)) = NULL; int n = backtrace(addrs, array_elements(addrs)); my_safe_printf_stderr("stack_bottom = %p thread_stack 0x%lx\n", stack_bottom, thread_stack); +#if HAVE_MY_ADDR_RESOLVE + if (print_with_addr_resolve(addrs, n)) + return; +#endif #if BACKTRACE_DEMANGLE if ((strings= backtrace_symbols(addrs, n))) { my_demangle_symbols(strings, n); free(strings); + return; } #endif #if HAVE_BACKTRACE_SYMBOLS_FD - if (!strings) - { - backtrace_symbols_fd(addrs, n, fileno(stderr)); - } + backtrace_symbols_fd(addrs, n, fileno(stderr)); #endif } @@ -441,6 +465,7 @@ void my_write_core(int sig) #include <dbghelp.h> #include <tlhelp32.h> +#include <my_sys.h> #if _MSC_VER #pragma comment(lib, "dbghelp") #endif @@ -474,10 +499,11 @@ static void add_to_symbol_path(char *path, size_t path_buffer_size, } /* - Get symbol path - semicolon-separated list of directories to search for debug - symbols. We expect PDB in the same directory as corresponding exe or dll, - so the path is build from directories of the loaded modules. If environment - variable _NT_SYMBOL_PATH is set, it's value appended to the symbol search path + Get symbol path - semicolon-separated list of directories to search + for debug symbols. We expect PDB in the same directory as + corresponding exe or dll, so the path is build from directories of + the loaded modules. If environment variable _NT_SYMBOL_PATH is set, + it's value appended to the symbol search path */ static void get_symbol_path(char *path, size_t size) { @@ -615,9 +641,9 @@ void my_print_stacktrace(uchar* unused1, ulong unused2) if(!have_module) { /* - ModuleInfo structure has been "compatibly" extended in releases after XP, - and its size was increased. To make XP dbghelp.dll function - happy, pretend passing the old structure. + ModuleInfo structure has been "compatibly" extended in + releases after XP, and its size was increased. To make XP + dbghelp.dll function happy, pretend passing the old structure. */ module.SizeOfStruct= MODULE64_SIZE_WINXP; have_module= SymGetModuleInfo64(hProcess, addr, &module); @@ -628,14 +654,9 @@ void my_print_stacktrace(uchar* unused1, ulong unused2) &(package.sym)); have_source= SymGetLineFromAddr64(hProcess, addr, &line_offset, &line); - my_safe_printf_stderr("%p ", addr); if(have_module) { - char *base_image_name= strrchr(module.ImageName, '\\'); - if(base_image_name) - base_image_name++; - else - base_image_name= module.ImageName; + const char *base_image_name= my_basename(module.ImageName); my_safe_printf_stderr("%s!", base_image_name); } if(have_symbol) @@ -646,11 +667,7 @@ void my_print_stacktrace(uchar* unused1, ulong unused2) if(have_source) { - char *base_file_name= strrchr(line.FileName, '\\'); - if(base_file_name) - base_file_name++; - else - base_file_name= line.FileName; + const char *base_file_name= my_basename(line.FileName); my_safe_printf_stderr("[%s:%u]", base_file_name, line.LineNumber); } @@ -719,198 +736,16 @@ void my_safe_print_str(const char *val, int len) } __except(EXCEPTION_EXECUTE_HANDLER) { - my_safe_printf_stderr("%s", "is an invalid string pointer\n"); + my_safe_printf_stderr("%s", "is an invalid string pointer"); } } #endif /*__WIN__*/ -#ifdef __WIN__ -size_t my_write_stderr(const void *buf, size_t count) -{ - DWORD bytes_written; - SetFilePointer(GetStdHandle(STD_ERROR_HANDLE), 0, NULL, FILE_END); - WriteFile(GetStdHandle(STD_ERROR_HANDLE), buf, count, &bytes_written, NULL); - return bytes_written; -} -#else size_t my_write_stderr(const void *buf, size_t count) { return (size_t) write(STDERR_FILENO, buf, count); } -#endif - - -static const char digits[]= "0123456789abcdef"; - -char *my_safe_utoa(int base, ulonglong val, char *buf) -{ - *buf--= 0; - do { - *buf--= digits[val % base]; - } while ((val /= base) != 0); - return buf + 1; -} - - -char *my_safe_itoa(int base, longlong val, char *buf) -{ - char *orig_buf= buf; - const my_bool is_neg= (val < 0); - *buf--= 0; - - if (is_neg) - val= -val; - if (is_neg && base == 16) - { - int ix; - val-= 1; - for (ix= 0; ix < 16; ++ix) - buf[-ix]= '0'; - } - - do { - *buf--= digits[val % base]; - } while ((val /= base) != 0); - - if (is_neg && base == 10) - *buf--= '-'; - - if (is_neg && base == 16) - { - int ix; - buf= orig_buf - 1; - for (ix= 0; ix < 16; ++ix, --buf) - { - switch (*buf) - { - case '0': *buf= 'f'; break; - case '1': *buf= 'e'; break; - case '2': *buf= 'd'; break; - case '3': *buf= 'c'; break; - case '4': *buf= 'b'; break; - case '5': *buf= 'a'; break; - case '6': *buf= '9'; break; - case '7': *buf= '8'; break; - case '8': *buf= '7'; break; - case '9': *buf= '6'; break; - case 'a': *buf= '5'; break; - case 'b': *buf= '4'; break; - case 'c': *buf= '3'; break; - case 'd': *buf= '2'; break; - case 'e': *buf= '1'; break; - case 'f': *buf= '0'; break; - } - } - } - return buf+1; -} - - -static const char *check_longlong(const char *fmt, my_bool *have_longlong) -{ - *have_longlong= FALSE; - if (*fmt == 'l') - { - fmt++; - if (*fmt != 'l') - *have_longlong= (sizeof(long) == sizeof(longlong)); - else - { - fmt++; - *have_longlong= TRUE; - } - } - return fmt; -} - -static size_t my_safe_vsnprintf(char *to, size_t size, - const char* format, va_list ap) -{ - char *start= to; - char *end= start + size - 1; - for (; *format; ++format) - { - my_bool have_longlong = FALSE; - if (*format != '%') - { - if (to == end) /* end of buffer */ - break; - *to++= *format; /* copy ordinary char */ - continue; - } - ++format; /* skip '%' */ - - format= check_longlong(format, &have_longlong); - - switch (*format) - { - case 'd': - case 'i': - case 'u': - case 'x': - case 'p': - { - longlong ival= 0; - ulonglong uval = 0; - if (*format == 'p') - have_longlong= (sizeof(void *) == sizeof(longlong)); - if (have_longlong) - { - if (*format == 'u') - uval= va_arg(ap, ulonglong); - else - ival= va_arg(ap, longlong); - } - else - { - if (*format == 'u') - uval= va_arg(ap, unsigned int); - else - ival= va_arg(ap, int); - } - - { - char buff[22]; - const int base= (*format == 'x' || *format == 'p') ? 16 : 10; - char *val_as_str= (*format == 'u') ? - my_safe_utoa(base, uval, &buff[sizeof(buff)-1]) : - my_safe_itoa(base, ival, &buff[sizeof(buff)-1]); - - /* Strip off "ffffffff" if we have 'x' format without 'll' */ - if (*format == 'x' && !have_longlong && ival < 0) - val_as_str+= 8; - - while (*val_as_str && to < end) - *to++= *val_as_str++; - continue; - } - } - case 's': - { - const char *val= va_arg(ap, char*); - if (!val) - val= "(null)"; - while (*val && to < end) - *to++= *val++; - continue; - } - } - } - *to= 0; - return to - start; -} - - -size_t my_safe_snprintf(char* to, size_t n, const char* fmt, ...) -{ - size_t result; - va_list args; - va_start(args,fmt); - result= my_safe_vsnprintf(to, n, fmt, args); - va_end(args); - return result; -} size_t my_safe_printf_stderr(const char* fmt, ...) @@ -919,7 +754,7 @@ size_t my_safe_printf_stderr(const char* fmt, ...) size_t result; va_list args; va_start(args,fmt); - result= my_safe_vsnprintf(to, sizeof(to), fmt, args); + result= my_vsnprintf(to, sizeof(to), fmt, args); va_end(args); my_write_stderr(to, result); return result; diff --git a/mysys/string.c b/mysys/string.c index 870963abd0d..d9791341c60 100644 --- a/mysys/string.c +++ b/mysys/string.c @@ -98,20 +98,21 @@ my_bool dynstr_append_mem(DYNAMIC_STRING *str, const char *append, size_t length) { char *new_ptr; + DBUG_ENTER("dynstr_append_mem"); if (str->length+length >= str->max_length) { size_t new_length=(str->length+length+str->alloc_increment)/ str->alloc_increment; new_length*=str->alloc_increment; if (!(new_ptr=(char*) my_realloc(str->str,new_length,MYF(MY_WME)))) - return TRUE; + DBUG_RETURN(TRUE); str->str=new_ptr; str->max_length=new_length; } memcpy(str->str + str->length,append,length); str->length+=length; str->str[str->length]=0; /* Safety for C programs */ - return FALSE; + DBUG_RETURN(FALSE); } @@ -180,3 +181,15 @@ void dynstr_free(DYNAMIC_STRING *str) my_free(str->str); str->str= NULL; } + + +/* Give over the control of the dynamic string to caller */ + +void dynstr_reassociate(DYNAMIC_STRING *str, char **ptr, size_t *length, + size_t *alloc_length) +{ + *ptr= str->str; + *length= str->length; + *alloc_length= str->max_length; + str->str=0; +} diff --git a/mysys/test_thr_mutex.c b/mysys/test_thr_mutex.c new file mode 100644 index 00000000000..0bd14a0d31b --- /dev/null +++ b/mysys/test_thr_mutex.c @@ -0,0 +1,162 @@ +/* Copyright (C) 2008 Sun Microsystems, Inc + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* Testing of deadlock detector */ + +#include <my_global.h> +#include <mysys_priv.h> + + +int main(int argc __attribute__((unused)), char** argv) +{ + pthread_mutex_t LOCK_A, LOCK_B, LOCK_C, LOCK_D, LOCK_E, LOCK_F, LOCK_G; + pthread_mutex_t LOCK_H, LOCK_I; + MY_INIT(argv[0]); + DBUG_ENTER("main"); + + DBUG_PUSH("d:t:O,/tmp/trace"); + printf("This program is testing the mutex deadlock detection.\n" + "It should print out different failures of wrong mutex usage" + "on stderr\n\n"); + + safe_mutex_deadlock_detector= 1; + pthread_mutex_init(&LOCK_A, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&LOCK_B, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&LOCK_C, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&LOCK_D, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&LOCK_E, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&LOCK_F, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&LOCK_G, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&LOCK_H, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&LOCK_I, MY_MUTEX_INIT_FAST); + + printf("Testing A->B and B->A\n"); + fflush(stdout); + pthread_mutex_lock(&LOCK_A); + pthread_mutex_lock(&LOCK_B); + pthread_mutex_unlock(&LOCK_A); + pthread_mutex_unlock(&LOCK_B); + + /* Test different (wrong) lock order */ + pthread_mutex_lock(&LOCK_B); + pthread_mutex_lock(&LOCK_A); /* Should give warning */ + + pthread_mutex_unlock(&LOCK_A); + pthread_mutex_unlock(&LOCK_B); + + /* Check that we don't get another warning for same lock */ + printf("Testing A->B and B->A again (should not give a warning)\n"); + pthread_mutex_lock(&LOCK_B); + pthread_mutex_lock(&LOCK_A); + pthread_mutex_unlock(&LOCK_A); + pthread_mutex_unlock(&LOCK_B); + + /* + Test of ring with many mutex + We also unlock mutex in different orders to get the unlock code properly + tested. + */ + printf("Testing A->C and C->D and D->A\n"); + pthread_mutex_lock(&LOCK_A); + pthread_mutex_lock(&LOCK_C); + pthread_mutex_unlock(&LOCK_A); + pthread_mutex_unlock(&LOCK_C); + pthread_mutex_lock(&LOCK_C); + pthread_mutex_lock(&LOCK_D); + pthread_mutex_unlock(&LOCK_D); + pthread_mutex_unlock(&LOCK_C); + + pthread_mutex_lock(&LOCK_D); + pthread_mutex_lock(&LOCK_A); /* Should give warning */ + + pthread_mutex_unlock(&LOCK_A); + pthread_mutex_unlock(&LOCK_D); + + printf("Testing E -> F ; H -> I ; F -> H ; H -> I -> E\n"); + fflush(stdout); + + pthread_mutex_lock(&LOCK_E); + pthread_mutex_lock(&LOCK_F); + pthread_mutex_unlock(&LOCK_E); + pthread_mutex_unlock(&LOCK_F); + pthread_mutex_lock(&LOCK_H); + pthread_mutex_lock(&LOCK_I); + pthread_mutex_unlock(&LOCK_I); + pthread_mutex_unlock(&LOCK_H); + pthread_mutex_lock(&LOCK_F); + pthread_mutex_lock(&LOCK_H); + pthread_mutex_unlock(&LOCK_H); + pthread_mutex_unlock(&LOCK_F); + + pthread_mutex_lock(&LOCK_H); + pthread_mutex_lock(&LOCK_I); + pthread_mutex_lock(&LOCK_E); /* Should give warning */ + + pthread_mutex_unlock(&LOCK_E); + pthread_mutex_unlock(&LOCK_I); + pthread_mutex_unlock(&LOCK_H); + + printf("\nFollowing shouldn't give any warnings\n"); + printf("Testing A->B and B->A without deadlock detection\n"); + fflush(stdout); + + /* Reinitialize mutex to get rid of old wrong usage markers */ + pthread_mutex_destroy(&LOCK_A); + pthread_mutex_destroy(&LOCK_B); + pthread_mutex_init(&LOCK_A, MY_MUTEX_INIT_FAST); + pthread_mutex_init(&LOCK_B, MY_MUTEX_INIT_FAST); + + /* Start testing */ + my_pthread_mutex_lock(&LOCK_A, MYF(MYF_NO_DEADLOCK_DETECTION)); + pthread_mutex_lock(&LOCK_B); + pthread_mutex_unlock(&LOCK_A); + pthread_mutex_unlock(&LOCK_B); + + pthread_mutex_lock(&LOCK_A); + my_pthread_mutex_lock(&LOCK_B, MYF(MYF_NO_DEADLOCK_DETECTION)); + pthread_mutex_unlock(&LOCK_A); + pthread_mutex_unlock(&LOCK_B); + + printf("Testing A -> C ; B -> C ; A->B\n"); + fflush(stdout); + pthread_mutex_lock(&LOCK_A); + pthread_mutex_lock(&LOCK_C); + pthread_mutex_unlock(&LOCK_C); + pthread_mutex_unlock(&LOCK_A); + + pthread_mutex_lock(&LOCK_B); + pthread_mutex_lock(&LOCK_C); + pthread_mutex_unlock(&LOCK_C); + pthread_mutex_unlock(&LOCK_B); + + pthread_mutex_lock(&LOCK_A); + pthread_mutex_lock(&LOCK_B); + pthread_mutex_unlock(&LOCK_B); + pthread_mutex_unlock(&LOCK_A); + + /* Cleanup */ + pthread_mutex_destroy(&LOCK_A); + pthread_mutex_destroy(&LOCK_B); + pthread_mutex_destroy(&LOCK_C); + pthread_mutex_destroy(&LOCK_D); + pthread_mutex_destroy(&LOCK_E); + pthread_mutex_destroy(&LOCK_F); + pthread_mutex_destroy(&LOCK_G); + pthread_mutex_destroy(&LOCK_H); + pthread_mutex_destroy(&LOCK_I); + + my_end(MY_DONT_FREE_DBUG); + exit(0); +} diff --git a/mysys/thr_alarm.c b/mysys/thr_alarm.c index 5fb4e544411..d0bb2f1ef4c 100644 --- a/mysys/thr_alarm.c +++ b/mysys/thr_alarm.c @@ -1,4 +1,5 @@ -/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates + Copyright (c) 2012, 2014, SkySQL Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -37,11 +38,24 @@ uint thr_client_alarm; static int alarm_aborted=1; /* No alarm thread */ -my_bool thr_alarm_inited= 0; +my_bool thr_alarm_inited= 0, my_disable_thr_alarm= 0; volatile my_bool alarm_thread_running= 0; time_t next_alarm_expire_time= ~ (time_t) 0; static sig_handler process_alarm_part2(int sig); +#ifdef DBUG_OFF +#define reset_index_in_queue(alarm_data) +#else +#define reset_index_in_queue(alarm_data) alarm_data->index_in_queue= 0; +#endif /* DBUG_OFF */ + +#ifndef USE_ONE_SIGNAL_HAND +#define one_signal_hand_sigmask(A,B,C) pthread_sigmask((A), (B), (C)) +#else +#define one_signal_hand_sigmask(A,B,C) +#endif + + #if !defined(__WIN__) static mysql_mutex_t LOCK_alarm; @@ -75,8 +89,9 @@ void init_thr_alarm(uint max_alarms) DBUG_ENTER("init_thr_alarm"); alarm_aborted=0; next_alarm_expire_time= ~ (time_t) 0; - init_queue_ex(&alarm_queue, max_alarms + 1, offsetof(ALARM,expire_time), 0, - compare_ulong, NullS, MY_THR_ALARM_QUEUE_EXTENT); + init_queue(&alarm_queue, max_alarms+1, offsetof(ALARM,expire_time), 0, + compare_ulong, NullS, offsetof(ALARM, index_in_queue)+1, + MY_THR_ALARM_QUEUE_EXTENT); sigfillset(&full_signal_set); /* Neaded to block signals */ mysql_mutex_init(key_LOCK_alarm, &LOCK_alarm, MY_MUTEX_INIT_FAST); mysql_cond_init(key_COND_alarm, &COND_alarm, NULL); @@ -156,7 +171,7 @@ void resize_thr_alarm(uint max_alarms) my_bool thr_alarm(thr_alarm_t *alrm, uint sec, ALARM *alarm_data) { - time_t now; + time_t now, next; #ifndef USE_ONE_SIGNAL_HAND sigset_t old_mask; #endif @@ -165,69 +180,66 @@ my_bool thr_alarm(thr_alarm_t *alrm, uint sec, ALARM *alarm_data) DBUG_ENTER("thr_alarm"); DBUG_PRINT("enter",("thread: %s sec: %d",my_thread_name(),sec)); - now= my_time(0); -#ifndef USE_ONE_SIGNAL_HAND - pthread_sigmask(SIG_BLOCK,&full_signal_set,&old_mask); -#endif - mysql_mutex_lock(&LOCK_alarm); /* Lock from threads & alarms */ - if (alarm_aborted > 0) + if (my_disable_thr_alarm) + { + (*alrm)= &alarm_data->alarmed; + alarm_data->alarmed= 1; /* Abort if interrupted */ + DBUG_RETURN(0); + } + + if (unlikely(alarm_aborted)) { /* No signal thread */ DBUG_PRINT("info", ("alarm aborted")); - *alrm= 0; /* No alarm */ - mysql_mutex_unlock(&LOCK_alarm); -#ifndef USE_ONE_SIGNAL_HAND - pthread_sigmask(SIG_SETMASK,&old_mask,NULL); -#endif - DBUG_RETURN(1); - } - if (alarm_aborted < 0) + if (alarm_aborted > 0) + goto abort_no_unlock; sec= 1; /* Abort mode */ - - if (alarm_queue.elements >= max_used_alarms) - { - max_used_alarms=alarm_queue.elements+1; } - reschedule= (ulong) next_alarm_expire_time > (ulong) now + sec; + + now= my_time(0); if (!alarm_data) { if (!(alarm_data=(ALARM*) my_malloc(sizeof(ALARM),MYF(MY_WME)))) - { - DBUG_PRINT("info", ("failed my_malloc()")); - *alrm= 0; /* No alarm */ - mysql_mutex_unlock(&LOCK_alarm); -#ifndef USE_ONE_SIGNAL_HAND - pthread_sigmask(SIG_SETMASK,&old_mask,NULL); -#endif - DBUG_RETURN(1); - } - alarm_data->malloced=1; + goto abort_no_unlock; + alarm_data->malloced= 1; } else - alarm_data->malloced=0; - alarm_data->expire_time=now+sec; - alarm_data->alarmed=0; + alarm_data->malloced= 0; + next= now + sec; + alarm_data->expire_time= next; + alarm_data->alarmed= 0; alarm_data->thread= current_my_thread_var->pthread_self; alarm_data->thread_id= current_my_thread_var->id; + + one_signal_hand_sigmask(SIG_BLOCK,&full_signal_set,&old_mask); + mysql_mutex_lock(&LOCK_alarm); /* Lock from threads & alarms */ + if (alarm_queue.elements >= max_used_alarms) + { + max_used_alarms=alarm_queue.elements+1; + } + reschedule= (ulong) next_alarm_expire_time > (ulong) next; queue_insert_safe(&alarm_queue, (uchar*) alarm_data); + assert(alarm_data->index_in_queue > 0); /* Reschedule alarm if the current one has more than sec left */ - if (reschedule) + if (unlikely(reschedule)) { DBUG_PRINT("info", ("reschedule")); if (pthread_equal(pthread_self(),alarm_thread)) { alarm(sec); /* purecov: inspected */ - next_alarm_expire_time= now + sec; + next_alarm_expire_time= next; } else reschedule_alarms(); /* Reschedule alarms */ } mysql_mutex_unlock(&LOCK_alarm); -#ifndef USE_ONE_SIGNAL_HAND - pthread_sigmask(SIG_SETMASK,&old_mask,NULL); -#endif + one_signal_hand_sigmask(SIG_SETMASK,&old_mask,NULL); (*alrm)= &alarm_data->alarmed; DBUG_RETURN(0); + +abort_no_unlock: + *alrm= 0; /* No alarm */ + DBUG_RETURN(1); } @@ -241,41 +253,20 @@ void thr_end_alarm(thr_alarm_t *alarmed) #ifndef USE_ONE_SIGNAL_HAND sigset_t old_mask; #endif - uint i, found=0; DBUG_ENTER("thr_end_alarm"); -#ifndef USE_ONE_SIGNAL_HAND - pthread_sigmask(SIG_BLOCK,&full_signal_set,&old_mask); -#endif - mysql_mutex_lock(&LOCK_alarm); - + if (my_disable_thr_alarm) + DBUG_VOID_RETURN; + one_signal_hand_sigmask(SIG_BLOCK,&full_signal_set,&old_mask); alarm_data= (ALARM*) ((uchar*) *alarmed - offsetof(ALARM,alarmed)); - for (i=0 ; i < alarm_queue.elements ; i++) - { - if ((ALARM*) queue_element(&alarm_queue,i) == alarm_data) - { - queue_remove(&alarm_queue,i),MYF(0); - if (alarm_data->malloced) - my_free(alarm_data); - found++; -#ifdef DBUG_OFF - break; -#endif - } - } - DBUG_ASSERT(!*alarmed || found == 1); - if (!found) - { - if (*alarmed) - fprintf(stderr,"Warning: Didn't find alarm 0x%lx in queue of %d alarms\n", - (long) *alarmed, alarm_queue.elements); - DBUG_PRINT("warning",("Didn't find alarm 0x%lx in queue\n", - (long) *alarmed)); - } + mysql_mutex_lock(&LOCK_alarm); + DBUG_ASSERT(alarm_data->index_in_queue != 0); + DBUG_ASSERT(queue_element(&alarm_queue, alarm_data->index_in_queue) == + alarm_data); + queue_remove(&alarm_queue, alarm_data->index_in_queue); mysql_mutex_unlock(&LOCK_alarm); -#ifndef USE_ONE_SIGNAL_HAND - pthread_sigmask(SIG_SETMASK,&old_mask,NULL); -#endif + one_signal_hand_sigmask(SIG_SETMASK,&old_mask,NULL); + reset_index_in_queue(alarm_data); DBUG_VOID_RETURN; } @@ -338,12 +329,13 @@ static sig_handler process_alarm_part2(int sig __attribute__((unused))) #if defined(MAIN) && !defined(__bsdi__) printf("process_alarm\n"); fflush(stdout); #endif - if (alarm_queue.elements) + if (likely(alarm_queue.elements)) { - if (alarm_aborted) + if (unlikely(alarm_aborted)) { uint i; - for (i=0 ; i < alarm_queue.elements ;) + for (i= queue_first_element(&alarm_queue) ; + i <= queue_last_element(&alarm_queue) ;) { alarm_data=(ALARM*) queue_element(&alarm_queue,i); alarm_data->alarmed=1; /* Info to thread */ @@ -354,6 +346,7 @@ static sig_handler process_alarm_part2(int sig __attribute__((unused))) printf("Warning: pthread_kill couldn't find thread!!!\n"); #endif queue_remove(&alarm_queue,i); /* No thread. Remove alarm */ + reset_index_in_queue(alarm_data); } else i++; /* Signal next thread */ @@ -365,8 +358,8 @@ static sig_handler process_alarm_part2(int sig __attribute__((unused))) } else { - ulong now=(ulong) my_time(0); - ulong next=now+10-(now%10); + time_t now= my_time(0); + time_t next= now+10-(now%10); while ((alarm_data=(ALARM*) queue_top(&alarm_queue))->expire_time <= now) { alarm_data->alarmed=1; /* Info to thread */ @@ -376,15 +369,16 @@ static sig_handler process_alarm_part2(int sig __attribute__((unused))) { #ifdef MAIN printf("Warning: pthread_kill couldn't find thread!!!\n"); -#endif - queue_remove(&alarm_queue,0); /* No thread. Remove alarm */ +#endif /* MAIN */ + queue_remove_top(&alarm_queue); /* No thread. Remove alarm */ + reset_index_in_queue(alarm_data); if (!alarm_queue.elements) break; } else { alarm_data->expire_time=next; - queue_replaced(&alarm_queue); + queue_replace_top(&alarm_queue); } } #ifndef USE_ALARM_THREAD @@ -477,21 +471,27 @@ void end_thr_alarm(my_bool free_structures) void thr_alarm_kill(my_thread_id thread_id) { uint i; + DBUG_ENTER("thr_alarm_kill"); + if (alarm_aborted) return; mysql_mutex_lock(&LOCK_alarm); - for (i=0 ; i < alarm_queue.elements ; i++) + for (i= queue_first_element(&alarm_queue) ; + i <= queue_last_element(&alarm_queue); + i++) { - if (((ALARM*) queue_element(&alarm_queue,i))->thread_id == thread_id) + ALARM *element= (ALARM*) queue_element(&alarm_queue,i); + if (element->thread_id == thread_id) { - ALARM *tmp=(ALARM*) queue_remove(&alarm_queue,i); - tmp->expire_time=0; - queue_insert(&alarm_queue,(uchar*) tmp); + DBUG_PRINT("info", ("found thread; Killing it")); + element->expire_time= 0; + queue_replace(&alarm_queue, i); reschedule_alarms(); break; } } mysql_mutex_unlock(&LOCK_alarm); + DBUG_VOID_RETURN; } @@ -502,7 +502,7 @@ void thr_alarm_info(ALARM_INFO *info) info->max_used_alarms= max_used_alarms; if ((info->active_alarms= alarm_queue.elements)) { - ulong now=(ulong) my_time(0); + time_t now= my_time(0); long time_diff; ALARM *alarm_data= (ALARM*) queue_top(&alarm_queue); time_diff= (long) (alarm_data->expire_time - now); @@ -550,7 +550,7 @@ static void *alarm_handler(void *arg __attribute__((unused))) { if (alarm_queue.elements) { - ulong sleep_time,now= my_time(0); + time_t sleep_time,now= my_time(0); if (alarm_aborted) sleep_time=now+1; else @@ -593,93 +593,6 @@ static void *alarm_handler(void *arg __attribute__((unused))) return 0; /* Impossible */ } #endif /* USE_ALARM_THREAD */ - -/***************************************************************************** - thr_alarm for win95 -*****************************************************************************/ - -#else /* __WIN__ */ - -void thr_alarm_kill(my_thread_id thread_id) -{ - /* Can't do this yet */ -} - -sig_handler process_alarm(int sig __attribute__((unused))) -{ - /* Can't do this yet */ -} - - -my_bool thr_alarm(thr_alarm_t *alrm, uint sec, ALARM *alarm) -{ - (*alrm)= &alarm->alarmed; - if (alarm_aborted) - { - alarm->alarmed.crono=0; - return 1; - } - if (!(alarm->alarmed.crono=SetTimer((HWND) NULL,0, sec*1000, - (TIMERPROC) NULL))) - return 1; - return 0; -} - - -my_bool thr_got_alarm(thr_alarm_t *alrm_ptr) -{ - thr_alarm_t alrm= *alrm_ptr; - MSG msg; - if (alrm->crono) - { - PeekMessage(&msg,NULL,WM_TIMER,WM_TIMER,PM_REMOVE) ; - if (msg.message == WM_TIMER || alarm_aborted) - { - KillTimer(NULL, alrm->crono); - alrm->crono = 0; - } - } - return !alrm->crono || alarm_aborted; -} - - -void thr_end_alarm(thr_alarm_t *alrm_ptr) -{ - thr_alarm_t alrm= *alrm_ptr; - /* alrm may be zero if thr_alarm aborted with an error */ - if (alrm && alrm->crono) - - { - KillTimer(NULL, alrm->crono); - alrm->crono = 0; - } -} - -void end_thr_alarm(my_bool free_structures) -{ - DBUG_ENTER("end_thr_alarm"); - alarm_aborted=1; /* No more alarms */ - DBUG_VOID_RETURN; -} - -void init_thr_alarm(uint max_alarm) -{ - DBUG_ENTER("init_thr_alarm"); - alarm_aborted=0; /* Yes, Gimmie alarms */ - DBUG_VOID_RETURN; -} - -void thr_alarm_info(ALARM_INFO *info) -{ - bzero((char*) info, sizeof(*info)); -} - -void resize_thr_alarm(uint max_alarms) -{ -} - -#endif /* __WIN__ */ - #endif /**************************************************************************** @@ -785,19 +698,6 @@ static void *test_thread(void *arg) return 0; } -#ifdef USE_ONE_SIGNAL_HAND -static sig_handler print_signal_warning(int sig) -{ - printf("Warning: Got signal %d from thread %s\n",sig,my_thread_name()); - fflush(stdout); -#ifdef SIGNAL_HANDLER_RESET_ON_DELIVERY - my_sigset(sig,print_signal_warning); /* int. thread system calls */ -#endif - if (sig == SIGALRM) - alarm(2); /* reschedule alarm */ -} -#endif /* USE_ONE_SIGNAL_HAND */ - static void *signal_hand(void *arg __attribute__((unused))) { @@ -963,4 +863,5 @@ int main(int argc __attribute__((unused)),char **argv __attribute__((unused))) } #endif /* !defined(DONT_USE_ALARM_THREAD) */ +#endif /* WIN */ #endif /* MAIN */ diff --git a/mysys/thr_lock.c b/mysys/thr_lock.c index bc2a29b4296..a7cbfa07db2 100644 --- a/mysys/thr_lock.c +++ b/mysys/thr_lock.c @@ -1,4 +1,6 @@ -/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2000, 2011, Oracle and/or its affiliates + Copyright (c) 2012, Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,7 +26,7 @@ Locks are prioritized according to: The current lock types are: -TL_READ # Low priority read +TL_READ # Low priority read TL_READ_WITH_SHARED_LOCKS TL_READ_HIGH_PRIORITY # High priority read TL_READ_NO_INSERT # Read without concurrent inserts @@ -56,8 +58,17 @@ check_status: In MyISAM this is a simple check if the insert can be done at the end of the datafile. update_status: - Before a write lock is released, this function is called. - In MyISAM this functions updates the count and length of the datafile + in thr_reschedule_write_lock(), when an insert delayed thread + downgrades TL_WRITE lock to TL_WRITE_DELAYED, to allow SELECT + threads to proceed. + A storage engine should also call update_status internally + in the ::external_lock(F_UNLCK) method. + In MyISAM and CSV this functions updates the length of the datafile. + MySQL does in some exceptional cases (when doing DLL statements on + open tables calls thr_unlock() followed by thr_lock() without calling + ::external_lock() in between. In this case thr_unlock() is called with + the THR_UNLOCK_UPDATE_STATUS flag and thr_unlock() will call + update_status for write locks. get_status: When one gets a lock this functions is called. In MyISAM this stores the number of rows and size of the datafile @@ -66,6 +77,8 @@ get_status: The lock algorithm allows one to have one TL_WRITE_CONCURRENT_INSERT or one TL_WRITE_DELAYED lock at the same time as multiple read locks. +In addition, if lock->allow_multiple_concurrent_insert is set then there can +be any number of TL_WRITE_CONCURRENT_INSERT locks aktive at the same time. */ #if !defined(MAIN) && !defined(DBUG_OFF) && !defined(EXTRA_DEBUG) @@ -83,8 +96,8 @@ ulong locks_immediate = 0L, locks_waited = 0L; enum thr_lock_type thr_upgraded_concurrent_insert_lock = TL_WRITE; /* The following constants are only for debug output */ -#define MAX_THREADS 100 -#define MAX_LOCKS 100 +#define MAX_THREADS 1000 +#define MAX_LOCKS 1000 LIST *thr_lock_thread_list; /* List of threads in use */ @@ -105,8 +118,45 @@ static inline mysql_cond_t *get_cond(void) return &my_thread_var->suspend; } + +/* + Sort locks in priority order + + LOCK_CMP() + A First lock + B Second lock + + Return: + 0 if A >= B + 1 if A < B + + Priority for locks (decides in which order locks are locked) + We want all write locks to be first, followed by read locks. + Locks from MERGE tables has a little lower priority than other + locks, to allow one to release merge tables without having + to unlock and re-lock other locks. + The lower the number, the higher the priority for the lock. + For MERGE tables we add 2 (THR_LOCK_MERGE_PRIV) to the lock priority. + THR_LOCK_LATE_PRIV (1) is used when one locks other tables to be merged + with existing locks. This way we prioritize the original locks over the + new locks. +*/ + + +static inline int LOCK_CMP(THR_LOCK_DATA *a, THR_LOCK_DATA *b) +{ + if (a->lock != b->lock) + return a->lock < b->lock; + + if (a->type != b->type) + return a->type > b->type; + + return a->priority < b->priority; +} + + /* -** For the future (now the thread specific cond is alloced by my_pthread.c) + For the future (now the thread specific cond is alloced by my_pthread.c) */ my_bool init_thr_lock() @@ -127,21 +177,37 @@ thr_lock_owner_equal(THR_LOCK_INFO *rhs, THR_LOCK_INFO *lhs) static uint found_errors=0; static int check_lock(struct st_lock_list *list, const char* lock_type, - const char *where, my_bool same_owner, my_bool no_cond) + const char *where, my_bool same_owner, my_bool no_cond, + my_bool read_lock) { THR_LOCK_DATA *data,**prev; uint count=0; - THR_LOCK_INFO *UNINIT_VAR(first_owner); prev= &list->data; if (list->data) { - enum thr_lock_type last_lock_type=list->data->type; + enum thr_lock_type last_lock_type= list->data->type; + THR_LOCK_INFO *first_owner= list->data->owner; - if (same_owner && list->data) - first_owner= list->data->owner; for (data=list->data; data && count++ < MAX_LOCKS ; data=data->next) { + if (data->type == TL_UNLOCK) + { + fprintf(stderr, + "Warning: Found unlocked lock at %s: %s\n", + lock_type, where); + return 1; + } + if ((read_lock && data->type > TL_READ_NO_INSERT) || + (!read_lock && data->type <= TL_READ_NO_INSERT)) + { + fprintf(stderr, + "Warning: Found %s lock in %s queue at %s: %s\n", + read_lock ? "write" : "read", + read_lock ? "read" : "write", + lock_type, where); + return 1; + } if (data->type != last_lock_type) last_lock_type=TL_IGNORE; if (data->prev != prev) @@ -153,11 +219,14 @@ static int check_lock(struct st_lock_list *list, const char* lock_type, } if (same_owner && !thr_lock_owner_equal(data->owner, first_owner) && - last_lock_type != TL_WRITE_ALLOW_WRITE) + last_lock_type != TL_WRITE_ALLOW_WRITE && + last_lock_type != TL_WRITE_CONCURRENT_INSERT) { fprintf(stderr, - "Warning: Found locks from different threads in %s: %s\n", - lock_type,where); + "Warning: Found locks from different threads for lock '%s' in '%s' at '%s'. org_lock_type: %d last_lock_type: %d new_lock_type: %d\n", + data->lock->name ? data->lock->name : "", + lock_type, where, list->data->type, last_lock_type, + data->type); return 1; } if (no_cond && data->cond) @@ -187,6 +256,7 @@ static int check_lock(struct st_lock_list *list, const char* lock_type, static void check_locks(THR_LOCK *lock, const char *where, + enum thr_lock_type type, my_bool allow_no_locks) { uint old_found_errors=found_errors; @@ -194,22 +264,27 @@ static void check_locks(THR_LOCK *lock, const char *where, if (found_errors < MAX_FOUND_ERRORS) { - if (check_lock(&lock->write,"write",where,1,1) | - check_lock(&lock->write_wait,"write_wait",where,0,0) | - check_lock(&lock->read,"read",where,0,1) | - check_lock(&lock->read_wait,"read_wait",where,0,0)) + if (check_lock(&lock->write,"write",where,1,1,0) | + check_lock(&lock->write_wait,"write_wait",where,0,0,0) | + check_lock(&lock->read,"read",where,0,1,1) | + check_lock(&lock->read_wait,"read_wait",where,0,0,1)) + { + DBUG_ASSERT(my_assert_on_error == 0); found_errors++; + } if (found_errors < MAX_FOUND_ERRORS) { - uint count=0; + uint count=0, count2= 0; THR_LOCK_DATA *data; for (data=lock->read.data ; data ; data=data->next) { - if ((int) data->type == (int) TL_READ_NO_INSERT) + count2++; + if (data->type == TL_READ_NO_INSERT) count++; /* Protect against infinite loop. */ - DBUG_ASSERT(count <= lock->read_no_write_count); + DBUG_ASSERT(count <= lock->read_no_write_count && + count2 <= MAX_LOCKS); } if (count != lock->read_no_write_count) { @@ -250,11 +325,32 @@ static void check_locks(THR_LOCK *lock, const char *where, found_errors++; fprintf(stderr, "Warning at '%s': Write lock %d waiting while no exclusive read locks\n",where,(int) lock->write_wait.data->type); + DBUG_PRINT("warning", ("Warning at '%s': Write lock %d waiting while no exclusive read locks\n",where,(int) lock->write_wait.data->type)); } } } else - { /* Have write lock */ + { + /* We have at least one write lock */ + if (lock->write.data->type == TL_WRITE_CONCURRENT_INSERT) + { + THR_LOCK_DATA *data; + uint count= 0; + for (data=lock->write.data->next; + data && count < MAX_LOCKS; + data=data->next) + { + if (data->type != TL_WRITE_CONCURRENT_INSERT) + { + fprintf(stderr, + "Warning at '%s': Found TL_WRITE_CONCURRENT_INSERT lock mixed with other write lock: %d\n", + where, data->type); + DBUG_PRINT("warning", ("Warning at '%s': Found TL_WRITE_CONCURRENT_INSERT lock mixed with other write lock: %d\n", + where, data->type)); + break; + } + } + } if (lock->write_wait.data) { if (!allow_no_locks && @@ -265,26 +361,34 @@ static void check_locks(THR_LOCK *lock, const char *where, fprintf(stderr, "Warning at '%s': Found WRITE_ALLOW_WRITE lock waiting for WRITE_ALLOW_WRITE lock\n", where); + DBUG_PRINT("warning", ("Warning at '%s': Found WRITE_ALLOW_WRITE lock waiting for WRITE_ALLOW_WRITE lock\n", + where)); + } } if (lock->read.data) { - if (!thr_lock_owner_equal(lock->write.data->owner, - lock->read.data->owner) && + THR_LOCK_DATA *data; + for (data=lock->read.data ; data ; data=data->next) + { + if (!thr_lock_owner_equal(lock->write.data->owner, + data->owner) && ((lock->write.data->type > TL_WRITE_DELAYED && lock->write.data->type != TL_WRITE_ONLY) || ((lock->write.data->type == TL_WRITE_CONCURRENT_INSERT || lock->write.data->type == TL_WRITE_ALLOW_WRITE) && - lock->read_no_write_count))) - { - found_errors++; - fprintf(stderr, - "Warning at '%s': Found lock of type %d that is write and read locked\n", - where, lock->write.data->type); - DBUG_PRINT("warning",("At '%s': Found lock of type %d that is write and read locked\n", - where, lock->write.data->type)); - - } + data->type == TL_READ_NO_INSERT))) + { + found_errors++; + fprintf(stderr, + "Warning at '%s' for lock: %d: Found lock of type %d that is write and read locked. Read_no_write_count: %d\n", + where, (int) type, lock->write.data->type, + lock->read_no_write_count); + DBUG_PRINT("warning",("At '%s' for lock %d: Found lock of type %d that is write and read locked\n", + where, (int) type, + lock->write.data->type)); + } + } } if (lock->read_wait.data) { @@ -310,7 +414,7 @@ static void check_locks(THR_LOCK *lock, const char *where, } #else /* EXTRA_DEBUG */ -#define check_locks(A,B,C) +#define check_locks(A,B,C,D) #endif @@ -361,6 +465,8 @@ void thr_lock_data_init(THR_LOCK *lock,THR_LOCK_DATA *data, void *param) data->owner= 0; /* no owner yet */ data->status_param=param; data->cond=0; + data->priority= 0; + data->debug_print_param= 0; } @@ -399,6 +505,7 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, struct timespec wait_timeout; enum enum_thr_lock_result result= THR_LOCK_ABORTED; const char *old_proc_info; + my_bool use_wait_callbacks= FALSE; DBUG_ENTER("wait_for_lock"); /* @@ -438,7 +545,7 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, data->cond= cond; old_proc_info= proc_info_hook(NULL, "Waiting for table level lock", - __func__, __FILE__, __LINE__); + __func__, __FILE__, __LINE__); /* Since before_lock_wait potentially can create more threads to @@ -451,7 +558,10 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, and once after the thread has exited the wait loop. */ if ((!thread_var->abort || in_wait_list) && before_lock_wait) + { + use_wait_callbacks= TRUE; (*before_lock_wait)(); + } set_timespec(wait_timeout, lock_wait_timeout); while (!thread_var->abort || in_wait_list) @@ -489,7 +599,7 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, We call the after_lock_wait callback once the wait loop has finished. */ - if (after_lock_wait) + if (after_lock_wait && use_wait_callbacks) (*after_lock_wait)(); DBUG_PRINT("thr_lock", ("aborted: %d in_wait_list: %d", @@ -504,21 +614,23 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, else wait->last=data->prev; data->type= TL_UNLOCK; /* No lock */ - check_locks(data->lock, "killed or timed out wait_for_lock", 1); + check_locks(data->lock, "killed or timed out wait_for_lock", data->type, + 1); wake_up_waiters(data->lock); } else { DBUG_PRINT("thr_lock", ("lock aborted")); - check_locks(data->lock, "aborted wait_for_lock", 0); + check_locks(data->lock, "aborted wait_for_lock", data->type, 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); + (*data->lock->get_status)(data->status_param, + data->type == TL_WRITE_CONCURRENT_INSERT); + check_locks(data->lock,"got wait_for_lock", data->type, 0); } mysql_mutex_unlock(&data->lock->mutex); @@ -534,25 +646,25 @@ wait_for_lock(struct st_lock_list *wait, THR_LOCK_DATA *data, } -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) +static enum enum_thr_lock_result +thr_lock(THR_LOCK_DATA *data, THR_LOCK_INFO *owner, ulong lock_wait_timeout) { THR_LOCK *lock=data->lock; enum enum_thr_lock_result result= THR_LOCK_SUCCESS; struct st_lock_list *wait_queue; + enum thr_lock_type lock_type= data->type; DBUG_ENTER("thr_lock"); data->next=0; data->cond=0; /* safety */ - data->type=lock_type; data->owner= owner; /* Must be reset ! */ + data->priority&= ~THR_LOCK_LATE_PRIV; 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); + "enter read_lock" : "enter write_lock", lock_type, 0); if ((int) lock_type <= (int) TL_READ_NO_INSERT) { /* Request for READ lock */ @@ -597,7 +709,7 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_INFO *owner, 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); + check_locks(lock,"read lock with old write lock", lock_type, 0); if (lock->get_status) (*lock->get_status)(data->status_param, 0); statistic_increment(locks_immediate,&THR_LOCK_lock); @@ -619,11 +731,11 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_INFO *owner, (*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); + check_locks(lock,"read lock with no write locks", lock_type, 0); + if (lock->get_status) + (*lock->get_status)(data->status_param, 0); statistic_increment(locks_immediate,&THR_LOCK_lock); goto end; } @@ -681,7 +793,8 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_INFO *owner, /* 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. + write locks are of the same type and are either + TL_WRITE_ALLOW_WRITE or TL_WRITE_CONCURRENT_INSERT Note that, since lock requests for the same table are sorted in such way that requests with higher thr_lock_type value come first @@ -712,15 +825,13 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_INFO *owner, lock->write.data->type == TL_WRITE_LOW_PRIORITY)) && lock->write.data->type != TL_WRITE_DELAYED)); - if ((lock_type == TL_WRITE_ALLOW_WRITE && + if (((lock_type == TL_WRITE_ALLOW_WRITE || + (lock_type == TL_WRITE_CONCURRENT_INSERT && + lock->allow_multiple_concurrent_insert)) && ! lock->write_wait.data && - lock->write.data->type == TL_WRITE_ALLOW_WRITE) || + lock->write.data->type == lock_type) || 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)); @@ -728,9 +839,10 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_INFO *owner, (*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); + check_locks(lock,"second write lock", lock_type, 0); + if (lock->get_status) + (*lock->get_status)(data->status_param, + lock_type == TL_WRITE_CONCURRENT_INSERT); statistic_increment(locks_immediate,&THR_LOCK_lock); goto end; } @@ -763,9 +875,9 @@ thr_lock(THR_LOCK_DATA *data, THR_LOCK_INFO *owner, (*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); + if (lock->get_status) + (*lock->get_status)(data->status_param, concurrent_insert); + check_locks(lock,"only write lock", lock_type, 0); statistic_increment(locks_immediate,&THR_LOCK_lock); goto end; } @@ -788,7 +900,7 @@ static inline void free_all_read_locks(THR_LOCK *lock, { THR_LOCK_DATA *data=lock->read_wait.data; - check_locks(lock,"before freeing read locks",1); + check_locks(lock,"before freeing read locks", TL_UNLOCK, 1); /* move all locks from read_wait list to read list */ (*lock->read.last)=data; @@ -830,12 +942,12 @@ static inline void free_all_read_locks(THR_LOCK *lock, *lock->read_wait.last=0; if (!lock->read_wait.data) lock->write_lock_count=0; - check_locks(lock,"after giving read locks",0); + check_locks(lock,"after giving read locks", TL_UNLOCK, 0); } /* Unlock lock and free next thread on same lock */ -void thr_unlock(THR_LOCK_DATA *data) +void thr_unlock(THR_LOCK_DATA *data, uint unlock_flags) { THR_LOCK *lock=data->lock; enum thr_lock_type lock_type=data->type; @@ -843,7 +955,7 @@ void thr_unlock(THR_LOCK_DATA *data) DBUG_PRINT("lock",("data: 0x%lx thread: 0x%lx lock: 0x%lx", (long) data, data->owner->thread_id, (long) lock)); mysql_mutex_lock(&lock->mutex); - check_locks(lock,"start of release lock",0); + check_locks(lock,"start of release lock", lock_type, 0); if (((*data->prev)=data->next)) /* remove from lock-list */ data->next->prev= data->prev; @@ -859,20 +971,24 @@ void thr_unlock(THR_LOCK_DATA *data) } else lock->write.last=data->prev; - if (lock_type >= TL_WRITE_CONCURRENT_INSERT) - { - if (lock->update_status) - (*lock->update_status)(data->status_param); - } - else + + if (unlock_flags & THR_UNLOCK_UPDATE_STATUS) { - if (lock->restore_status) - (*lock->restore_status)(data->status_param); + /* External lock was not called; Update or restore status */ + if (lock_type >= TL_WRITE_CONCURRENT_INSERT) + { + if (lock->update_status) + (*lock->update_status)(data->status_param); + } + else + { + if (lock->restore_status) + (*lock->restore_status)(data->status_param); + } } if (lock_type == TL_READ_NO_INSERT) lock->read_no_write_count--; data->type=TL_UNLOCK; /* Mark unlocked */ - check_locks(lock,"after releasing lock",1); wake_up_waiters(lock); mysql_mutex_unlock(&lock->mutex); DBUG_VOID_RETURN; @@ -891,9 +1007,9 @@ static void wake_up_waiters(THR_LOCK *lock) { THR_LOCK_DATA *data; enum thr_lock_type lock_type; - DBUG_ENTER("wake_up_waiters"); + check_locks(lock, "before waking up waiters", TL_UNLOCK, 1); if (!lock->write.data) /* If no active write locks */ { data=lock->write_wait.data; @@ -999,20 +1115,18 @@ static void wake_up_waiters(THR_LOCK *lock) free_all_read_locks(lock,0); } end: - check_locks(lock, "after waking up waiters", 0); + check_locks(lock, "after waking up waiters", TL_UNLOCK, 0); DBUG_VOID_RETURN; } /* -** Get all locks in a specific order to avoid dead-locks -** Sort acording to lock position and put write_locks before read_locks if -** lock on same lock. + Get all locks in a specific order to avoid dead-locks + Sort acording to lock position and put write_locks before read_locks if + lock on same lock. Locks on MERGE tables has lower priority than other + locks of the same type. See comment for lock_priority. */ - -#define LOCK_CMP(A,B) ((uchar*) (A->lock) - (uint) ((A)->type) < (uchar*) (B->lock)- (uint) ((B)->type)) - static void sort_locks(THR_LOCK_DATA **data,uint count) { THR_LOCK_DATA **pos,**end,**prev,*tmp; @@ -1038,19 +1152,25 @@ enum enum_thr_lock_result thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_INFO *owner, ulong lock_wait_timeout) { - THR_LOCK_DATA **pos,**end; + THR_LOCK_DATA **pos, **end, **first_lock; DBUG_ENTER("thr_multi_lock"); DBUG_PRINT("lock",("data: 0x%lx count: %d", (long) data, count)); + if (count > 1) sort_locks(data,count); + else if (count == 0) + DBUG_RETURN(THR_LOCK_SUCCESS); + /* 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); + enum enum_thr_lock_result result= thr_lock(*pos, owner, lock_wait_timeout); if (result != THR_LOCK_SUCCESS) { /* Aborted */ - thr_multi_unlock(data,(uint) (pos-data)); + thr_multi_unlock(data,(uint) (pos-data), 0); + /* Mark all requested locks as TL_UNLOCK (to simplify lock checking) */ + for ( ; pos < end ; pos++) + (*pos)->type= TL_UNLOCK; DBUG_RETURN(result); } DEBUG_SYNC_C("thr_multi_lock_after_thr_lock"); @@ -1059,93 +1179,103 @@ thr_multi_lock(THR_LOCK_DATA **data, uint count, THR_LOCK_INFO *owner, (long) pos[0]->lock, pos[0]->type); fflush(stdout); #endif } - thr_lock_merge_status(data, count); + + /* + Call start_trans for all locks. + If we lock the same table multiple times, we must use the same + status_param; We ensure this by calling copy_status() for all + copies of the same tables. + */ + if ((*data)->lock->start_trans) + ((*data)->lock->start_trans)((*data)->status_param); + for (first_lock=data, pos= data+1 ; pos < end ; pos++) + { + /* Get the current status (row count, checksum, trid etc) */ + if ((*pos)->lock->start_trans) + (*(*pos)->lock->start_trans)((*pos)->status_param); + /* + If same table as previous table use pointer to previous status + information to ensure that all read/write tables shares same + state. + */ + if (pos[0]->lock == pos[-1]->lock && pos[0]->lock->copy_status) + (pos[0]->lock->copy_status)((*pos)->status_param, + (*first_lock)->status_param); + else + { + /* Different lock, use this as base for next lock */ + first_lock= pos; + } + } DBUG_RETURN(THR_LOCK_SUCCESS); } /** - Ensure that all locks for a given table have the same - status_param. - - This is a MyISAM and possibly Maria specific crutch. MyISAM - engine stores data file length, record count and other table - properties in status_param member of handler. When a table is - locked, connection-local copy is made from a global copy - (myisam_share) by mi_get_status(). When a table is unlocked, - the changed status is transferred back to the global share by - mi_update_status(). - - One thing MyISAM doesn't do is to ensure that when the same - table is opened twice in a connection all instances share the - same status_param. This is necessary, however: for one, to keep - all instances of a connection "on the same page" with regard to - the current state of the table. For other, unless this is done, - myisam_share will always get updated from the last unlocked - instance (in mi_update_status()), and when this instance was not - the one that was used to update data, records may be lost. - - For each table, this function looks up the last lock_data in the - list of acquired locks, and makes sure that all other instances - share status_param with it. + Merge two sets of locks. + + @param data All locks. First old locks, then new locks. + @param old_count Original number of locks. These are first in 'data'. + @param new_count How many new locks + + The merge is needed if the new locks contains same tables as the old + locks, in which case we have to ensure that same tables shares the + same status (as after a thr_multi_lock()). */ -void -thr_lock_merge_status(THR_LOCK_DATA **data, uint count) +void thr_merge_locks(THR_LOCK_DATA **data, uint old_count, uint new_count) { -#if !defined(DONT_USE_RW_LOCKS) - THR_LOCK_DATA **pos= data; - THR_LOCK_DATA **end= data + count; - if (count > 1) + THR_LOCK_DATA **pos, **end, **first_lock= 0; + DBUG_ENTER("thr_merge_lock"); + + /* Remove marks on old locks to make them sort before new ones */ + for (pos=data, end= pos + old_count; pos < end ; pos++) + (*pos)->priority&= ~THR_LOCK_LATE_PRIV; + + /* Mark new locks with LATE_PRIV to make them sort after org ones */ + for (pos=data + old_count, end= pos + new_count; pos < end ; pos++) + (*pos)->priority|= THR_LOCK_LATE_PRIV; + + sort_locks(data, old_count + new_count); + + for (pos=data ; pos < end ; pos++) { - THR_LOCK_DATA *last_lock= end[-1]; - pos=end-1; - do + /* Check if lock was unlocked before */ + if (pos[0]->type == TL_UNLOCK || ! pos[0]->lock->fix_status) { - pos--; - if (last_lock->lock == (*pos)->lock && - last_lock->lock->copy_status) - { - if (last_lock->type <= TL_READ_NO_INSERT) - { - THR_LOCK_DATA **read_lock; - /* - If we are locking the same table with read locks we must ensure - that all tables share the status of the last write lock or - the same read lock. - */ - for (; - (*pos)->type <= TL_READ_NO_INSERT && - pos != data && - pos[-1]->lock == (*pos)->lock ; - pos--) ; - - read_lock = pos+1; - do - { - (last_lock->lock->copy_status)((*read_lock)->status_param, - (*pos)->status_param); - } while (*(read_lock++) != last_lock); - last_lock= (*pos); /* Point at last write lock */ - } - else - (*last_lock->lock->copy_status)((*pos)->status_param, - last_lock->status_param); - } - else - last_lock=(*pos); - } while (pos != data); + DBUG_PRINT("info", ("lock skipped. unlocked: %d fix_status: %d", + pos[0]->type == TL_UNLOCK, + pos[0]->lock->fix_status == 0)); + continue; + } + + /* + If same table as previous table use pointer to previous status + information to ensure that all read/write tables shares same + state. + */ + if (first_lock && pos[0]->lock == first_lock[0]->lock) + (pos[0]->lock->fix_status)((*first_lock)->status_param, + (*pos)->status_param); + else + { + /* Different lock, use this as base for next lock */ + first_lock= pos; + (pos[0]->lock->fix_status)((*first_lock)->status_param, 0); + } } -#endif + DBUG_VOID_RETURN; } - /* free all locks */ -void thr_multi_unlock(THR_LOCK_DATA **data,uint count) +/* Unlock all locks */ + +void thr_multi_unlock(THR_LOCK_DATA **data,uint count, uint unlock_flags) { THR_LOCK_DATA **pos,**end; DBUG_ENTER("thr_multi_unlock"); - DBUG_PRINT("lock",("data: 0x%lx count: %d", (long) data, count)); + DBUG_PRINT("lock",("data: 0x%lx count: %d flags: %u", (long) data, count, + unlock_flags)); for (pos=data,end=data+count; pos < end ; pos++) { @@ -1155,7 +1285,7 @@ void thr_multi_unlock(THR_LOCK_DATA **data,uint count) fflush(stdout); #endif if ((*pos)->type != TL_UNLOCK) - thr_unlock(*pos); + thr_unlock(*pos, unlock_flags); else { DBUG_PRINT("lock",("Free lock: data: 0x%lx thread: 0x%lx lock: 0x%lx", @@ -1292,7 +1422,7 @@ void thr_downgrade_write_lock(THR_LOCK_DATA *in_data, DBUG_ASSERT(old_lock_type == TL_WRITE_ONLY); DBUG_ASSERT(old_lock_type > new_lock_type); in_data->type= new_lock_type; - check_locks(lock,"after downgrading lock",0); + check_locks(lock,"after downgrading lock", old_lock_type, 0); mysql_mutex_unlock(&lock->mutex); DBUG_VOID_RETURN; @@ -1305,6 +1435,7 @@ my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data, ulong lock_wait_timeout) { THR_LOCK *lock=data->lock; + enum enum_thr_lock_result res; DBUG_ENTER("thr_upgrade_write_delay_lock"); mysql_mutex_lock(&lock->mutex); @@ -1313,7 +1444,7 @@ my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data, mysql_mutex_unlock(&lock->mutex); DBUG_RETURN(data->type == TL_UNLOCK); /* Test if Aborted */ } - check_locks(lock,"before upgrading lock",0); + check_locks(lock,"before upgrading lock", data->type, 0); /* TODO: Upgrade to TL_WRITE_CONCURRENT_INSERT in some cases */ data->type= new_lock_type; /* Upgrade lock */ @@ -1325,6 +1456,8 @@ my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data, if (data->lock->get_status) (*data->lock->get_status)(data->status_param, 0); mysql_mutex_unlock(&lock->mutex); + if (lock->start_trans) + (*lock->start_trans)(data->status_param); DBUG_RETURN(0); } @@ -1339,13 +1472,16 @@ my_bool thr_upgrade_write_delay_lock(THR_LOCK_DATA *data, lock->write_wait.last= &data->next; data->prev= &lock->write_wait.data; lock->write_wait.data=data; - check_locks(lock,"upgrading lock",0); + check_locks(lock,"upgrading lock", new_lock_type, 0); } else { - check_locks(lock,"waiting for lock",0); + check_locks(lock,"waiting for lock", new_lock_type, 0); } - DBUG_RETURN(wait_for_lock(&lock->write_wait,data,1, lock_wait_timeout)); + res= wait_for_lock(&lock->write_wait, data, 1, lock_wait_timeout); + if (res == THR_LOCK_SUCCESS && lock->start_trans) + DBUG_RETURN((*lock->start_trans)(data->status_param)); + DBUG_RETURN(0); } @@ -1462,7 +1598,7 @@ struct st_test { enum thr_lock_type lock_type; }; -THR_LOCK locks[5]; /* 4 locks */ +THR_LOCK locks[6]; /* Number of locks +1 */ struct st_test test_0[] = {{0,TL_READ}}; /* One lock */ struct st_test test_1[] = {{0,TL_READ},{0,TL_WRITE}}; /* Read and write lock of lock 0 */ @@ -1514,7 +1650,7 @@ static ulong sum=0; /* The following functions is for WRITE_CONCURRENT_INSERT */ static void test_get_status(void* param __attribute__((unused)), - int concurrent_insert __attribute__((unused))) + my_bool concurrent_insert __attribute__((unused))) { } @@ -1543,7 +1679,6 @@ static void *test_thread(void *arg) printf("Thread %s (%d) started\n",my_thread_name(),param); fflush(stdout); - thr_lock_info_init(&lock_info); for (i=0; i < lock_counts[param] ; i++) thr_lock_data_init(locks+tests[param][i].lock_nr,data+i,NULL); @@ -1570,7 +1705,7 @@ static void *test_thread(void *arg) } } mysql_mutex_unlock(&LOCK_thread_count); - thr_multi_unlock(multi_locks,lock_counts[param]); + thr_multi_unlock(multi_locks,lock_counts[param], THR_UNLOCK_UPDATE_STATUS); } printf("Thread %s (%d) ended\n",my_thread_name(),param); fflush(stdout); @@ -1588,7 +1723,8 @@ int main(int argc __attribute__((unused)),char **argv __attribute__((unused))) { pthread_t tid; pthread_attr_t thr_attr; - int i,*param,error; + int *param,error; + uint i; MY_INIT(argv[0]); if (argc > 1 && argv[1][0] == '-' && argv[1][1] == '#') DBUG_PUSH(argv[1]+2); @@ -1608,13 +1744,14 @@ int main(int argc __attribute__((unused)),char **argv __attribute__((unused))) exit(1); } - for (i=0 ; i < (int) array_elements(locks) ; i++) + for (i=0 ; i < array_elements(locks) ; i++) { thr_lock_init(locks+i); locks[i].check_status= test_check_status; locks[i].update_status=test_update_status; locks[i].copy_status= test_copy_status; locks[i].get_status= test_get_status; + locks[i].allow_multiple_concurrent_insert= 1; } if ((error=pthread_attr_init(&thr_attr))) { @@ -1640,7 +1777,7 @@ int main(int argc __attribute__((unused)),char **argv __attribute__((unused))) #ifdef HAVE_THR_SETCONCURRENCY (void) thr_setconcurrency(2); #endif - for (i=0 ; i < (int) array_elements(lock_counts) ; i++) + for (i=0 ; i < array_elements(lock_counts) ; i++) { param=(int*) malloc(sizeof(int)); *param=i; @@ -1674,7 +1811,7 @@ int main(int argc __attribute__((unused)),char **argv __attribute__((unused))) } if ((error= mysql_mutex_unlock(&LOCK_thread_count))) fprintf(stderr, "Got error: %d from mysql_mutex_unlock\n", error); - for (i=0 ; i < (int) array_elements(locks) ; i++) + for (i=0 ; i < array_elements(locks) ; i++) thr_lock_delete(locks+i); #ifdef EXTRA_DEBUG if (found_errors) diff --git a/mysys/thr_mutex.c b/mysys/thr_mutex.c index 4d05139e599..17cda782b30 100644 --- a/mysys/thr_mutex.c +++ b/mysys/thr_mutex.c @@ -1,4 +1,6 @@ -/* Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved. +/* + Copyright (c) 2000, 2011, Oracle and/or its affiliates. + Copyright (c) 2010, 2011, Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,11 +21,16 @@ #if defined(TARGET_OS_LINUX) && !defined (__USE_UNIX98) #define __USE_UNIX98 /* To get rw locks under Linux */ #endif -#if defined(SAFE_MUTEX) -#undef SAFE_MUTEX /* Avoid safe_mutex redefinitions */ + +#ifdef SAFE_MUTEX +#define SAFE_MUTEX_DEFINED +#undef SAFE_MUTEX /* Avoid safe_mutex redefinitions */ +#endif + #include "mysys_priv.h" #include "my_static.h" #include <m_string.h> +#include <hash.h> #ifndef DO_NOT_REMOVE_THREAD_WRAPPERS /* Remove wrappers */ @@ -31,38 +38,169 @@ #undef pthread_mutex_init #undef pthread_mutex_lock #undef pthread_mutex_unlock +#undef pthread_mutex_trylock #undef pthread_mutex_destroy #undef pthread_cond_wait #undef pthread_cond_timedwait -#ifdef HAVE_NONPOSIX_PTHREAD_MUTEX_INIT -#define pthread_mutex_init(a,b) my_pthread_mutex_init((a),(b)) -#endif +#undef safe_mutex_free_deadlock_data #endif /* DO_NOT_REMOVE_THREAD_WRAPPERS */ -/* Not instrumented */ +#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP +pthread_mutexattr_t my_fast_mutexattr; +#endif +#ifdef PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP +pthread_mutexattr_t my_errorcheck_mutexattr; +#endif + +#ifdef SAFE_MUTEX_DEFINED static pthread_mutex_t THR_LOCK_mutex; static ulong safe_mutex_count= 0; /* Number of mutexes created */ +static ulong safe_mutex_id= 0; +my_bool safe_mutex_deadlock_detector= 1; /* On by default */ + #ifdef SAFE_MUTEX_DETECT_DESTROY -static struct st_safe_mutex_info_t *safe_mutex_root= NULL; +static struct st_safe_mutex_create_info_t *safe_mutex_create_root= NULL; +#endif + +static my_bool add_used_to_locked_mutex(safe_mutex_t *used_mutex, + safe_mutex_deadlock_t *locked_mutex); +static my_bool add_to_locked_mutex(safe_mutex_deadlock_t *locked_mutex, + safe_mutex_t *current_mutex); +static my_bool remove_from_locked_mutex(safe_mutex_t *mp, + safe_mutex_t *delete_mutex); +static my_bool remove_from_used_mutex(safe_mutex_deadlock_t *locked_mutex, + safe_mutex_t *mutex); +static void print_deadlock_warning(safe_mutex_t *new_mutex, + safe_mutex_t *conflicting_mutex); +#endif + + +/* Initialize all mutex handling */ + +void my_mutex_init() +{ + /* Initialize mutex attributes */ +#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP + /* + Set mutex type to "fast" a.k.a "adaptive" + + In this case the thread may steal the mutex from some other thread + that is waiting for the same mutex. This will save us some + context switches but may cause a thread to 'starve forever' while + waiting for the mutex (not likely if the code within the mutex is + short). + */ + pthread_mutexattr_init(&my_fast_mutexattr); + pthread_mutexattr_settype(&my_fast_mutexattr, + PTHREAD_MUTEX_ADAPTIVE_NP); +#endif +#ifdef PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP + /* + Set mutex type to "errorcheck" + */ + pthread_mutexattr_init(&my_errorcheck_mutexattr); + pthread_mutexattr_settype(&my_errorcheck_mutexattr, + PTHREAD_MUTEX_ERRORCHECK); #endif +#if defined(SAFE_MUTEX_DEFINED) + safe_mutex_global_init(); +#elif defined(MY_PTHREAD_FASTMUTEX) + fastmutex_global_init(); +#endif +} + +void my_mutex_end() +{ +#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP + pthread_mutexattr_destroy(&my_fast_mutexattr); +#endif +#ifdef PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP + pthread_mutexattr_destroy(&my_errorcheck_mutexattr); +#endif +} + + +/* Initialize safe_mutex handling */ + +#ifdef SAFE_MUTEX_DEFINED void safe_mutex_global_init(void) { pthread_mutex_init(&THR_LOCK_mutex,MY_MUTEX_INIT_FAST); + safe_mutex_id= safe_mutex_count= 0; + safe_mutex_deadlock_detector= 1; + +#ifdef SAFE_MUTEX_DETECT_DESTROY + safe_mutex_create_root= 0; +#endif +} + +static inline void remove_from_active_list(safe_mutex_t *mp) +{ + if (!(mp->active_flags & (MYF_NO_DEADLOCK_DETECTION | MYF_TRY_LOCK))) + { + /* Remove mutex from active mutex linked list */ + if (mp->next) + mp->next->prev= mp->prev; + if (mp->prev) + mp->prev->next= mp->next; + else + *my_thread_var_mutex_in_use()= mp->next; + } + mp->prev= mp->next= 0; } +/* + We initialise the hashes for deadlock detection lazily. + This greatly helps with performance when lots of mutexes are initiased but + only a few of them are actually used (eg. XtraDB). +*/ +static int safe_mutex_lazy_init_deadlock_detection(safe_mutex_t *mp) +{ + if (!my_multi_malloc(MY_FAE | MY_WME, + &mp->locked_mutex, sizeof(*mp->locked_mutex), + &mp->used_mutex, sizeof(*mp->used_mutex), NullS)) + { + /* Disable deadlock handling for this mutex */ + mp->create_flags|= MYF_NO_DEADLOCK_DETECTION; + mp->active_flags|= MYF_NO_DEADLOCK_DETECTION; + return 1; /* Error */ + } + + pthread_mutex_lock(&THR_LOCK_mutex); + mp->id= ++safe_mutex_id; + pthread_mutex_unlock(&THR_LOCK_mutex); + my_hash_init2(mp->locked_mutex, 64, &my_charset_bin, + 128, + offsetof(safe_mutex_deadlock_t, id), + sizeof(mp->id), + 0, 0, HASH_UNIQUE); + my_hash_init2(mp->used_mutex, 64, &my_charset_bin, + 128, + offsetof(safe_mutex_t, id), + sizeof(mp->id), + 0, 0, HASH_UNIQUE); + return 0; +} int safe_mutex_init(safe_mutex_t *mp, const pthread_mutexattr_t *attr __attribute__((unused)), - const char *file, - uint line) + const char *name, const char *file, uint line) { + DBUG_ENTER("safe_mutex_init"); + DBUG_PRINT("enter",("mutex: 0x%lx name: %s", (ulong) mp, name)); bzero((char*) mp,sizeof(*mp)); pthread_mutex_init(&mp->global,MY_MUTEX_INIT_ERRCHK); pthread_mutex_init(&mp->mutex,attr); /* Mark that mutex is initialized */ mp->file= file; mp->line= line; + /* Skip the very common '&' prefix from the autogenerated name */ + mp->name= name[0] == '&' ? name + 1 : name; + + /* Deadlock detection is initialised only lazily, on first use. */ + + mp->create_flags= safe_mutex_deadlock_detector ? 0 : MYF_NO_DEADLOCK_DETECTION; #ifdef SAFE_MUTEX_DETECT_DESTROY /* @@ -71,7 +209,7 @@ int safe_mutex_init(safe_mutex_t *mp, */ if ((mp->info= (safe_mutex_info_t *) malloc(sizeof(safe_mutex_info_t)))) { - struct st_safe_mutex_info_t *info =mp->info; + struct st_safe_mutex_info_t *info= mp->info; info->init_file= file; info->init_line= line; @@ -79,9 +217,9 @@ int safe_mutex_init(safe_mutex_t *mp, info->next= NULL; pthread_mutex_lock(&THR_LOCK_mutex); - if ((info->next= safe_mutex_root)) - safe_mutex_root->prev= info; - safe_mutex_root= info; + if ((info->next= safe_mutex_create_root)) + safe_mutex_create_root->prev= info; + safe_mutex_create_root= info; safe_mutex_count++; pthread_mutex_unlock(&THR_LOCK_mutex); } @@ -90,13 +228,16 @@ int safe_mutex_init(safe_mutex_t *mp, safe_mutex_count++; pthread_mutex_unlock(&THR_LOCK_mutex); #endif /* SAFE_MUTEX_DETECT_DESTROY */ - return 0; + DBUG_RETURN(0); } -int safe_mutex_lock(safe_mutex_t *mp, my_bool try_lock, const char *file, uint line) +int safe_mutex_lock(safe_mutex_t *mp, myf my_flags, const char *file, + uint line) { int error; + DBUG_PRINT("mutex", ("%s (0x%lx) locking", mp->name ? mp->name : "Null", + (ulong) mp)); if (!mp->file) { fprintf(stderr, @@ -109,12 +250,13 @@ int safe_mutex_lock(safe_mutex_t *mp, my_bool try_lock, const char *file, uint l pthread_mutex_lock(&mp->global); if (mp->count > 0) { - if (try_lock) - { - pthread_mutex_unlock(&mp->global); - return EBUSY; - } - else if (pthread_equal(pthread_self(),mp->thread)) + /* + Check that we are not trying to lock mutex twice. This is an error + even if we are using 'try_lock' as it's not portably what happens + if you lock the mutex many times and this is in any case bad + behaviour that should not be encouraged + */ + if (pthread_equal(pthread_self(),mp->thread)) { fprintf(stderr, "safe_mutex: Trying to lock mutex at %s, line %d, when the" @@ -142,7 +284,7 @@ int safe_mutex_lock(safe_mutex_t *mp, my_bool try_lock, const char *file, uint l instead just return EBUSY, since this is the expected behaviour of trylock(). */ - if (try_lock) + if (my_flags & MYF_TRY_LOCK) { error= pthread_mutex_trylock(&mp->mutex); if (error == EBUSY) @@ -153,22 +295,110 @@ int safe_mutex_lock(safe_mutex_t *mp, my_bool try_lock, const char *file, uint l if (error || (error=pthread_mutex_lock(&mp->global))) { - fprintf(stderr,"Got error %d when trying to lock mutex at %s, line %d\n", - error, file, line); + fprintf(stderr,"Got error %d when trying to lock mutex %s at %s, line %d\n", + error, mp->name, file, line); fflush(stderr); abort(); } mp->thread= pthread_self(); if (mp->count++) { - fprintf(stderr,"safe_mutex: Error in thread libray: Got mutex at %s, \ -line %d more than 1 time\n", file,line); + fprintf(stderr,"safe_mutex: Error in thread libray: Got mutex %s at %s, " + "line %d more than 1 time\n", mp->name, file,line); fflush(stderr); abort(); } mp->file= file; - mp->line=line; + mp->line= line; + mp->active_flags= mp->create_flags | my_flags; pthread_mutex_unlock(&mp->global); + + /* Deadlock detection */ + + mp->prev= mp->next= 0; + if (!(mp->active_flags & (MYF_TRY_LOCK | MYF_NO_DEADLOCK_DETECTION)) && + (mp->used_mutex != NULL || !safe_mutex_lazy_init_deadlock_detection(mp))) + { + safe_mutex_t **mutex_in_use= my_thread_var_mutex_in_use(); + + if (!mutex_in_use) + { + /* thread has not called my_thread_init() */ + mp->active_flags|= MYF_NO_DEADLOCK_DETECTION; + } + else + { + safe_mutex_t *mutex_root; + if ((mutex_root= *mutex_in_use)) /* If not first locked */ + { + /* + Protect locked_mutex against changes if a mutex is deleted + */ + pthread_mutex_lock(&THR_LOCK_mutex); + + if (!my_hash_search(mutex_root->locked_mutex, (uchar*) &mp->id, 0)) + { + safe_mutex_deadlock_t *deadlock; + safe_mutex_t *mutex; + + /* Create object to store mutex info */ + if (!(deadlock= my_malloc(sizeof(*deadlock), + MYF(MY_ZEROFILL | MY_WME | MY_FAE)))) + goto abort_loop; + deadlock->name= mp->name; + deadlock->id= mp->id; + deadlock->mutex= mp; + /* The following is useful for debugging wrong mutex usage */ + deadlock->file= file; + deadlock->line= line; + + /* Check if potential deadlock */ + mutex= mutex_root; + do + { + if (my_hash_search(mp->locked_mutex, (uchar*) &mutex->id, 0)) + { + print_deadlock_warning(mp, mutex); + /* Mark wrong usage to avoid future warnings for same error */ + deadlock->warning_only= 1; + add_to_locked_mutex(deadlock, mutex_root); + DBUG_ASSERT(deadlock->count > 0); + goto abort_loop; + } + } + while ((mutex= mutex->next)); + + /* + Copy current mutex and all mutex that has been locked + after current mutex (mp->locked_mutex) to all mutex that + was locked before previous mutex (mutex_root->used_mutex) + + For example if A->B would have been done before and we + are now locking (C) in B->C, then we would add C into + B->locked_mutex and A->locked_mutex + */ + my_hash_iterate(mutex_root->used_mutex, + (my_hash_walk_action) add_used_to_locked_mutex, + deadlock); + + /* + Copy all current mutex and all mutex locked after current one + into the prev mutex + */ + add_used_to_locked_mutex(mutex_root, deadlock); + DBUG_ASSERT(deadlock->count > 0); + } + abort_loop: + pthread_mutex_unlock(&THR_LOCK_mutex); + } + /* Link mutex into mutex_in_use list */ + if ((mp->next= *mutex_in_use)) + (*mutex_in_use)->prev= mp; + *mutex_in_use= mp; + } + } + + DBUG_PRINT("mutex", ("%s (0x%lx) locked", mp->name, (ulong) mp)); return error; } @@ -176,23 +406,34 @@ line %d more than 1 time\n", file,line); int safe_mutex_unlock(safe_mutex_t *mp,const char *file, uint line) { int error; + DBUG_PRINT("mutex", ("%s (0x%lx) unlocking", mp->name, (ulong) mp)); pthread_mutex_lock(&mp->global); if (mp->count == 0) { - fprintf(stderr,"safe_mutex: Trying to unlock mutex that wasn't locked at %s, line %d\n Last used at %s, line: %d\n", - file,line,mp->file ? mp->file : "",mp->line); + fprintf(stderr, + "safe_mutex: Trying to unlock mutex %s that wasn't locked at " + "%s, line %d\n" + "Last used at %s, line: %d\n", + mp->name ? mp->name : "Null", file, line, + mp->file ? mp->file : "Null", mp->line); fflush(stderr); abort(); } if (!pthread_equal(pthread_self(),mp->thread)) { - fprintf(stderr,"safe_mutex: Trying to unlock mutex at %s, line %d that was locked by another thread at: %s, line: %d\n", - file,line,mp->file,mp->line); + fprintf(stderr, + "safe_mutex: Trying to unlock mutex %s at %s, line %d that was " + "locked by " + "another thread at: %s, line: %d\n", + mp->name, file, line, mp->file, mp->line); fflush(stderr); abort(); } mp->thread= 0; mp->count--; + + remove_from_active_list(mp); + #ifdef __WIN__ pthread_mutex_unlock(&mp->mutex); error=0; @@ -200,7 +441,9 @@ int safe_mutex_unlock(safe_mutex_t *mp,const char *file, uint line) error=pthread_mutex_unlock(&mp->mutex); if (error) { - fprintf(stderr,"safe_mutex: Got error: %d (%d) when trying to unlock mutex at %s, line %d\n", error, errno, file, line); + fprintf(stderr, + "safe_mutex: Got error: %d (%d) when trying to unlock mutex " + "%s at %s, line %d\n", error, errno, mp->name, file, line); fflush(stderr); abort(); } @@ -214,43 +457,62 @@ int safe_cond_wait(pthread_cond_t *cond, safe_mutex_t *mp, const char *file, uint line) { int error; + safe_mutex_t save_state; + pthread_mutex_lock(&mp->global); if (mp->count == 0) { - fprintf(stderr,"safe_mutex: Trying to cond_wait on a unlocked mutex at %s, line %d\n",file,line); + fprintf(stderr, + "safe_mutex: Trying to cond_wait on a unlocked mutex %s at %s, " + "line %d\n", + mp->name ? mp->name : "Null", file, line); fflush(stderr); abort(); } if (!pthread_equal(pthread_self(),mp->thread)) { - fprintf(stderr,"safe_mutex: Trying to cond_wait on a mutex at %s, line %d that was locked by another thread at: %s, line: %d\n", - file,line,mp->file,mp->line); + fprintf(stderr, + "safe_mutex: Trying to cond_wait on a mutex %s at %s, line %d " + "that was locked by another thread at: %s, line: %d\n", + mp->name, file, line, mp->file, mp->line); fflush(stderr); abort(); } if (mp->count-- != 1) { - fprintf(stderr,"safe_mutex: Count was %d on locked mutex at %s, line %d\n", - mp->count+1, file, line); + fprintf(stderr, + "safe_mutex: Count was %d on locked mutex %s at %s, line %d\n", + mp->count+1, mp->name, file, line); fflush(stderr); abort(); } + save_state= *mp; + remove_from_active_list(mp); pthread_mutex_unlock(&mp->global); error=pthread_cond_wait(cond,&mp->mutex); pthread_mutex_lock(&mp->global); + if (error) { - fprintf(stderr,"safe_mutex: Got error: %d (%d) when doing a safe_mutex_wait at %s, line %d\n", error, errno, file, line); + fprintf(stderr, + "safe_mutex: Got error: %d (%d) when doing a safe_mutex_wait on " + "%s at %s, line %d\n", error, errno, mp->name, file, line); fflush(stderr); abort(); } - mp->thread=pthread_self(); + /* Restore state as it was before */ + mp->thread= save_state.thread; + mp->active_flags= save_state.active_flags; + mp->next= save_state.next; + mp->prev= save_state.prev; + if (mp->count++) { fprintf(stderr, - "safe_mutex: Count was %d in thread 0x%lx when locking mutex at %s, line %d\n", - mp->count-1, my_thread_dbug_id(), file, line); + "safe_mutex: Count was %d in thread 0x%lx when locking mutex %s " + "at %s, line %d\n", + mp->count-1, my_thread_dbug_id(), mp->name, file, line); fflush(stderr); abort(); } @@ -262,33 +524,50 @@ int safe_cond_wait(pthread_cond_t *cond, safe_mutex_t *mp, const char *file, int safe_cond_timedwait(pthread_cond_t *cond, safe_mutex_t *mp, - const struct timespec *abstime, - const char *file, uint line) + const struct timespec *abstime, + const char *file, uint line) { int error; + safe_mutex_t save_state; + pthread_mutex_lock(&mp->global); if (mp->count != 1 || !pthread_equal(pthread_self(),mp->thread)) { - fprintf(stderr,"safe_mutex: Trying to cond_wait at %s, line %d on a not hold mutex\n",file,line); + fprintf(stderr, + "safe_mutex: Trying to cond_wait at %s, line %d on a not hold " + "mutex %s\n", + file, line, mp->name ? mp->name : "Null"); fflush(stderr); abort(); } mp->count--; /* Mutex will be released */ + save_state= *mp; + remove_from_active_list(mp); pthread_mutex_unlock(&mp->global); error=pthread_cond_timedwait(cond,&mp->mutex,abstime); #ifdef EXTRA_DEBUG if (error && (error != EINTR && error != ETIMEDOUT && error != ETIME)) { - fprintf(stderr,"safe_mutex: Got error: %d (%d) when doing a safe_mutex_timedwait at %s, line %d\n", error, errno, file, line); + fprintf(stderr, + "safe_mutex: Got error: %d (%d) when doing a safe_mutex_timedwait " + "on %s at %s, line %d\n", + error, errno, mp->name, file, line); } #endif pthread_mutex_lock(&mp->global); - mp->thread=pthread_self(); + /* Restore state as it was before */ + mp->thread= save_state.thread; + mp->active_flags= save_state.active_flags; + mp->next= save_state.next; + mp->prev= save_state.prev; + if (mp->count++) { fprintf(stderr, - "safe_mutex: Count was %d in thread 0x%lx when locking mutex at %s, line %d (error: %d (%d))\n", - mp->count-1, my_thread_dbug_id(), file, line, error, error); + "safe_mutex: Count was %d in thread 0x%lx when locking mutex " + "%s at %s, line %d (error: %d (%d))\n", + mp->count-1, my_thread_dbug_id(), mp->name, file, line, + error, error); fflush(stderr); abort(); } @@ -302,6 +581,8 @@ int safe_cond_timedwait(pthread_cond_t *cond, safe_mutex_t *mp, int safe_mutex_destroy(safe_mutex_t *mp, const char *file, uint line) { int error=0; + DBUG_ENTER("safe_mutex_destroy"); + DBUG_PRINT("enter", ("mutex: 0x%lx name: %s", (ulong) mp, mp->name)); if (!mp->file) { fprintf(stderr, @@ -312,11 +593,17 @@ int safe_mutex_destroy(safe_mutex_t *mp, const char *file, uint line) } if (mp->count != 0) { - fprintf(stderr,"safe_mutex: Trying to destroy a mutex that was locked at %s, line %d at %s, line %d\n", - mp->file,mp->line, file, line); + fprintf(stderr, + "safe_mutex: Trying to destroy a mutex %s that was locked at %s, " + "line %d at %s, line %d\n", + mp->name, mp->file, mp->line, file, line); fflush(stderr); abort(); } + + /* Free all entries that points to this one */ + safe_mutex_free_deadlock_data(mp); + #ifdef __WIN__ pthread_mutex_destroy(&mp->global); pthread_mutex_destroy(&mp->mutex); @@ -337,7 +624,7 @@ int safe_mutex_destroy(safe_mutex_t *mp, const char *file, uint line) if (info->prev) info->prev->next = info->next; else - safe_mutex_root = info->next; + safe_mutex_create_root = info->next; if (info->next) info->next->prev = info->prev; safe_mutex_count--; @@ -351,10 +638,38 @@ int safe_mutex_destroy(safe_mutex_t *mp, const char *file, uint line) safe_mutex_count--; pthread_mutex_unlock(&THR_LOCK_mutex); #endif /* SAFE_MUTEX_DETECT_DESTROY */ - return error; + DBUG_RETURN(error); } +/** + Free all data related to deadlock detection + + This is also useful together with safemalloc when you don't want to + have reports of not freed memory for mysys mutexes. +*/ + +void safe_mutex_free_deadlock_data(safe_mutex_t *mp) +{ + /* Free all entries that points to this one */ + if (!(mp->create_flags & MYF_NO_DEADLOCK_DETECTION) && mp->used_mutex != NULL) + { + pthread_mutex_lock(&THR_LOCK_mutex); + my_hash_iterate(mp->used_mutex, + (my_hash_walk_action) remove_from_locked_mutex, + mp); + my_hash_iterate(mp->locked_mutex, + (my_hash_walk_action) remove_from_used_mutex, + mp); + pthread_mutex_unlock(&THR_LOCK_mutex); + + my_hash_free(mp->used_mutex); + my_hash_free(mp->locked_mutex); + my_free(mp->locked_mutex); + mp->create_flags|= MYF_NO_DEADLOCK_DETECTION; + } +} + /* Free global resources and check that all mutex has been destroyed @@ -385,40 +700,147 @@ void safe_mutex_end(FILE *file __attribute__((unused))) } { struct st_safe_mutex_info_t *ptr; - for (ptr= safe_mutex_root ; ptr ; ptr= ptr->next) + for (ptr= safe_mutex_create_root ; ptr ; ptr= ptr->next) { - fprintf(file, "\tMutex initiated at line %4u in '%s'\n", - ptr->init_line, ptr->init_file); + fprintf(file, "\tMutex %s initiated at line %4u in '%s'\n", + ptr->name, ptr->init_line, ptr->init_file); (void) fflush(file); } } #endif /* SAFE_MUTEX_DETECT_DESTROY */ } -#endif /* SAFE_MUTEX */ +static my_bool add_used_to_locked_mutex(safe_mutex_t *used_mutex, + safe_mutex_deadlock_t *locked_mutex) +{ + /* Add mutex to all parent of the current mutex */ + if (!locked_mutex->warning_only) + { + (void) my_hash_iterate(locked_mutex->mutex->locked_mutex, + (my_hash_walk_action) add_to_locked_mutex, + used_mutex); + /* mark that locked_mutex is locked after used_mutex */ + (void) add_to_locked_mutex(locked_mutex, used_mutex); + } + return 0; +} -#if defined(MY_PTHREAD_FASTMUTEX) && !defined(SAFE_MUTEX) -#include "mysys_priv.h" -#include "my_static.h" -#include <m_string.h> +/** + register that locked_mutex was locked after current_mutex +*/ -#include <m_ctype.h> -#include <hash.h> -#include <myisampack.h> -#include <mysys_err.h> -#include <my_sys.h> +static my_bool add_to_locked_mutex(safe_mutex_deadlock_t *locked_mutex, + safe_mutex_t *current_mutex) +{ + DBUG_ENTER("add_to_locked_mutex"); + DBUG_PRINT("info", ("inserting 0x%lx into 0x%lx (id: %lu -> %lu)", + (ulong) locked_mutex, (long) current_mutex, + locked_mutex->id, current_mutex->id)); + if (my_hash_insert(current_mutex->locked_mutex, (uchar*) locked_mutex)) + { + /* Got mutex through two paths; ignore */ + DBUG_RETURN(0); + } + locked_mutex->count++; + if (my_hash_insert(locked_mutex->mutex->used_mutex, + (uchar*) current_mutex)) + { + DBUG_ASSERT(0); + } + DBUG_RETURN(0); +} -#undef pthread_mutex_t -#undef pthread_mutex_init -#undef pthread_mutex_lock -#undef pthread_mutex_trylock -#undef pthread_mutex_unlock -#undef pthread_mutex_destroy -#undef pthread_cond_wait -#undef pthread_cond_timedwait -ulong mutex_delay(ulong delayloops) +/** + Remove mutex from the locked mutex hash + @fn remove_from_used_mutex() + @param mp Mutex that has delete_mutex in it's locked_mutex hash + @param delete_mutex Mutex should be removed from the hash + + @notes + safe_mutex_deadlock_t entries in the locked hash are shared. + When counter goes to 0, we delete the safe_mutex_deadlock_t entry. +*/ + +static my_bool remove_from_locked_mutex(safe_mutex_t *mp, + safe_mutex_t *delete_mutex) +{ + safe_mutex_deadlock_t *found; + DBUG_ENTER("remove_from_locked_mutex"); + DBUG_PRINT("enter", ("delete_mutex: 0x%lx mutex: 0x%lx (id: %lu <- %lu)", + (ulong) delete_mutex, (ulong) mp, + delete_mutex->id, mp->id)); + + found= (safe_mutex_deadlock_t *) my_hash_search(mp->locked_mutex, + (uchar*) &delete_mutex->id, 0); + DBUG_ASSERT(found); + if (found) + { + if (my_hash_delete(mp->locked_mutex, (uchar*) found)) + { + DBUG_ASSERT(0); + } + if (!--found->count) + my_free(found); + } + DBUG_RETURN(0); +} + +static my_bool remove_from_used_mutex(safe_mutex_deadlock_t *locked_mutex, + safe_mutex_t *mutex) +{ + DBUG_ENTER("remove_from_used_mutex"); + DBUG_PRINT("enter", ("delete_mutex: 0x%lx mutex: 0x%lx (id: %lu <- %lu)", + (ulong) mutex, (ulong) locked_mutex, + mutex->id, locked_mutex->id)); + if (my_hash_delete(locked_mutex->mutex->used_mutex, (uchar*) mutex)) + { + DBUG_ASSERT(0); + } + if (!--locked_mutex->count) + my_free(locked_mutex); + DBUG_RETURN(0); +} + + +static void print_deadlock_warning(safe_mutex_t *new_mutex, + safe_mutex_t *parent_mutex) +{ + safe_mutex_t *mutex_root; + DBUG_ENTER("print_deadlock_warning"); + DBUG_PRINT("enter", ("mutex: %s parent: %s", + new_mutex->name, parent_mutex->name)); + + fprintf(stderr, "safe_mutex: Found wrong usage of mutex " + "'%s' and '%s'\n", + parent_mutex->name, new_mutex->name); + DBUG_PRINT("info", ("safe_mutex: Found wrong usage of mutex " + "'%s' and '%s'", + parent_mutex->name, new_mutex->name)); + fprintf(stderr, "Mutex currently locked (in reverse order):\n"); + DBUG_PRINT("info", ("Mutex currently locked (in reverse order):")); + fprintf(stderr, "%-32.32s %s line %u\n", new_mutex->name, new_mutex->file, + new_mutex->line); + DBUG_PRINT("info", ("%-32.32s %s line %u\n", new_mutex->name, + new_mutex->file, new_mutex->line)); + for (mutex_root= *my_thread_var_mutex_in_use() ; + mutex_root; + mutex_root= mutex_root->next) + { + fprintf(stderr, "%-32.32s %s line %u\n", mutex_root->name, + mutex_root->file, mutex_root->line); + DBUG_PRINT("info", ("%-32.32s %s line %u", mutex_root->name, + mutex_root->file, mutex_root->line)); + } + fflush(stderr); + DBUG_ASSERT(my_assert_on_error == 0); + DBUG_VOID_RETURN; +} + +#elif defined(MY_PTHREAD_FASTMUTEX) + +static ulong mutex_delay(ulong delayloops) { ulong i; volatile ulong j; @@ -499,5 +921,5 @@ void fastmutex_global_init(void) cpu_count= sysconf(_SC_NPROCESSORS_CONF); #endif } - -#endif /* defined(MY_PTHREAD_FASTMUTEX) && !defined(SAFE_MUTEX) */ + +#endif /* defined(MY_PTHREAD_FASTMUTEX) */ diff --git a/mysys/thr_rwlock.c b/mysys/thr_rwlock.c index 9bfaa8b63d2..dd6c625a286 100644 --- a/mysys/thr_rwlock.c +++ b/mysys/thr_rwlock.c @@ -142,7 +142,7 @@ static int srw_unlock(my_rw_lock_t *rwp) * Multithreaded Demo Source * * Copyright (C) 1995 by Sun Microsystems, Inc. -* All rights reserved. +* * * This file is a product of SunSoft, Inc. and is provided for * unrestricted use provided that this legend is included on all diff --git a/mysys/tree.c b/mysys/tree.c index c5bf3681a35..85770194f25 100644 --- a/mysys/tree.c +++ b/mysys/tree.c @@ -77,13 +77,13 @@ static void rb_insert(TREE *tree,TREE_ELEMENT ***parent, static void rb_delete_fixup(TREE *tree,TREE_ELEMENT ***parent); - /* The actuall code for handling binary trees */ +/* The actual code for handling binary trees */ #ifndef DBUG_OFF static int test_rb_tree(TREE_ELEMENT *element); #endif -void init_tree(TREE *tree, ulong default_alloc_size, ulong memory_limit, +void init_tree(TREE *tree, size_t default_alloc_size, size_t memory_limit, int size, qsort_cmp2 compare, my_bool with_delete, tree_element_free free_element, void *custom_arg) { @@ -96,7 +96,7 @@ void init_tree(TREE *tree, ulong default_alloc_size, ulong memory_limit, bzero((uchar*) &tree->null_element,sizeof(tree->null_element)); tree->root= &tree->null_element; tree->compare=compare; - tree->size_of_element=size > 0 ? (uint) size : 0; + tree->size_of_element= size > 0 ? (uint) size : 0; tree->memory_limit=memory_limit; tree->free=free_element; tree->allocated=0; @@ -127,7 +127,7 @@ void init_tree(TREE *tree, ulong default_alloc_size, ulong memory_limit, } if (!(tree->with_delete=with_delete)) { - init_alloc_root(&tree->mem_root, (uint) default_alloc_size, 0); + init_alloc_root(&tree->mem_root, default_alloc_size, 0); tree->mem_root.min_malloc=(sizeof(TREE_ELEMENT)+tree->size_of_element); } DBUG_VOID_RETURN; @@ -221,7 +221,10 @@ TREE_ELEMENT *tree_insert(TREE *tree, void *key, uint key_size, } if (element == &tree->null_element) { - uint alloc_size=sizeof(TREE_ELEMENT)+key_size+tree->size_of_element; + uint alloc_size; + if (tree->flag & TREE_ONLY_DUPS) + return((TREE_ELEMENT *) 1); + alloc_size=sizeof(TREE_ELEMENT)+key_size+tree->size_of_element; tree->allocated+=alloc_size; if (tree->memory_limit && tree->elements_in_tree @@ -375,6 +378,7 @@ void *tree_search_key(TREE *tree, const void *key, case HA_READ_KEY_EXACT: case HA_READ_KEY_OR_NEXT: case HA_READ_BEFORE_KEY: + case HA_READ_KEY_OR_PREV: last_equal_element= parents; cmp= 1; break; @@ -418,6 +422,9 @@ void *tree_search_key(TREE *tree, const void *key, case HA_READ_BEFORE_KEY: *last_pos= last_right_step_parent; break; + case HA_READ_KEY_OR_PREV: + *last_pos= last_equal_element ? last_equal_element : last_right_step_parent; + break; default: return NULL; } diff --git a/mysys/typelib.c b/mysys/typelib.c index 737ffa4eb81..2c36c97c6d6 100644 --- a/mysys/typelib.c +++ b/mysys/typelib.c @@ -20,9 +20,10 @@ #include <m_ctype.h> -#define is_field_separator(X) ((X) == ',' || (X) == '=') +#define is_field_separator(F, X) \ + ((F & FIND_TYPE_COMMA_TERM) && ((X) == ',' || (X) == '=')) -int find_type_or_exit(const char *x, TYPELIB *typelib, const char *option) +int find_type_with_warning(const char *x, TYPELIB *typelib, const char *option) { int res; const char **ptr; @@ -38,6 +39,17 @@ int find_type_or_exit(const char *x, TYPELIB *typelib, const char *option) while (*++ptr) fprintf(stderr, ",'%s'", *ptr); fprintf(stderr, "\n"); + } + return res; +} + + +int find_type_or_exit(const char *x, TYPELIB *typelib, const char *option) +{ + int res; + if ((res= find_type_with_warning(x, typelib, option)) <= 0) + { + sf_leaking_memory= 1; /* no memory leak reports here */ exit(1); } return res; @@ -47,14 +59,14 @@ int find_type_or_exit(const char *x, TYPELIB *typelib, const char *option) /** Search after a string in a list of strings. Endspace in x is not compared. - @param x String to find + @param x pointer to string to find + (not necessarily zero-terminated). + by return it'll be advanced to point to the terminator. @param typelib TYPELIB (struct of pointer to values + count) @param flags flags to tune behaviour: a combination of FIND_TYPE_NO_PREFIX - FIND_TYPE_ALLOW_NUMBER FIND_TYPE_COMMA_TERM. - FIND_TYPE_NO_OVERWRITE can be passed but is - superfluous (is always implicitely on). + @param eol a pointer to the end of the string. @retval -1 Too many matching values @@ -65,17 +77,20 @@ int find_type_or_exit(const char *x, TYPELIB *typelib, const char *option) */ -int find_type(const char *x, const TYPELIB *typelib, uint flags) +static int find_type_eol(const char **x, const TYPELIB *typelib, uint flags, + const char *eol) { int find,pos; int UNINIT_VAR(findpos); /* guarded by find */ + const char *UNINIT_VAR(termptr); const char *i; const char *j; - DBUG_ENTER("find_type"); - DBUG_PRINT("enter",("x: '%s' lib: 0x%lx", x, (long) typelib)); + CHARSET_INFO *cs= &my_charset_latin1; + DBUG_ENTER("find_type_eol"); + DBUG_PRINT("enter",("x: '%s' lib: 0x%lx", *x, (long) typelib)); + + DBUG_ASSERT(!(flags & ~(FIND_TYPE_NO_PREFIX | FIND_TYPE_COMMA_TERM))); - DBUG_ASSERT(!(flags & ~(FIND_TYPE_NO_PREFIX | FIND_TYPE_ALLOW_NUMBER | - FIND_TYPE_NO_OVERWRITE | FIND_TYPE_COMMA_TERM))); if (!typelib->count) { DBUG_PRINT("exit",("no count")); @@ -84,44 +99,54 @@ int find_type(const char *x, const TYPELIB *typelib, uint flags) find=0; for (pos=0 ; (j=typelib->type_names[pos]) ; pos++) { - for (i=x ; - *i && (!(flags & FIND_TYPE_COMMA_TERM) || !is_field_separator(*i)) && - my_toupper(&my_charset_latin1,*i) == - my_toupper(&my_charset_latin1,*j) ; i++, j++) ; + for (i=*x ; + i < eol && !is_field_separator(flags, *i) && + my_toupper(cs, *i) == my_toupper(cs, *j) ; i++, j++) ; if (! *j) { - while (*i == ' ') + while (i < eol && *i == ' ') i++; /* skip_end_space */ - if (! *i || ((flags & FIND_TYPE_COMMA_TERM) && is_field_separator(*i))) + if (i >= eol || is_field_separator(flags, *i)) + { + *x= i; DBUG_RETURN(pos+1); + } } - if ((!*i && - (!(flags & FIND_TYPE_COMMA_TERM) || !is_field_separator(*i))) && + if ((i >= eol && !is_field_separator(flags, *i)) && (!*j || !(flags & FIND_TYPE_NO_PREFIX))) { find++; findpos=pos; + termptr=i; } } - if (find == 0 && (flags & FIND_TYPE_ALLOW_NUMBER) && x[0] == '#' && - strend(x)[-1] == '#' && - (findpos=atoi(x+1)-1) >= 0 && (uint) findpos < typelib->count) - find=1; - else if (find == 0 || ! x[0]) + if (find == 0 || *x == eol) { DBUG_PRINT("exit",("Couldn't find type")); DBUG_RETURN(0); } else if (find != 1 || (flags & FIND_TYPE_NO_PREFIX)) { - DBUG_PRINT("exit",("Too many possybilities")); + DBUG_PRINT("exit",("Too many possibilities")); DBUG_RETURN(-1); } + *x= termptr; DBUG_RETURN(findpos+1); -} /* find_type */ +} /* find_type_eol */ /** + Search after a string in a list of strings. Endspace in x is not compared. + + Same as find_type_eol, but for zero-terminated strings, + and without advancing the pointer. +*/ +int find_type(const char *x, const TYPELIB *typelib, uint flags) +{ + return find_type_eol(&x, typelib, flags, x + strlen(x)); +} + +/** Get name of type nr @note @@ -187,7 +212,7 @@ my_ulonglong find_typeset(char *x, TYPELIB *lib, int *err) { (*err)++; i= x; - while (*x && !is_field_separator(*x)) + while (*x && *x != ',') x++; if (x[0] && x[1]) /* skip separator if found */ x++; @@ -272,12 +297,10 @@ static TYPELIB on_off_default_typelib= {array_elements(on_off_default_names)-1, >0 Offset+1 in typelib for matched name */ -static uint parse_name(const TYPELIB *lib, const char **strpos, const char *end) +static uint parse_name(const TYPELIB *lib, const char **pos, const char *end) { - const char *pos= *strpos; - uint find= find_type(pos, lib, FIND_TYPE_COMMA_TERM); - for (; pos != end && *pos != '=' && *pos !=',' ; pos++); - *strpos= pos; + uint find= find_type_eol(pos, lib, + FIND_TYPE_COMMA_TERM | FIND_TYPE_NO_PREFIX, end); return find; } diff --git a/mysys/waiting_threads.c b/mysys/waiting_threads.c new file mode 100644 index 00000000000..c861dcc738c --- /dev/null +++ b/mysys/waiting_threads.c @@ -0,0 +1,1152 @@ +/* Copyright (C) 2008 MySQL AB, 2008-2009 Sun Microsystems, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/** + @file + + "waiting threads" subsystem - a unified interface for threads to wait + on each other, with built-in deadlock detection. + + Main concepts + ^^^^^^^^^^^^^ + a thread - is represented by a WT_THD structure. One physical thread + can have only one WT_THD descriptor at any given moment. + + a resource - a thread does not wait for other threads directly, + instead it waits for a "resource", which is "owned" by other threads. + It waits, exactly, for all "owners" to "release" a resource. + It does not have to correspond to a physical resource. For example, it + may be convenient in certain cases to force resource == thread. + A resource is represented by a WT_RESOURCE structure. + + a resource identifier - a pair of {resource type, value}. A value is + an ulonglong number. Represented by a WT_RESOURCE_ID structure. + + a resource type - a pointer to a statically defined instance of + WT_RESOURCE_TYPE structure. This structure contains a pointer to + a function that knows how to compare values of this resource type. + In the simple case it could be wt_resource_id_memcmp(). + + a wait-for graph - a graph, that represenst "wait-for" relationships. + It has two types of nodes - threads and resources. There are directed + edges from a thread to a resource it is waiting for (WT_THD::waiting_for), + from a thread to resources that it "owns" (WT_THD::my_resources), + and from a resource to threads that "own" it (WT_RESOURCE::owners) + + Graph completeness + ^^^^^^^^^^^^^^^^^^ + + For flawless deadlock detection wait-for graph must be complete. + It means that when a thread starts waiting it needs to know *all* its + blockers, and call wt_thd_will_wait_for() for every one of them. + Otherwise two phenomena should be expected: + + 1. Fuzzy timeouts: + + thread A needs to get a lock, and is blocked by a thread B. + it waits. + Just before the timeout thread B releases the lock. + thread A is ready to grab the lock but discovers that it is also + blocked by a thread C. + It waits and times out. + + As a result thread A has waited two timeout intervals, instead of one. + + 2. Unreliable cycle detection: + + Thread A waits for threads B and C + Thread C waits for D + Thread D wants to start waiting for A + + one can see immediately that thread D creates a cycle, and thus + a deadlock is detected. + + But if thread A would only wait for B, and start waiting for C + when B would unlock, thread D would be allowed to wait, a deadlock + would be only detected when B unlocks or somebody times out. + + These two phenomena don't affect a correctness, and strictly speaking, + the caller is not required to call wt_thd_will_wait_for() for *all* + blockers - it may optimize wt_thd_will_wait_for() calls. But they + may be perceived as bugs by users, it must be understood that such + an optimization comes with its price. + + Usage + ^^^^^ + + First, the wt* subsystem must be initialized by calling + wt_init(). In the server you don't need to do it, it's done + in mysqld.cc. + + Similarly, wt_end() frees wt* structures, should be called + at the end, but in the server mysqld.cc takes care of that. + + Every WT_THD should be initialized with wt_thd_lazy_init(). + After that they can be used in other wt_thd_* calls. + Before discarding, WT_THD should be free'd with + wt_thd_destroy(). In the server both are handled in sql_class.cc, + it's an error to try to do it manually. + + To use the deadlock detection one needs to use this thread's WT_THD, + call wt_thd_will_wait_for() for every thread it needs to wait on, + then call wt_thd_cond_timedwait(). When thread releases a resource + it should call wt_thd_release() (or wt_thd_release_all()) - it will + notify (send a signal) threads waiting in wt_thd_cond_timedwait(), + if appropriate. + + Just like with pthread's cond_wait, there could be spurious + wake-ups from wt_thd_cond_timedwait(). A caller is expected to + handle that (that is, to re-check the blocking criteria). + + wt_thd_will_wait_for() and wt_thd_cond_timedwait() return either + WT_OK or WT_DEADLOCK. Additionally wt_thd_cond_timedwait() can return + WT_TIMEOUT. Out of memory and other fatal errors are reported as + WT_DEADLOCK - and a transaction must be aborted just the same. + + Configuration + ^^^^^^^^^^^^^ + There are four config variables. Two deadlock search depths - short and + long - and two timeouts. Deadlock search is performed with the short + depth on every wt_thd_will_wait_for() call. wt_thd_cond_timedwait() + waits with a short timeout, performs a deadlock search with the long + depth, and waits with a long timeout. As most deadlock cycles are supposed + to be short, most deadlocks will be detected at once, and waits will + rarely be necessary. + + These config variables are thread-local. Different threads may have + different search depth and timeout values. + + Also, deadlock detector supports different killing strategies, the victim + in a deadlock cycle is selected based on the "weight". See "weight" + description in waiting_threads.h for details. It's up to the caller to + set weights accordingly. + + Status + ^^^^^^ + We calculate the number of successfull waits (WT_OK returned from + wt_thd_cond_timedwait()), a number of timeouts, a deadlock cycle + length distribution - number of deadlocks with every length from + 1 to WT_CYCLE_STATS, and a wait time distribution - number + of waits with a time from 1 us to 1 min in WT_WAIT_STATS + intervals on a log e scale. +*/ + +/* + Note that if your lock system satisfy the following condition: + + there exist four lock levels A, B, C, D, such as + A is compatible with B + A is not compatible with C + D is not compatible with B + + (example A=IX, B=IS, C=S, D=X) + + you need to include lock level in the resource identifier - a + thread waiting for lock of the type A on resource R and another + thread waiting for lock of the type B on resource R should wait on + different WT_RESOURCE structures, on different {lock, resource} + pairs. Otherwise the following is possible: + + thread1> take S-lock on R + thread2> take IS-lock on R + thread3> wants X-lock on R, starts waiting for threads 1 and 2 on R. + thread3 is killed (or timeout or whatever) + WT_RESOURCE structure for R is still in the hash, as it has two owners + thread4> wants an IX-lock on R + WT_RESOURCE for R is found in the hash, thread4 starts waiting on it. + !! now thread4 is waiting for both thread1 and thread2 + !! while, in fact, IX-lock and IS-lock are compatible and + !! thread4 should not wait for thread2. +*/ + +#include <waiting_threads.h> +#include <m_string.h> + +/* status variables */ + +/** + preset table of wait intervals +*/ +ulonglong wt_wait_table[WT_WAIT_STATS]; +/** + wait time distribution (log e scale) +*/ +uint32 wt_wait_stats[WT_WAIT_STATS+1]; +/** + distribution of cycle lengths + first column tells whether this was during short or long detection +*/ +uint32 wt_cycle_stats[2][WT_CYCLE_STATS+1]; +uint32 wt_success_stats; + +static my_atomic_rwlock_t cycle_stats_lock, wait_stats_lock, success_stats_lock; + +#ifdef HAVE_PSI_INTERFACE +extern PSI_cond_key key_WT_RESOURCE_cond; +#endif + +#ifdef SAFE_STATISTICS +#define incr(VAR, LOCK) \ + do { \ + my_atomic_rwlock_wrlock(&(LOCK)); \ + my_atomic_add32(&(VAR), 1); \ + my_atomic_rwlock_wrunlock(&(LOCK)); \ + } while(0) +#else +#define incr(VAR,LOCK) do { (VAR)++; } while(0) +#endif + +static void increment_success_stats() +{ + incr(wt_success_stats, success_stats_lock); +} + +static void increment_cycle_stats(uint depth, uint slot) +{ + if (depth >= WT_CYCLE_STATS) + depth= WT_CYCLE_STATS; + incr(wt_cycle_stats[slot][depth], cycle_stats_lock); +} + +static void increment_wait_stats(ulonglong waited,int ret) +{ + uint i; + if ((ret) == ETIMEDOUT) + i= WT_WAIT_STATS; + else + for (i= 0; i < WT_WAIT_STATS && waited/10 > wt_wait_table[i]; i++) ; + incr(wt_wait_stats[i], wait_stats_lock); +} + +/* + 'lock' protects 'owners', 'state', and 'waiter_count' + 'id' is read-only + + a resource is picked up from a hash in a lock-free manner + it's returned pinned, so it cannot be freed at once + but it may be freed right after the pin is removed + to free a resource it should + 1. have no owners + 2. have no waiters + + two ways to access a resource: + 1. find it in a hash + - it's returned pinned. + a) take a lock in exclusive mode + b) check the state, it should be ACTIVE to be usable + c) unpin + 2. by a direct reference + - could only used if a resource cannot be freed + e.g. accessing a resource by thd->waiting_for is safe, + a resource cannot be freed as there's a thread waiting for it +*/ +struct st_wt_resource { + WT_RESOURCE_ID id; + uint waiter_count; + enum { ACTIVE, FREE } state; +#ifndef DBUG_OFF + mysql_mutex_t *cond_mutex; /* a mutex for the 'cond' below */ +#endif + /* + before the 'lock' all elements are mutable, after (and including) - + immutable in the sense that lf_hash_insert() won't memcpy() over them. + See wt_init(). + */ +#ifdef WT_RWLOCKS_USE_MUTEXES + /* + we need a special rwlock-like 'lock' to allow readers bypass + waiting writers, otherwise readers can deadlock. For example: + + A waits on resource x, owned by B, B waits on resource y, owned + by A, we have a cycle (A->x->B->y->A) + Both A and B start deadlock detection: + + A locks x B locks y + A goes deeper B goes deeper + A locks y B locks x + + with mutexes it would deadlock. With rwlocks it won't, as long + as both A and B are taking read locks (and they do). + But other threads may take write locks. Assume there's + C who wants to start waiting on x, and D who wants to start + waiting on y. + + A read-locks x B read-locks y + A goes deeper B goes deeper + => C write-locks x (to add a new edge) D write-locks y + .. C is blocked D is blocked + A read-locks y B read-locks x + + Now, if a read lock can bypass a pending wrote lock request, we're fine. + If it can not, we have a deadlock. + + writer starvation is technically possible, but unlikely, because + the contention is expected to be low. + */ + struct { + pthread_cond_t cond; + pthread_mutex_t mutex; + uint readers: 16; + uint pending_writers: 15; + uint write_locked: 1; + } lock; +#else + rw_lock_t lock; +#endif + mysql_cond_t cond; /* the corresponding mutex is provided by the caller */ + DYNAMIC_ARRAY owners; +}; + +#ifdef WT_RWLOCKS_USE_MUTEXES +static void rc_rwlock_init(WT_RESOURCE *rc) +{ + pthread_cond_init(&rc->lock.cond, 0); + pthread_mutex_init(&rc->lock.mutex, MY_MUTEX_INIT_FAST); +} +static void rc_rwlock_destroy(WT_RESOURCE *rc) +{ + DBUG_ASSERT(rc->lock.write_locked == 0); + DBUG_ASSERT(rc->lock.readers == 0); + pthread_cond_destroy(&rc->lock.cond); + pthread_mutex_destroy(&rc->lock.mutex); +} +static void rc_rdlock(WT_RESOURCE *rc) +{ + DBUG_PRINT("wt", ("TRYLOCK resid=%ld for READ", (ulong)rc->id.value)); + pthread_mutex_lock(&rc->lock.mutex); + while (rc->lock.write_locked) + pthread_cond_wait(&rc->lock.cond, &rc->lock.mutex); + rc->lock.readers++; + pthread_mutex_unlock(&rc->lock.mutex); + DBUG_PRINT("wt", ("LOCK resid=%ld for READ", (ulong)rc->id.value)); +} +static void rc_wrlock(WT_RESOURCE *rc) +{ + DBUG_PRINT("wt", ("TRYLOCK resid=%ld for WRITE", (ulong)rc->id.value)); + pthread_mutex_lock(&rc->lock.mutex); + while (rc->lock.write_locked || rc->lock.readers) + pthread_cond_wait(&rc->lock.cond, &rc->lock.mutex); + rc->lock.write_locked= 1; + pthread_mutex_unlock(&rc->lock.mutex); + DBUG_PRINT("wt", ("LOCK resid=%ld for WRITE", (ulong)rc->id.value)); +} +static void rc_unlock(WT_RESOURCE *rc) +{ + DBUG_PRINT("wt", ("UNLOCK resid=%ld", (ulong)rc->id.value)); + pthread_mutex_lock(&rc->lock.mutex); + if (rc->lock.write_locked) + { + rc->lock.write_locked= 0; + pthread_cond_broadcast(&rc->lock.cond); + } + else if (--rc->lock.readers == 0) + pthread_cond_broadcast(&rc->lock.cond); + pthread_mutex_unlock(&rc->lock.mutex); +} +#else +static void rc_rwlock_init(WT_RESOURCE *rc) +{ + my_rwlock_init(&rc->lock, 0); +} +static void rc_rwlock_destroy(WT_RESOURCE *rc) +{ + rwlock_destroy(&rc->lock); +} +static void rc_rdlock(WT_RESOURCE *rc) +{ + DBUG_PRINT("wt", ("TRYLOCK resid=%ld for READ", (ulong)rc->id.value)); + rw_rdlock(&rc->lock); + DBUG_PRINT("wt", ("LOCK resid=%ld for READ", (ulong)rc->id.value)); +} +static void rc_wrlock(WT_RESOURCE *rc) +{ + DBUG_PRINT("wt", ("TRYLOCK resid=%ld for WRITE", (ulong)rc->id.value)); + rw_wrlock(&rc->lock); + DBUG_PRINT("wt", ("LOCK resid=%ld for WRITE", (ulong)rc->id.value)); +} +static void rc_unlock(WT_RESOURCE *rc) +{ + DBUG_PRINT("wt", ("UNLOCK resid=%ld", (ulong)rc->id.value)); + rw_unlock(&rc->lock); +} +#endif + +/* + All resources are stored in a lock-free hash. Different threads + may add new resources and perform deadlock detection concurrently. +*/ +static LF_HASH reshash; + +/** + WT_RESOURCE constructor + + It's called from lf_hash and takes a pointer to an LF_SLIST instance. + WT_RESOURCE is located at arg+sizeof(LF_SLIST) +*/ +static void wt_resource_init(uchar *arg) +{ + WT_RESOURCE *rc= (WT_RESOURCE*)(arg+LF_HASH_OVERHEAD); + DBUG_ENTER("wt_resource_init"); + + bzero(rc, sizeof(*rc)); + rc_rwlock_init(rc); + mysql_cond_init(key_WT_RESOURCE_cond, &rc->cond, 0); + my_init_dynamic_array(&rc->owners, sizeof(WT_THD *), 0, 5); + DBUG_VOID_RETURN; +} + +/** + WT_RESOURCE destructor + + It's called from lf_hash and takes a pointer to an LF_SLIST instance. + WT_RESOURCE is located at arg+sizeof(LF_SLIST) +*/ +static void wt_resource_destroy(uchar *arg) +{ + WT_RESOURCE *rc= (WT_RESOURCE*)(arg+LF_HASH_OVERHEAD); + DBUG_ENTER("wt_resource_destroy"); + + DBUG_ASSERT(rc->owners.elements == 0); + rc_rwlock_destroy(rc); + mysql_cond_destroy(&rc->cond); + delete_dynamic(&rc->owners); + DBUG_VOID_RETURN; +} + +static int wt_init_done; + +void wt_init() +{ + DBUG_ENTER("wt_init"); + DBUG_ASSERT(reshash.alloc.constructor != wt_resource_init); + + lf_hash_init(&reshash, sizeof(WT_RESOURCE), LF_HASH_UNIQUE, 0, + sizeof_WT_RESOURCE_ID, 0, 0); + reshash.alloc.constructor= wt_resource_init; + reshash.alloc.destructor= wt_resource_destroy; + /* + Note a trick: we initialize the hash with the real element size, + but fix it later to a shortened element size. This way + the allocator will allocate elements correctly, but + lf_hash_insert() will only overwrite part of the element with memcpy(). + lock, condition, and dynamic array will be intact. + */ + reshash.element_size= offsetof(WT_RESOURCE, lock); + bzero(wt_wait_stats, sizeof(wt_wait_stats)); + bzero(wt_cycle_stats, sizeof(wt_cycle_stats)); + wt_success_stats= 0; + { /* initialize wt_wait_table[]. from 1 us to 1 min, log e scale */ + int i; + double from= log(1); /* 1 us */ + double to= log(60e6); /* 1 min */ + for (i= 0; i < WT_WAIT_STATS; i++) + { + wt_wait_table[i]= (ulonglong)exp((to-from)/(WT_WAIT_STATS-1)*i+from); + DBUG_ASSERT(i == 0 || wt_wait_table[i-1] != wt_wait_table[i]); + } + } + my_atomic_rwlock_init(&cycle_stats_lock); + my_atomic_rwlock_init(&success_stats_lock); + my_atomic_rwlock_init(&wait_stats_lock); + wt_init_done= 1; + DBUG_VOID_RETURN; +} + +void wt_end() +{ + DBUG_ENTER("wt_end"); + if (!wt_init_done) + DBUG_VOID_RETURN; + + DBUG_ASSERT(reshash.count == 0); + lf_hash_destroy(&reshash); + my_atomic_rwlock_destroy(&cycle_stats_lock); + my_atomic_rwlock_destroy(&success_stats_lock); + my_atomic_rwlock_destroy(&wait_stats_lock); + reshash.alloc.constructor= NULL; + wt_init_done= 0; + DBUG_VOID_RETURN; +} + +/** + Lazy WT_THD initialization + + Cheap initialization of WT_THD. Only initialize fields that don't require + memory allocations - basically, it only does assignments. The rest of the + WT_THD structure will be initialized on demand, on the first use. + This allows one to initialize lazily all WT_THD structures, even if some + (or even most) of them will never be used for deadlock detection. + + @param ds a pointer to deadlock search depth short value + @param ts a pointer to deadlock timeout short value (microseconds) + @param dl a pointer to deadlock search depth long value + @param tl a pointer to deadlock timeout long value (microseconds) + + @note these are pointers to values, and WT_THD stores them as pointers. + It allows one later to change search depths and timeouts for existing + threads. It also means that the pointers must stay valid for the lifetime + of WT_THD. +*/ +void wt_thd_lazy_init(WT_THD *thd, const ulong *ds, const ulong *ts, + const ulong *dl, const ulong *tl) +{ + DBUG_ENTER("wt_thd_lazy_init"); + thd->waiting_for= 0; + thd->weight= 0; + thd->deadlock_search_depth_short= ds; + thd->timeout_short= ts; + thd->deadlock_search_depth_long= dl; + thd->timeout_long= tl; + /* dynamic array is also initialized lazily - without memory allocations */ + my_init_dynamic_array(&thd->my_resources, sizeof(WT_RESOURCE *), 0, 5); +#ifndef DBUG_OFF + thd->name= my_thread_name(); +#endif + DBUG_VOID_RETURN; +} + +/** + Finalize WT_THD initialization + + After lazy WT_THD initialization, parts of the structure are still + uninitialized. This function completes the initialization, allocating + memory, if necessary. It's called automatically on demand, when WT_THD + is about to be used. +*/ +static int fix_thd_pins(WT_THD *thd) +{ + if (unlikely(thd->pins == 0)) + { + thd->pins= lf_hash_get_pins(&reshash); +#ifndef DBUG_OFF + thd->name= my_thread_name(); +#endif + } + return thd->pins == 0; +} + +void wt_thd_destroy(WT_THD *thd) +{ + DBUG_ENTER("wt_thd_destroy"); + + DBUG_ASSERT(thd->my_resources.elements == 0); + DBUG_ASSERT(thd->waiting_for == 0); + + if (thd->pins != 0) + lf_hash_put_pins(thd->pins); + + delete_dynamic(&thd->my_resources); + DBUG_VOID_RETURN; +} +/** + Trivial resource id comparison function - bytewise memcmp. + + It can be used in WT_RESOURCE_TYPE structures where bytewise + comparison of values is sufficient. +*/ +my_bool wt_resource_id_memcmp(const void *a, const void *b) +{ + /* we use the fact that there's no padding in the middle of WT_RESOURCE_ID */ + compile_time_assert(offsetof(WT_RESOURCE_ID, type) == sizeof(ulonglong)); + return memcmp(a, b, sizeof_WT_RESOURCE_ID); +} + +/** + arguments for the recursive deadlock_search function +*/ +struct deadlock_arg { + WT_THD * const thd; /**< starting point of a search */ + uint const max_depth; /**< search depth limit */ + WT_THD *victim; /**< a thread to be killed to resolve a deadlock */ + WT_RESOURCE *last_locked_rc; /**< see comment at the end of deadlock_search() */ +}; + +/** + helper function to change the victim, according to the weight +*/ +static void change_victim(WT_THD* found, struct deadlock_arg *arg) +{ + if (found->weight < arg->victim->weight) + { + if (arg->victim != arg->thd) + { + rc_unlock(arg->victim->waiting_for); /* release the previous victim */ + DBUG_ASSERT(arg->last_locked_rc == found->waiting_for); + } + arg->victim= found; + arg->last_locked_rc= 0; + } +} + +/** + recursive loop detection in a wait-for graph with a limited search depth +*/ +static int deadlock_search(struct deadlock_arg *arg, WT_THD *blocker, + uint depth) +{ + WT_RESOURCE *rc, *volatile *shared_ptr= &blocker->waiting_for; + WT_THD *cursor; + uint i; + int ret= WT_OK; + DBUG_ENTER("deadlock_search"); + DBUG_PRINT("wt", ("enter: thd=%s, blocker=%s, depth=%u", + arg->thd->name, blocker->name, depth)); + + LF_REQUIRE_PINS(1); + + arg->last_locked_rc= 0; + + if (depth > arg->max_depth) + { + DBUG_PRINT("wt", ("exit: WT_DEPTH_EXCEEDED (early)")); + DBUG_RETURN(WT_DEPTH_EXCEEDED); + } + +retry: + /* + safe dereference as explained in lf_alloc-pin.c + (in short: protects against lf_alloc_free() in lf_hash_delete()) + */ + do + { + rc= *shared_ptr; + lf_pin(arg->thd->pins, 0, rc); + } while (rc != *shared_ptr && LF_BACKOFF); + + if (rc == 0) + { + DBUG_PRINT("wt", ("exit: OK (early)")); + DBUG_RETURN(0); + } + + rc_rdlock(rc); + if (rc->state != ACTIVE || *shared_ptr != rc) + { + /* blocker is not waiting on this resource anymore */ + rc_unlock(rc); + lf_unpin(arg->thd->pins, 0); + goto retry; + } + /* as the state is locked, we can unpin now */ + lf_unpin(arg->thd->pins, 0); + + /* + Below is not a pure depth-first search. It's a depth-first with a + slightest hint of breadth-first. Depth-first is: + + check(element, X): + foreach current in element->nodes[] do: + if current == X return error; + check(current, X); + + while we do + + check(element, X): + foreach current in element->nodes[] do: + if current == X return error; + foreach current in element->nodes[] do: + check(current, X); + + preferring shorter deadlocks over longer ones. + */ + for (i= 0; i < rc->owners.elements; i++) + { + cursor= *dynamic_element(&rc->owners, i, WT_THD**); + /* + We're only looking for (and detecting) cycles that include 'arg->thd'. + That is, only deadlocks that *we* have created. For example, + thd->A->B->thd + (thd waits for A, A waits for B, while B is waiting for thd). + While walking the graph we can encounter other cicles, e.g. + thd->A->B->C->A + This will not be detected. Instead we will walk it in circles until + the search depth limit is reached (the latter guarantees that an + infinite loop is impossible). We expect the thread that has created + the cycle (one of A, B, and C) to detect its deadlock. + */ + if (cursor == arg->thd) + { + ret= WT_DEADLOCK; + increment_cycle_stats(depth, arg->max_depth == + *arg->thd->deadlock_search_depth_long); + arg->victim= cursor; + goto end; + } + } + for (i= 0; i < rc->owners.elements; i++) + { + cursor= *dynamic_element(&rc->owners, i, WT_THD**); + switch (deadlock_search(arg, cursor, depth+1)) { + case WT_OK: + break; + case WT_DEPTH_EXCEEDED: + ret= WT_DEPTH_EXCEEDED; + break; + case WT_DEADLOCK: + ret= WT_DEADLOCK; + change_victim(cursor, arg); /* also sets arg->last_locked_rc to 0 */ + i= rc->owners.elements; /* jump out of the loop */ + break; + default: + DBUG_ASSERT(0); + } + if (arg->last_locked_rc) + rc_unlock(arg->last_locked_rc); + } +end: + /* + Note that 'rc' is locked in this function, but it's never unlocked here. + Instead it's saved in arg->last_locked_rc and the *caller* is + expected to unlock it. It's done to support different killing + strategies. This is how it works: + Assuming a graph + + thd->A->B->C->thd + + deadlock_search() function starts from thd, locks it (in fact it locks not + a thd, but a resource it is waiting on, but below, for simplicity, I'll + talk about "locking a thd"). Then it goes down recursively, locks A, and so + on. Goes down recursively, locks B. Goes down recursively, locks C. + Notices that C is waiting on thd. Deadlock detected. Sets arg->victim=thd. + Returns from the last deadlock_search() call. C stays locked! + Now it checks whether C is a more appropriate victim than 'thd'. + If yes - arg->victim=C, otherwise C is unlocked. Returns. B stays locked. + Now it checks whether B is a more appropriate victim than arg->victim. + If yes - old arg->victim is unlocked and arg->victim=B, + otherwise B is unlocked. Return. + And so on. + + In short, a resource is locked in a frame. But it's not unlocked in the + same frame, it's unlocked by the caller, and only after the caller checks + that it doesn't need to use current WT_THD as a victim. If it does - the + lock is kept and the old victim's resource is unlocked. When the recursion + is unrolled and we are back to deadlock() function, there are only two + locks left - on thd and on the victim. + */ + arg->last_locked_rc= rc; + DBUG_PRINT("wt", ("exit: %s", + ret == WT_DEPTH_EXCEEDED ? "WT_DEPTH_EXCEEDED" : + ret ? "WT_DEADLOCK" : "OK")); + DBUG_RETURN(ret); +} + +/** + Deadlock detection in a wait-for graph + + A wrapper for recursive deadlock_search() - prepares deadlock_arg structure, + invokes deadlock_search(), increments statistics, notifies the victim. + + @param thd thread that is going to wait. Deadlock is detected + if, while walking the graph, we reach a thread that + is waiting on thd + @param blocker starting point of a search. In wt_thd_cond_timedwait() + it's thd, in wt_thd_will_wait_for() it's a thread that + thd is going to wait for + @param depth starting search depth. In general it's the number of + edges in the wait-for graph between thd and the + blocker. Practically only two values are used (and + supported) - when thd == blocker it's 0, when thd + waits directly for blocker, it's 1 + @param max_depth search depth limit +*/ +static int deadlock(WT_THD *thd, WT_THD *blocker, uint depth, + uint max_depth) +{ + struct deadlock_arg arg= {thd, max_depth, 0, 0}; + int ret; + DBUG_ENTER("deadlock"); + DBUG_ASSERT(depth < 2); + ret= deadlock_search(&arg, blocker, depth); + if (ret == WT_DEPTH_EXCEEDED) + { + increment_cycle_stats(WT_CYCLE_STATS, max_depth == + *thd->deadlock_search_depth_long); + ret= WT_OK; + } + /* + if we started with depth==1, blocker was never considered for a victim + in deadlock_search(). Do it here. + */ + if (ret == WT_DEADLOCK && depth) + change_victim(blocker, &arg); + if (arg.last_locked_rc) + { + /* + Special return code if there's nobody to wait for. + + depth == 0 means that we start the search from thd (thd == blocker). + ret == WT_OK means that no cycle was found and + arg.last_locked_rc == thd->waiting_for. + and arg.last_locked_rc->owners.elements == 0 means that + (applying the rule above) thd->waiting_for->owners.elements == 0, + and thd doesn't have anybody to wait for. + */ + if (depth == 0 && ret == WT_OK && arg.last_locked_rc->owners.elements == 0) + { + DBUG_ASSERT(thd == blocker); + DBUG_ASSERT(arg.last_locked_rc == thd->waiting_for); + ret= WT_FREE_TO_GO; + } + rc_unlock(arg.last_locked_rc); + } + /* notify the victim, if appropriate */ + if (ret == WT_DEADLOCK && arg.victim != thd) + { + DBUG_PRINT("wt", ("killing %s", arg.victim->name)); + arg.victim->killed= 1; + mysql_cond_broadcast(&arg.victim->waiting_for->cond); + rc_unlock(arg.victim->waiting_for); + ret= WT_OK; + } + DBUG_RETURN(ret); +} + + +/** + Delete an element from reshash if it has no waiters or owners + + rc->lock must be locked by the caller and it's unlocked on return. +*/ +static int unlock_lock_and_free_resource(WT_THD *thd, WT_RESOURCE *rc) +{ + uint keylen; + const void *key; + DBUG_ENTER("unlock_lock_and_free_resource"); + + DBUG_ASSERT(rc->state == ACTIVE); + + if (rc->owners.elements || rc->waiter_count) + { + DBUG_PRINT("wt", ("nothing to do, %u owners, %u waiters", + rc->owners.elements, rc->waiter_count)); + rc_unlock(rc); + DBUG_RETURN(0); + } + + if (fix_thd_pins(thd)) + { + rc_unlock(rc); + DBUG_RETURN(1); + } + + /* XXX if (rc->id.type->make_key) key= rc->id.type->make_key(&rc->id, &keylen); else */ + { + key= &rc->id; + keylen= sizeof_WT_RESOURCE_ID; + } + + /* + To free the element correctly we need to: + 1. take its lock (already done). + 2. set the state to FREE + 3. release the lock + 4. remove from the hash + */ + rc->state= FREE; + rc_unlock(rc); + DBUG_RETURN(lf_hash_delete(&reshash, thd->pins, key, keylen) == -1); +} + + +/** + register the fact that thd is not waiting anymore + + decrease waiter_count, clear waiting_for, free the resource if appropriate. + thd->waiting_for must be locked! +*/ +static int stop_waiting_locked(WT_THD *thd) +{ + int ret; + WT_RESOURCE *rc= thd->waiting_for; + DBUG_ENTER("stop_waiting_locked"); + + DBUG_ASSERT(rc->waiter_count); + DBUG_ASSERT(rc->state == ACTIVE); + rc->waiter_count--; + thd->waiting_for= 0; + ret= unlock_lock_and_free_resource(thd, rc); + DBUG_RETURN((thd->killed || ret) ? WT_DEADLOCK : WT_OK); +} + +/** + register the fact that thd is not waiting anymore + + locks thd->waiting_for and calls stop_waiting_locked(). +*/ +static int stop_waiting(WT_THD *thd) +{ + int ret; + WT_RESOURCE *rc= thd->waiting_for; + DBUG_ENTER("stop_waiting"); + + if (!rc) + DBUG_RETURN(WT_OK); + /* + nobody's trying to free the resource now, + as its waiter_count is guaranteed to be non-zero + */ + rc_wrlock(rc); + ret= stop_waiting_locked(thd); + DBUG_RETURN(ret); +} + +/** + notify the system that a thread needs to wait for another thread + + called by a *waiter* to declare that it (thd) will wait for another + thread (blocker) on a specific resource (resid). + can be called many times, if many blockers own a blocking resource. + but must always be called with the same resource id - a thread cannot + wait for more than one resource at a time. + + @return WT_OK or WT_DEADLOCK + + As a new edge is added to the wait-for graph, a deadlock detection is + performed for this new edge. +*/ +int wt_thd_will_wait_for(WT_THD *thd, WT_THD *blocker, + const WT_RESOURCE_ID *resid) +{ + uint i; + WT_RESOURCE *rc; + DBUG_ENTER("wt_thd_will_wait_for"); + + LF_REQUIRE_PINS(3); + + DBUG_PRINT("wt", ("enter: thd=%s, blocker=%s, resid=%lu", + thd->name, blocker->name, (ulong)resid->value)); + + if (fix_thd_pins(thd)) + DBUG_RETURN(WT_DEADLOCK); + + if (thd->waiting_for == 0) + { + uint keylen; + const void *key; + /* XXX if (restype->make_key) key= restype->make_key(resid, &keylen); else */ + { + key= resid; + keylen= sizeof_WT_RESOURCE_ID; + } + + DBUG_PRINT("wt", ("first blocker")); + +retry: + while ((rc= lf_hash_search(&reshash, thd->pins, key, keylen)) == 0) + { + WT_RESOURCE tmp; + + DBUG_PRINT("wt", ("failed to find rc in hash, inserting")); + bzero(&tmp, sizeof(tmp)); + tmp.id= *resid; + tmp.state= ACTIVE; + + if (lf_hash_insert(&reshash, thd->pins, &tmp) == -1) /* if OOM */ + DBUG_RETURN(WT_DEADLOCK); + /* + Two cases: either lf_hash_insert() failed - because another thread + has just inserted a resource with the same id - and we need to retry. + Or lf_hash_insert() succeeded, and then we need to repeat + lf_hash_search() to find a real address of the newly inserted element. + That is, we don't care what lf_hash_insert() has returned. + And we need to repeat the loop anyway. + */ + } + if (rc == MY_ERRPTR) + DBUG_RETURN(WT_DEADLOCK); + + DBUG_PRINT("wt", ("found in hash rc=%p", rc)); + + rc_wrlock(rc); + if (rc->state != ACTIVE) + { + DBUG_PRINT("wt", ("but it's not active, retrying")); + /* Somebody has freed the element while we weren't looking */ + rc_unlock(rc); + lf_hash_search_unpin(thd->pins); + goto retry; + } + + lf_hash_search_unpin(thd->pins); /* the element cannot go away anymore */ + thd->waiting_for= rc; + rc->waiter_count++; + thd->killed= 0; + } + else + { + DBUG_ASSERT(thd->waiting_for->id.type == resid->type); + DBUG_ASSERT(resid->type->compare(&thd->waiting_for->id, resid) == 0); + DBUG_PRINT("wt", ("adding another blocker")); + + /* + we can safely access the resource here, it's in the hash as it has + non-zero waiter_count + */ + rc= thd->waiting_for; + rc_wrlock(rc); + DBUG_ASSERT(rc->waiter_count); + DBUG_ASSERT(rc->state == ACTIVE); + + if (thd->killed) + { + stop_waiting_locked(thd); + DBUG_RETURN(WT_DEADLOCK); + } + } + /* + Another thread could be waiting on this resource for this very 'blocker'. + In this case we should not add it to the list for the second time. + */ + for (i= 0; i < rc->owners.elements; i++) + if (*dynamic_element(&rc->owners, i, WT_THD**) == blocker) + break; + if (i >= rc->owners.elements) + { + if (push_dynamic(&blocker->my_resources, (void*)&rc)) + { + stop_waiting_locked(thd); + DBUG_RETURN(WT_DEADLOCK); /* deadlock and OOM use the same error code */ + } + if (push_dynamic(&rc->owners, (void*)&blocker)) + { + pop_dynamic(&blocker->my_resources); + stop_waiting_locked(thd); + DBUG_RETURN(WT_DEADLOCK); + } + } + rc_unlock(rc); + + if (deadlock(thd, blocker, 1, *thd->deadlock_search_depth_short) != WT_OK) + { + stop_waiting(thd); + DBUG_RETURN(WT_DEADLOCK); + } + DBUG_RETURN(WT_OK); +} + +/** + called by a *waiter* (thd) to start waiting + + It's supposed to be a drop-in replacement for + mysql_cond_timedwait(), and it takes mutex as an argument. + + @return one of WT_TIMEOUT, WT_DEADLOCK, WT_OK +*/ +int wt_thd_cond_timedwait(WT_THD *thd, mysql_mutex_t *mutex) +{ + int ret= WT_TIMEOUT; + struct timespec timeout; + my_hrtime_t before, after, starttime; + WT_RESOURCE *rc= thd->waiting_for; + ulonglong end_wait_time; + DBUG_ENTER("wt_thd_cond_timedwait"); + DBUG_PRINT("wt", ("enter: thd=%s, rc=%p", thd->name, rc)); + +#ifndef DBUG_OFF + if (rc->cond_mutex) + DBUG_ASSERT(rc->cond_mutex == mutex); + else + rc->cond_mutex= mutex; + mysql_mutex_assert_owner(mutex); +#endif + + before= starttime= my_hrtime(); + + rc_wrlock(rc); + if (rc->owners.elements == 0) + ret= WT_OK; + rc_unlock(rc); + + end_wait_time= starttime.val *1000 + (*thd->timeout_short)*ULL(1000000); + set_timespec_time_nsec(timeout, end_wait_time); + if (ret == WT_TIMEOUT && !thd->killed) + ret= mysql_cond_timedwait(&rc->cond, mutex, &timeout); + if (ret == WT_TIMEOUT && !thd->killed) + { + int r= deadlock(thd, thd, 0, *thd->deadlock_search_depth_long); + if (r == WT_FREE_TO_GO) + ret= WT_OK; + else if (r != WT_OK) + ret= WT_DEADLOCK; + else if (*thd->timeout_long > *thd->timeout_short) + { + end_wait_time= starttime.val *1000 + (*thd->timeout_long)*ULL(1000000); + set_timespec_time_nsec(timeout, end_wait_time); + if (!thd->killed) + ret= mysql_cond_timedwait(&rc->cond, mutex, &timeout); + } + } + after= my_hrtime(); + if (stop_waiting(thd) == WT_DEADLOCK) /* if we're killed */ + ret= WT_DEADLOCK; + increment_wait_stats(after.val-before.val, ret); + if (ret == WT_OK) + increment_success_stats(); + DBUG_RETURN(ret); +} + +/** + called by a *blocker* when it releases a resource + + it's conceptually similar to pthread_cond_broadcast, and must be done + under the same mutex as wt_thd_cond_timedwait(). + + @param resid a resource to release. 0 to release all resources +*/ + +void wt_thd_release(WT_THD *thd, const WT_RESOURCE_ID *resid) +{ + uint i; + DBUG_ENTER("wt_thd_release"); + + for (i= 0; i < thd->my_resources.elements; i++) + { + WT_RESOURCE *rc= *dynamic_element(&thd->my_resources, i, WT_RESOURCE**); + if (!resid || (resid->type->compare(&rc->id, resid) == 0)) + { + uint j; + + rc_wrlock(rc); + /* + nobody's trying to free the resource now, + as its owners[] array is not empty (at least thd must be there) + */ + DBUG_ASSERT(rc->state == ACTIVE); + for (j= 0; j < rc->owners.elements; j++) + if (*dynamic_element(&rc->owners, j, WT_THD**) == thd) + break; + DBUG_ASSERT(j < rc->owners.elements); + delete_dynamic_element(&rc->owners, j); + if (rc->owners.elements == 0) + { + mysql_cond_broadcast(&rc->cond); +#ifndef DBUG_OFF + if (rc->cond_mutex) + mysql_mutex_assert_owner(rc->cond_mutex); +#endif + } + unlock_lock_and_free_resource(thd, rc); + if (resid) + { + delete_dynamic_element(&thd->my_resources, i); + DBUG_VOID_RETURN; + } + } + } + if (!resid) + reset_dynamic(&thd->my_resources); + DBUG_VOID_RETURN; +} + diff --git a/mysys/wqueue.c b/mysys/wqueue.c new file mode 100644 index 00000000000..2fcced14f77 --- /dev/null +++ b/mysys/wqueue.c @@ -0,0 +1,241 @@ +/* + Copyright (c) 2007, 2008, Sun Microsystems, Inc, + Copyright (c) 2011, 2012, Monty Program Ab + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <wqueue.h> + +#define STRUCT_PTR(TYPE, MEMBER, a) \ + (TYPE *) ((char *) (a) - offsetof(TYPE, MEMBER)) +/* + Link a thread into double-linked queue of waiting threads. + + SYNOPSIS + wqueue_link_into_queue() + wqueue pointer to the queue structure + thread pointer to the thread to be added to the queue + + RETURN VALUE + none + + NOTES. + Queue is represented by a circular list of the thread structures + The list is double-linked of the type (**prev,*next), accessed by + a pointer to the last element. +*/ + +void wqueue_link_into_queue(WQUEUE *wqueue, struct st_my_thread_var *thread) +{ + struct st_my_thread_var *last; + if (!(last= wqueue->last_thread)) + { + /* Queue is empty */ + thread->next= thread; + thread->prev= &thread->next; + } + else + { + thread->prev= last->next->prev; + last->next->prev= &thread->next; + thread->next= last->next; + last->next= thread; + } + wqueue->last_thread= thread; +} + + +/* + Add a thread to single-linked queue of waiting threads + + SYNOPSIS + wqueue_add_to_queue() + wqueue pointer to the queue structure + thread pointer to the thread to be added to the queue + + RETURN VALUE + none + + NOTES. + Queue is represented by a circular list of the thread structures + The list is single-linked of the type (*next), accessed by a pointer + to the last element. +*/ + +void wqueue_add_to_queue(WQUEUE *wqueue, struct st_my_thread_var *thread) +{ + struct st_my_thread_var *last; + if (!(last= wqueue->last_thread)) + thread->next= thread; + else + { + thread->next= last->next; + last->next= thread; + } +#ifndef DBUG_OFF + thread->prev= NULL; /* force segfault if used */ +#endif + wqueue->last_thread= thread; +} + +/* + Unlink a thread from double-linked queue of waiting threads + + SYNOPSIS + wqueue_unlink_from_queue() + wqueue pointer to the queue structure + thread pointer to the thread to be removed from the queue + + RETURN VALUE + none + + NOTES. + See NOTES for link_into_queue +*/ + +void wqueue_unlink_from_queue(WQUEUE *wqueue, struct st_my_thread_var *thread) +{ + if (thread->next == thread) + /* The queue contains only one member */ + wqueue->last_thread= NULL; + else + { + thread->next->prev= thread->prev; + *thread->prev= thread->next; + if (wqueue->last_thread == thread) + wqueue->last_thread= STRUCT_PTR(struct st_my_thread_var, next, + thread->prev); + } + thread->next= NULL; +} + + +/* + Remove all threads from queue signaling them to proceed + + SYNOPSIS + wqueue_realease_queue() + wqueue pointer to the queue structure + thread pointer to the thread to be added to the queue + + RETURN VALUE + none + + NOTES. + See notes for add_to_queue + When removed from the queue each thread is signaled via condition + variable thread->suspend. +*/ + +void wqueue_release_queue(WQUEUE *wqueue) +{ + struct st_my_thread_var *last= wqueue->last_thread; + struct st_my_thread_var *next= last->next; + struct st_my_thread_var *thread; + do + { + thread= next; + mysql_cond_signal(&thread->suspend); + next= thread->next; + thread->next= NULL; + } + while (thread != last); + wqueue->last_thread= NULL; +} + + +/** + @brief Removes all threads waiting for read or first one waiting for write. + + @param wqueue pointer to the queue structure + @param thread pointer to the thread to be added to the queue + + @note This function is applicable only to single linked lists. +*/ + +void wqueue_release_one_locktype_from_queue(WQUEUE *wqueue) +{ + struct st_my_thread_var *last= wqueue->last_thread; + struct st_my_thread_var *next= last->next; + struct st_my_thread_var *thread; + struct st_my_thread_var *new_list= NULL; + uint first_type= next->lock_type; + if (first_type == MY_PTHREAD_LOCK_WRITE) + { + /* release first waiting for write lock */ + mysql_cond_signal(&next->suspend); + if (next == last) + wqueue->last_thread= NULL; + else + last->next= next->next; + next->next= NULL; + return; + } + do + { + thread= next; + next= thread->next; + if (thread->lock_type == MY_PTHREAD_LOCK_WRITE) + { + /* skip waiting for write lock */ + if (new_list) + { + thread->next= new_list->next; + new_list= new_list->next= thread; + } + else + new_list= thread->next= thread; + } + else + { + /* release waiting for read lock */ + mysql_cond_signal(&thread->suspend); + thread->next= NULL; + } + } while (thread != last); + wqueue->last_thread= new_list; +} + + +/* + Add thread and wait + + SYNOPSYS + wqueue_add_and_wait() + wqueue queue to add to + thread thread which is waiting + lock mutex need for the operation +*/ + +void wqueue_add_and_wait(WQUEUE *wqueue, + struct st_my_thread_var *thread, + mysql_mutex_t *lock) +{ + DBUG_ENTER("wqueue_add_and_wait"); + DBUG_PRINT("enter", + ("thread: 0x%lx cond: 0x%lx mutex: 0x%lx", + (ulong) thread, (ulong) &thread->suspend, (ulong) lock)); + wqueue_add_to_queue(wqueue, thread); + do + { + DBUG_PRINT("info", ("wait... cond: 0x%lx mutex: 0x%lx", + (ulong) &thread->suspend, (ulong) lock)); + mysql_cond_wait(&thread->suspend, lock); + DBUG_PRINT("info", ("wait done cond: 0x%lx mutex: 0x%lx next: 0x%lx", + (ulong) &thread->suspend, (ulong) lock, + (ulong) thread->next)); + } + while (thread->next); + DBUG_VOID_RETURN; +} |