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 02110-1335 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 "ma_ftdefs.h"
24 #include "ma_crypt.h"
25 
maria_close(register MARIA_HA * info)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   /* Check that file is not part of any uncommitted transactions */
41   DBUG_ASSERT(info->trn == 0 || info->trn == &dummy_transaction_object);
42 
43   if (share->reopen == 1)
44   {
45     /*
46       If we are going to close the file, flush page cache without
47       a global mutex
48     */
49     if (flush_pagecache_blocks(share->pagecache, &share->kfile,
50                        share->deleting ? FLUSH_IGNORE_CHANGED : 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   maria_ftparser_call_deinitializer(info);
90   my_free(info->rec_buff);
91   (*share->end)(info);
92 
93   if (flag)
94   {
95     /* Last close of file; Flush everything */
96 
97     /* Check that we don't have any dangling pointers from the transaction */
98     DBUG_ASSERT(share->in_trans == 0);
99     DBUG_ASSERT(share->open_list == 0);
100 
101     if (share->kfile.file >= 0)
102     {
103       my_bool save_global_changed= share->global_changed;
104 
105       /* Avoid _ma_mark_file_changed() when flushing pages */
106       share->global_changed= 1;
107 
108       if ((*share->once_end)(share))
109         error= my_errno;
110       /*
111         Extra flush, just in case someone opened and closed the file
112         since the start of the function (very unlikely)
113       */
114       if (flush_pagecache_blocks(share->pagecache, &share->kfile,
115                         share->deleting ? FLUSH_IGNORE_CHANGED : FLUSH_RELEASE))
116         error= my_errno;
117       unmap_file(info);
118       if (!internal_table &&
119           (((share->changed && share->base.born_transactional) ||
120             maria_is_crashed(info) ||
121             (share->temporary && !share->deleting))))
122       {
123         if (save_global_changed)
124         {
125           /*
126             Reset effect of _ma_mark_file_changed(). Better to do it
127             here than in _ma_decrement_open_count(), as
128             _ma_state_info_write() will write the open_count.
129            */
130           save_global_changed= 0;
131           share->state.open_count--;
132         }
133         /*
134           State must be written to file as it was not done at table's
135           unlocking.
136         */
137         if (_ma_state_info_write(share, MA_STATE_INFO_WRITE_DONT_MOVE_OFFSET))
138           error= my_errno;
139       }
140       DBUG_ASSERT(maria_is_crashed(info) || !share->base.born_transactional ||
141                   share->state.open_count == 0 ||
142                   share->open_count_not_zero_on_open);
143 
144       /* Ensure that open_count is zero on close */
145       share->global_changed= save_global_changed;
146       _ma_decrement_open_count(info, 0);
147 
148       /* Ensure that open_count really is zero */
149       DBUG_ASSERT(maria_is_crashed(info) || share->temporary ||
150                   share->state.open_count == 0 ||
151                   share->open_count_not_zero_on_open);
152 
153       /*
154         File must be synced as it is going out of the maria_open_list and so
155         becoming unknown to future Checkpoints.
156       */
157       if (share->now_transactional && mysql_file_sync(share->kfile.file, MYF(MY_WME)))
158         error= my_errno;
159       if (mysql_file_close(share->kfile.file, MYF(0)))
160         error= my_errno;
161     }
162     thr_lock_delete(&share->lock);
163     mysql_mutex_destroy(&share->key_del_lock);
164 
165     {
166       int i,keys;
167       keys = share->state.header.keys;
168       mysql_rwlock_destroy(&share->mmap_lock);
169       for(i=0; i<keys; i++) {
170 	mysql_rwlock_destroy(&share->keyinfo[i].root_lock);
171       }
172     }
173     DBUG_ASSERT(share->now_transactional == share->base.born_transactional);
174     /*
175       We assign -1 because checkpoint does not need to flush (in case we
176       have concurrent checkpoint if no then we do not need it here also)
177     */
178     share->kfile.file= -1;
179 
180     /*
181       Remember share->history for future opens
182 
183       We have to unlock share->intern_lock then lock it after
184       LOCK_trn_list (trnman_lock()) to avoid dead locks.
185     */
186     if (!internal_table)
187       mysql_mutex_unlock(&share->intern_lock);
188     _ma_remove_not_visible_states_with_lock(share, TRUE);
189     if (!internal_table)
190       mysql_mutex_lock(&share->intern_lock);
191 
192     if (share->in_checkpoint & MARIA_CHECKPOINT_LOOKS_AT_ME)
193     {
194       /* we cannot my_free() the share, Checkpoint would see a bad pointer */
195       share->in_checkpoint|= MARIA_CHECKPOINT_SHOULD_FREE_ME;
196     }
197     else
198       share_can_be_freed= TRUE;
199 
200     if (share->state_history)
201     {
202       if (share->state_history->trid)           /* If not visible for all */
203       {
204         MARIA_STATE_HISTORY_CLOSED *history;
205         DBUG_PRINT("info", ("Storing state history"));
206         /*
207           Here we ignore the unlikely case that we don't have memory
208           to store the state. In the worst case what happens is that
209           any transaction that tries to access this table will get a
210           wrong status information.
211         */
212         if ((history= (MARIA_STATE_HISTORY_CLOSED *)
213              my_malloc(sizeof(*history), MYF(MY_WME))))
214         {
215           history->create_rename_lsn= share->state.create_rename_lsn;
216           history->state_history= share->state_history;
217           if (my_hash_insert(&maria_stored_state, (uchar*) history))
218             my_free(history);
219         }
220       }
221       else
222         my_free(share->state_history);
223       /* Marker for concurrent checkpoint */
224       share->state_history= 0;
225     }
226   }
227   if (!internal_table)
228   {
229     mysql_mutex_unlock(&THR_LOCK_maria);
230     mysql_mutex_unlock(&share->intern_lock);
231     mysql_mutex_unlock(&share->close_lock);
232   }
233   if (share_can_be_freed)
234   {
235     ma_crypt_free(share);
236     (void) mysql_mutex_destroy(&share->intern_lock);
237     (void) mysql_mutex_destroy(&share->close_lock);
238     (void) mysql_cond_destroy(&share->key_del_cond);
239     my_free(share);
240     /*
241       If share cannot be freed, it's because checkpoint has previously
242       recorded to include this share in the checkpoint and so is soon going to
243       look at some of its content (share->in_checkpoint/id/last_version).
244     */
245   }
246   my_free(info->ftparser_param);
247   if (info->dfile.file >= 0)
248   {
249     /*
250       This is outside of mutex so would confuse a concurrent
251       Checkpoint. Fortunately in BLOCK_RECORD we close earlier under mutex.
252     */
253     if (mysql_file_close(info->dfile.file, MYF(0)))
254       error= my_errno;
255   }
256 
257   delete_dynamic(&info->pinned_pages);
258   my_free(info);
259 
260   if (error)
261   {
262     DBUG_PRINT("error", ("Got error on close: %d", my_errno));
263     DBUG_RETURN(my_errno= error);
264   }
265   DBUG_RETURN(0);
266 } /* maria_close */
267