| 1 | /* Copyright (C) 2006 MySQL AB & MySQL Finland AB & TCX DataKonsult AB |
| 2 | |
| 3 | This program is free software; you can redistribute it and/or modify |
| 4 | it under the terms of the GNU General Public License as published by |
| 5 | the Free Software Foundation; version 2 of the License. |
| 6 | |
| 7 | This program is distributed in the hope that it will be useful, |
| 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 10 | GNU General Public License for more details. |
| 11 | |
| 12 | You should have received a copy of the GNU General Public License |
| 13 | along with this program; if not, write to the Free Software |
| 14 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ |
| 15 | |
| 16 | /* close a isam-database */ |
| 17 | /* |
| 18 | TODO: |
| 19 | We need to have a separate mutex on the closed file to allow other threads |
| 20 | to open other files during the time we flush the cache and close this file |
| 21 | */ |
| 22 | |
| 23 | #include "maria_def.h" |
| 24 | #include "ma_crypt.h" |
| 25 | |
| 26 | int maria_close(register MARIA_HA *info) |
| 27 | { |
| 28 | int error=0,flag; |
| 29 | my_bool share_can_be_freed= FALSE; |
| 30 | MARIA_SHARE *share= info->s; |
| 31 | my_bool internal_table= share->internal_table; |
| 32 | DBUG_ENTER("maria_close" ); |
| 33 | DBUG_PRINT("enter" ,("name: '%s' base: %p reopen: %u locks: %u" , |
| 34 | share->open_file_name.str, |
| 35 | info, (uint) share->reopen, |
| 36 | (uint) share->tot_locks)); |
| 37 | |
| 38 | /* Check that we have unlocked key delete-links properly */ |
| 39 | DBUG_ASSERT(info->key_del_used == 0); |
| 40 | |
| 41 | if (share->reopen == 1) |
| 42 | { |
| 43 | /* |
| 44 | If we are going to close the file, flush page cache without |
| 45 | a global mutex |
| 46 | */ |
| 47 | if (flush_pagecache_blocks(share->pagecache, &share->kfile, |
| 48 | ((share->temporary || share->deleting) ? |
| 49 | FLUSH_IGNORE_CHANGED : |
| 50 | FLUSH_RELEASE))) |
| 51 | error= my_errno; |
| 52 | } |
| 53 | |
| 54 | /* Ensure no one can open this file while we are closing it */ |
| 55 | if (!internal_table) |
| 56 | mysql_mutex_lock(&THR_LOCK_maria); |
| 57 | if (info->lock_type == F_EXTRA_LCK) |
| 58 | info->lock_type=F_UNLCK; /* HA_EXTRA_NO_USER_CHANGE */ |
| 59 | |
| 60 | if (info->lock_type != F_UNLCK) |
| 61 | { |
| 62 | if (maria_lock_database(info,F_UNLCK)) |
| 63 | error=my_errno; |
| 64 | } |
| 65 | if (!internal_table) |
| 66 | { |
| 67 | mysql_mutex_lock(&share->close_lock); |
| 68 | mysql_mutex_lock(&share->intern_lock); |
| 69 | } |
| 70 | |
| 71 | if (share->options & HA_OPTION_READ_ONLY_DATA) |
| 72 | { |
| 73 | share->r_locks--; |
| 74 | share->tot_locks--; |
| 75 | } |
| 76 | if (info->opt_flag & (READ_CACHE_USED | WRITE_CACHE_USED)) |
| 77 | { |
| 78 | if (end_io_cache(&info->rec_cache)) |
| 79 | error=my_errno; |
| 80 | info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); |
| 81 | } |
| 82 | flag= !--share->reopen; |
| 83 | if (!internal_table) |
| 84 | { |
| 85 | maria_open_list= list_delete(maria_open_list,&info->open_list); |
| 86 | share->open_list= list_delete(share->open_list, &info->share_list); |
| 87 | } |
| 88 | |
| 89 | my_free(info->rec_buff); |
| 90 | (*share->end)(info); |
| 91 | |
| 92 | if (flag) |
| 93 | { |
| 94 | /* Last close of file; Flush everything */ |
| 95 | |
| 96 | /* Check that we don't have any dangling pointers from the transaction */ |
| 97 | DBUG_ASSERT(share->in_trans == 0); |
| 98 | DBUG_ASSERT(share->open_list == 0); |
| 99 | |
| 100 | if (share->kfile.file >= 0) |
| 101 | { |
| 102 | my_bool save_global_changed= share->global_changed; |
| 103 | |
| 104 | /* Avoid _ma_mark_file_changed() when flushing pages */ |
| 105 | share->global_changed= 1; |
| 106 | |
| 107 | if ((*share->once_end)(share)) |
| 108 | error= my_errno; |
| 109 | /* |
| 110 | Extra flush, just in case someone opened and closed the file |
| 111 | since the start of the function (very unlikely) |
| 112 | */ |
| 113 | if (flush_pagecache_blocks(share->pagecache, &share->kfile, |
| 114 | ((share->temporary || share->deleting) ? |
| 115 | FLUSH_IGNORE_CHANGED : |
| 116 | FLUSH_RELEASE))) |
| 117 | error= my_errno; |
| 118 | #ifdef HAVE_MMAP |
| 119 | if (share->file_map) |
| 120 | _ma_unmap_file(info); |
| 121 | #endif |
| 122 | /* |
| 123 | If we are crashed, we can safely flush the current state as it will |
| 124 | not change the crashed state. |
| 125 | We can NOT write the state in other cases as other threads |
| 126 | may be using the file at this point |
| 127 | IF using --external-locking, which does not apply to Maria. |
| 128 | */ |
| 129 | if (((share->changed && share->base.born_transactional) || |
| 130 | maria_is_crashed(info))) |
| 131 | { |
| 132 | if (save_global_changed) |
| 133 | { |
| 134 | /* |
| 135 | Reset effect of _ma_mark_file_changed(). Better to do it |
| 136 | here than in _ma_decrement_open_count(), as |
| 137 | _ma_state_info_write() will write the open_count. |
| 138 | */ |
| 139 | save_global_changed= 0; |
| 140 | share->state.open_count--; |
| 141 | } |
| 142 | /* |
| 143 | State must be written to file as it was not done at table's |
| 144 | unlocking. |
| 145 | */ |
| 146 | if (_ma_state_info_write(share, MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET)) |
| 147 | error= my_errno; |
| 148 | } |
| 149 | DBUG_ASSERT(maria_is_crashed(info) || !share->base.born_transactional || |
| 150 | share->state.open_count == 0 || |
| 151 | share->open_count_not_zero_on_open); |
| 152 | |
| 153 | /* Ensure that open_count is zero on close */ |
| 154 | share->global_changed= save_global_changed; |
| 155 | _ma_decrement_open_count(info, 0); |
| 156 | |
| 157 | /* Ensure that open_count really is zero */ |
| 158 | DBUG_ASSERT(maria_is_crashed(info) || share->temporary || |
| 159 | share->state.open_count == 0 || |
| 160 | share->open_count_not_zero_on_open); |
| 161 | |
| 162 | /* |
| 163 | File must be synced as it is going out of the maria_open_list and so |
| 164 | becoming unknown to future Checkpoints. |
| 165 | */ |
| 166 | if (share->now_transactional && mysql_file_sync(share->kfile.file, MYF(MY_WME))) |
| 167 | error= my_errno; |
| 168 | if (mysql_file_close(share->kfile.file, MYF(0))) |
| 169 | error= my_errno; |
| 170 | } |
| 171 | thr_lock_delete(&share->lock); |
| 172 | mysql_mutex_destroy(&share->key_del_lock); |
| 173 | |
| 174 | { |
| 175 | int i,keys; |
| 176 | keys = share->state.header.keys; |
| 177 | mysql_rwlock_destroy(&share->mmap_lock); |
| 178 | for(i=0; i<keys; i++) { |
| 179 | mysql_rwlock_destroy(&share->keyinfo[i].root_lock); |
| 180 | } |
| 181 | } |
| 182 | DBUG_ASSERT(share->now_transactional == share->base.born_transactional); |
| 183 | /* |
| 184 | We assign -1 because checkpoint does not need to flush (in case we |
| 185 | have concurrent checkpoint if no then we do not need it here also) |
| 186 | */ |
| 187 | share->kfile.file= -1; |
| 188 | |
| 189 | /* |
| 190 | Remember share->history for future opens |
| 191 | |
| 192 | We have to unlock share->intern_lock then lock it after |
| 193 | LOCK_trn_list (trnman_lock()) to avoid dead locks. |
| 194 | */ |
| 195 | if (!internal_table) |
| 196 | mysql_mutex_unlock(&share->intern_lock); |
| 197 | _ma_remove_not_visible_states_with_lock(share, TRUE); |
| 198 | if (!internal_table) |
| 199 | mysql_mutex_lock(&share->intern_lock); |
| 200 | |
| 201 | if (share->in_checkpoint & MARIA_CHECKPOINT_LOOKS_AT_ME) |
| 202 | { |
| 203 | /* we cannot my_free() the share, Checkpoint would see a bad pointer */ |
| 204 | share->in_checkpoint|= MARIA_CHECKPOINT_SHOULD_FREE_ME; |
| 205 | } |
| 206 | else |
| 207 | share_can_be_freed= TRUE; |
| 208 | |
| 209 | if (share->state_history) |
| 210 | { |
| 211 | if (share->state_history->trid) /* If not visible for all */ |
| 212 | { |
| 213 | MARIA_STATE_HISTORY_CLOSED *history; |
| 214 | DBUG_PRINT("info" , ("Storing state history" )); |
| 215 | /* |
| 216 | Here we ignore the unlikely case that we don't have memory |
| 217 | to store the state. In the worst case what happens is that |
| 218 | any transaction that tries to access this table will get a |
| 219 | wrong status information. |
| 220 | */ |
| 221 | if ((history= (MARIA_STATE_HISTORY_CLOSED *) |
| 222 | my_malloc(sizeof(*history), MYF(MY_WME)))) |
| 223 | { |
| 224 | history->create_rename_lsn= share->state.create_rename_lsn; |
| 225 | history->state_history= share->state_history; |
| 226 | if (my_hash_insert(&maria_stored_state, (uchar*) history)) |
| 227 | my_free(history); |
| 228 | } |
| 229 | } |
| 230 | else |
| 231 | my_free(share->state_history); |
| 232 | /* Marker for concurrent checkpoint */ |
| 233 | share->state_history= 0; |
| 234 | } |
| 235 | } |
| 236 | if (!internal_table) |
| 237 | { |
| 238 | mysql_mutex_unlock(&THR_LOCK_maria); |
| 239 | mysql_mutex_unlock(&share->intern_lock); |
| 240 | mysql_mutex_unlock(&share->close_lock); |
| 241 | } |
| 242 | if (share_can_be_freed) |
| 243 | { |
| 244 | ma_crypt_free(share); |
| 245 | (void) mysql_mutex_destroy(&share->intern_lock); |
| 246 | (void) mysql_mutex_destroy(&share->close_lock); |
| 247 | (void) mysql_cond_destroy(&share->key_del_cond); |
| 248 | my_free(share); |
| 249 | /* |
| 250 | If share cannot be freed, it's because checkpoint has previously |
| 251 | recorded to include this share in the checkpoint and so is soon going to |
| 252 | look at some of its content (share->in_checkpoint/id/last_version). |
| 253 | */ |
| 254 | } |
| 255 | my_free(info->ftparser_param); |
| 256 | if (info->dfile.file >= 0) |
| 257 | { |
| 258 | /* |
| 259 | This is outside of mutex so would confuse a concurrent |
| 260 | Checkpoint. Fortunately in BLOCK_RECORD we close earlier under mutex. |
| 261 | */ |
| 262 | if (mysql_file_close(info->dfile.file, MYF(0))) |
| 263 | error= my_errno; |
| 264 | } |
| 265 | |
| 266 | delete_dynamic(&info->pinned_pages); |
| 267 | my_free(info); |
| 268 | |
| 269 | if (error) |
| 270 | { |
| 271 | DBUG_PRINT("error" , ("Got error on close: %d" , my_errno)); |
| 272 | DBUG_RETURN(my_errno= error); |
| 273 | } |
| 274 | DBUG_RETURN(0); |
| 275 | } /* maria_close */ |
| 276 | |