MySQL 5.6.14 Source Code Document
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
mi_locking.c
1 /* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
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 St, Fifth Floor, Boston, MA 02110-1301 USA */
15 
16 /*
17  locking of isam-tables.
18  reads info from a isam-table. Must be first request before doing any furter
19  calls to any isamfunktion. Is used to allow many process use the same
20  isamdatabase.
21 */
22 
23 #include "ftdefs.h"
24 
25  /* lock table by F_UNLCK, F_RDLCK or F_WRLCK */
26 
27 int mi_lock_database(MI_INFO *info, int lock_type)
28 {
29  int error;
30  uint count;
31  MYISAM_SHARE *share=info->s;
32  DBUG_ENTER("mi_lock_database");
33  DBUG_PRINT("enter",("lock_type: %d old lock %d r_locks: %u w_locks: %u "
34  "global_changed: %d open_count: %u name: '%s'",
35  lock_type, info->lock_type, share->r_locks,
36  share->w_locks,
37  share->global_changed, share->state.open_count,
38  share->index_file_name));
39  if (share->options & HA_OPTION_READ_ONLY_DATA ||
40  info->lock_type == lock_type)
41  DBUG_RETURN(0);
42  if (lock_type == F_EXTRA_LCK) /* Used by TMP tables */
43  {
44  ++share->w_locks;
45  ++share->tot_locks;
46  info->lock_type= lock_type;
47  info->s->in_use= list_add(info->s->in_use, &info->in_use);
48  DBUG_RETURN(0);
49  }
50 
51  error= 0;
52  mysql_mutex_lock(&share->intern_lock);
53  if (share->kfile >= 0) /* May only be false on windows */
54  {
55  switch (lock_type) {
56  case F_UNLCK:
57  ftparser_call_deinitializer(info);
58  if (info->lock_type == F_RDLCK)
59  count= --share->r_locks;
60  else
61  count= --share->w_locks;
62  --share->tot_locks;
63  if (info->lock_type == F_WRLCK && !share->w_locks &&
64  !share->delay_key_write && flush_key_blocks(share->key_cache,
65  share->kfile,FLUSH_KEEP))
66  {
67  error=my_errno;
68  mi_print_error(info->s, HA_ERR_CRASHED);
69  mi_mark_crashed(info); /* Mark that table must be checked */
70  }
71  if (info->opt_flag & (READ_CACHE_USED | WRITE_CACHE_USED))
72  {
73  if (end_io_cache(&info->rec_cache))
74  {
75  error=my_errno;
76  mi_print_error(info->s, HA_ERR_CRASHED);
77  mi_mark_crashed(info);
78  }
79  }
80  if (!count)
81  {
82  DBUG_PRINT("info",("changed: %u w_locks: %u",
83  (uint) share->changed, share->w_locks));
84  if (share->changed && !share->w_locks)
85  {
86 #ifdef HAVE_MMAP
87  if ((info->s->mmaped_length != info->s->state.state.data_file_length) &&
88  (info->s->nonmmaped_inserts > MAX_NONMAPPED_INSERTS))
89  {
90  if (info->s->concurrent_insert)
91  mysql_rwlock_wrlock(&info->s->mmap_lock);
92  mi_remap_file(info, info->s->state.state.data_file_length);
93  info->s->nonmmaped_inserts= 0;
94  if (info->s->concurrent_insert)
95  mysql_rwlock_unlock(&info->s->mmap_lock);
96  }
97 #endif
98  share->state.process= share->last_process=share->this_process;
99  share->state.unique= info->last_unique= info->this_unique;
100  share->state.update_count= info->last_loop= ++info->this_loop;
101  if (mi_state_info_write(share->kfile, &share->state, 1))
102  error=my_errno;
103  share->changed=0;
104  if (myisam_flush)
105  {
106  if (mysql_file_sync(share->kfile, MYF(0)))
107  error= my_errno;
108  if (mysql_file_sync(info->dfile, MYF(0)))
109  error= my_errno;
110  }
111  else
112  share->not_flushed=1;
113  if (error)
114  {
115  mi_print_error(info->s, HA_ERR_CRASHED);
116  mi_mark_crashed(info);
117  }
118  }
119  if (info->lock_type != F_EXTRA_LCK)
120  {
121  if (share->r_locks)
122  { /* Only read locks left */
123  if (my_lock(share->kfile,F_RDLCK,0L,F_TO_EOF,
124  MYF(MY_WME | MY_SEEK_NOT_DONE)) && !error)
125  error=my_errno;
126  }
127  else if (!share->w_locks)
128  { /* No more locks */
129  if (my_lock(share->kfile,F_UNLCK,0L,F_TO_EOF,
130  MYF(MY_WME | MY_SEEK_NOT_DONE)) && !error)
131  error=my_errno;
132  }
133  }
134  }
135  info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED);
136  info->lock_type= F_UNLCK;
137  info->s->in_use= list_delete(info->s->in_use, &info->in_use);
138  break;
139  case F_RDLCK:
140  if (info->lock_type == F_WRLCK)
141  {
142  /*
143  Change RW to READONLY
144 
145  mysqld does not turn write locks to read locks,
146  so we're never here in mysqld.
147  */
148  if (share->w_locks == 1)
149  {
150  if (my_lock(share->kfile,lock_type,0L,F_TO_EOF,
151  MYF(MY_SEEK_NOT_DONE)))
152  {
153  error=my_errno;
154  break;
155  }
156  }
157  share->w_locks--;
158  share->r_locks++;
159  info->lock_type=lock_type;
160  break;
161  }
162  if (!share->r_locks && !share->w_locks)
163  {
164  if (my_lock(share->kfile,lock_type,0L,F_TO_EOF,
165  info->lock_wait | MY_SEEK_NOT_DONE))
166  {
167  error=my_errno;
168  break;
169  }
170  if (mi_state_info_read_dsk(share->kfile, &share->state, 1))
171  {
172  error=my_errno;
173  (void) my_lock(share->kfile,F_UNLCK,0L,F_TO_EOF,MYF(MY_SEEK_NOT_DONE));
174  my_errno=error;
175  break;
176  }
177  }
178  (void) _mi_test_if_changed(info);
179  share->r_locks++;
180  share->tot_locks++;
181  info->lock_type=lock_type;
182  info->s->in_use= list_add(info->s->in_use, &info->in_use);
183  break;
184  case F_WRLCK:
185  if (info->lock_type == F_RDLCK)
186  { /* Change READONLY to RW */
187  if (share->r_locks == 1)
188  {
189  if (my_lock(share->kfile,lock_type,0L,F_TO_EOF,
190  MYF(info->lock_wait | MY_SEEK_NOT_DONE)))
191  {
192  error=my_errno;
193  break;
194  }
195  share->r_locks--;
196  share->w_locks++;
197  info->lock_type=lock_type;
198  break;
199  }
200  }
201  if (!(share->options & HA_OPTION_READ_ONLY_DATA))
202  {
203  if (!share->w_locks)
204  {
205  if (my_lock(share->kfile,lock_type,0L,F_TO_EOF,
206  info->lock_wait | MY_SEEK_NOT_DONE))
207  {
208  error=my_errno;
209  break;
210  }
211  if (!share->r_locks)
212  {
213  if (mi_state_info_read_dsk(share->kfile, &share->state, 1))
214  {
215  error=my_errno;
216  (void) my_lock(share->kfile,F_UNLCK,0L,F_TO_EOF,
217  info->lock_wait | MY_SEEK_NOT_DONE);
218  my_errno=error;
219  break;
220  }
221  }
222  }
223  }
224  (void) _mi_test_if_changed(info);
225 
226  info->lock_type=lock_type;
227  info->invalidator=info->s->invalidator;
228  share->w_locks++;
229  share->tot_locks++;
230  info->s->in_use= list_add(info->s->in_use, &info->in_use);
231  break;
232  default:
233  break; /* Impossible */
234  }
235  }
236 #ifdef _WIN32
237  else
238  {
239  /*
240  Check for bad file descriptors if this table is part
241  of a merge union. Failing to capture this may cause
242  a crash on windows if the table is renamed and
243  later on referenced by the merge table.
244  */
245  if( info->owned_by_merge && (info->s)->kfile < 0 )
246  {
247  error = HA_ERR_NO_SUCH_TABLE;
248  }
249  }
250 #endif
251  mysql_mutex_unlock(&share->intern_lock);
252  DBUG_RETURN(error);
253 } /* mi_lock_database */
254 
255 
256 /****************************************************************************
257  The following functions are called by thr_lock() in threaded applications
258 ****************************************************************************/
259 
260 /*
261  Create a copy of the current status for the table
262 
263  SYNOPSIS
264  mi_get_status()
265  param Pointer to Myisam handler
266  concurrent_insert Set to 1 if we are going to do concurrent inserts
267  (THR_WRITE_CONCURRENT_INSERT was used)
268 */
269 
270 void mi_get_status(void* param, int concurrent_insert)
271 {
272  MI_INFO *info=(MI_INFO*) param;
273  DBUG_ENTER("mi_get_status");
274  DBUG_PRINT("info",("key_file: %ld data_file: %ld concurrent_insert: %d",
275  (long) info->s->state.state.key_file_length,
276  (long) info->s->state.state.data_file_length,
277  concurrent_insert));
278 #ifndef DBUG_OFF
279  if (info->state->key_file_length > info->s->state.state.key_file_length ||
280  info->state->data_file_length > info->s->state.state.data_file_length)
281  DBUG_PRINT("warning",("old info: key_file: %ld data_file: %ld",
282  (long) info->state->key_file_length,
283  (long) info->state->data_file_length));
284 #endif
285  info->save_state=info->s->state.state;
286  info->state= &info->save_state;
287  info->append_insert_at_end= concurrent_insert;
288  if (concurrent_insert)
289  info->s->state.state.uncacheable= TRUE;
290  DBUG_VOID_RETURN;
291 }
292 
293 
294 void mi_update_status(void* param)
295 {
296  MI_INFO *info=(MI_INFO*) param;
297  /*
298  Because someone may have closed the table we point at, we only
299  update the state if its our own state. This isn't a problem as
300  we are always pointing at our own lock or at a read lock.
301  (This is enforced by thr_multi_lock.c)
302  */
303  if (info->state == &info->save_state)
304  {
305 #ifndef DBUG_OFF
306  DBUG_PRINT("info",("updating status: key_file: %ld data_file: %ld",
307  (long) info->state->key_file_length,
308  (long) info->state->data_file_length));
309  if (info->state->key_file_length < info->s->state.state.key_file_length ||
310  info->state->data_file_length < info->s->state.state.data_file_length)
311  DBUG_PRINT("warning",("old info: key_file: %ld data_file: %ld",
312  (long) info->s->state.state.key_file_length,
313  (long) info->s->state.state.data_file_length));
314 #endif
315  info->s->state.state= *info->state;
316  }
317  info->state= &info->s->state.state;
318  info->append_insert_at_end= 0;
319 
320  /*
321  We have to flush the write cache here as other threads may start
322  reading the table before mi_lock_database() is called
323  */
324  if (info->opt_flag & WRITE_CACHE_USED)
325  {
326  if (end_io_cache(&info->rec_cache))
327  {
328  mi_print_error(info->s, HA_ERR_CRASHED);
329  mi_mark_crashed(info);
330  }
331  info->opt_flag&= ~WRITE_CACHE_USED;
332  }
333 }
334 
335 
336 void mi_restore_status(void *param)
337 {
338  MI_INFO *info= (MI_INFO*) param;
339  info->state= &info->s->state.state;
340  info->append_insert_at_end= 0;
341 }
342 
343 
344 void mi_copy_status(void* to,void *from)
345 {
346  ((MI_INFO*) to)->state= &((MI_INFO*) from)->save_state;
347 }
348 
349 
350 /*
351  Check if should allow concurrent inserts
352 
353  IMPLEMENTATION
354  Allow concurrent inserts if we don't have a hole in the table or
355  if there is no active write lock and there is active read locks and
356  myisam_concurrent_insert == 2. In this last case the new
357  row('s) are inserted at end of file instead of filling up the hole.
358 
359  The last case is to allow one to inserts into a heavily read-used table
360  even if there is holes.
361 
362  NOTES
363  If there is a an rtree indexes in the table, concurrent inserts are
364  disabled in mi_open()
365 
366  RETURN
367  0 ok to use concurrent inserts
368  1 not ok
369 */
370 
371 my_bool mi_check_status(void *param)
372 {
373  MI_INFO *info=(MI_INFO*) param;
374  /*
375  The test for w_locks == 1 is here because this thread has already done an
376  external lock (in other words: w_locks == 1 means no other threads has
377  a write lock)
378  */
379  DBUG_PRINT("info",("dellink: %ld r_locks: %u w_locks: %u",
380  (long) info->s->state.dellink, (uint) info->s->r_locks,
381  (uint) info->s->w_locks));
382  return (my_bool) !(info->s->state.dellink == HA_OFFSET_ERROR ||
383  (myisam_concurrent_insert == 2 && info->s->r_locks &&
384  info->s->w_locks == 1));
385 }
386 
387 
388 /****************************************************************************
389  ** functions to read / write the state
390 ****************************************************************************/
391 
392 int _mi_readinfo(register MI_INFO *info, int lock_type, int check_keybuffer)
393 {
394  DBUG_ENTER("_mi_readinfo");
395 
396  if (info->lock_type == F_UNLCK)
397  {
398  MYISAM_SHARE *share=info->s;
399  if (!share->tot_locks)
400  {
401  if (my_lock(share->kfile,lock_type,0L,F_TO_EOF,
402  info->lock_wait | MY_SEEK_NOT_DONE))
403  DBUG_RETURN(1);
404  if (mi_state_info_read_dsk(share->kfile, &share->state, 1))
405  {
406  int error=my_errno ? my_errno : -1;
407  (void) my_lock(share->kfile,F_UNLCK,0L,F_TO_EOF,
408  MYF(MY_SEEK_NOT_DONE));
409  my_errno=error;
410  DBUG_RETURN(1);
411  }
412  }
413  if (check_keybuffer)
414  (void) _mi_test_if_changed(info);
415  info->invalidator=info->s->invalidator;
416  }
417  else if (lock_type == F_WRLCK && info->lock_type == F_RDLCK)
418  {
419  my_errno=EACCES; /* Not allowed to change */
420  DBUG_RETURN(-1); /* when have read_lock() */
421  }
422  DBUG_RETURN(0);
423 } /* _mi_readinfo */
424 
425 
426 /*
427  Every isam-function that uppdates the isam-database MUST end with this
428  request
429 */
430 
431 int _mi_writeinfo(register MI_INFO *info, uint operation)
432 {
433  int error,olderror;
434  MYISAM_SHARE *share=info->s;
435  DBUG_ENTER("_mi_writeinfo");
436  DBUG_PRINT("info",("operation: %u tot_locks: %u", operation,
437  share->tot_locks));
438 
439  error=0;
440  if (share->tot_locks == 0)
441  {
442  olderror=my_errno; /* Remember last error */
443  if (operation)
444  { /* Two threads can't be here */
445  share->state.process= share->last_process= share->this_process;
446  share->state.unique= info->last_unique= info->this_unique;
447  share->state.update_count= info->last_loop= ++info->this_loop;
448  if ((error=mi_state_info_write(share->kfile, &share->state, 1)))
449  olderror=my_errno;
450 #ifdef _WIN32
451  if (myisam_flush)
452  {
453  mysql_file_sync(share->kfile, 0);
454  mysql_file_sync(info->dfile, 0);
455  }
456 #endif
457  }
458  if (!(operation & WRITEINFO_NO_UNLOCK) &&
459  my_lock(share->kfile,F_UNLCK,0L,F_TO_EOF,
460  MYF(MY_WME | MY_SEEK_NOT_DONE)) && !error)
461  DBUG_RETURN(1);
462  my_errno=olderror;
463  }
464  else if (operation)
465  share->changed= 1; /* Mark keyfile changed */
466  DBUG_RETURN(error);
467 } /* _mi_writeinfo */
468 
469 
470  /* Test if someone has changed the database */
471  /* (Should be called after readinfo) */
472 
473 int _mi_test_if_changed(register MI_INFO *info)
474 {
475  MYISAM_SHARE *share=info->s;
476  if (share->state.process != share->last_process ||
477  share->state.unique != info->last_unique ||
478  share->state.update_count != info->last_loop)
479  { /* Keyfile has changed */
480  DBUG_PRINT("info",("index file changed"));
481  if (share->state.process != share->this_process)
482  (void) flush_key_blocks(share->key_cache, share->kfile, FLUSH_RELEASE);
483  share->last_process=share->state.process;
484  info->last_unique= share->state.unique;
485  info->last_loop= share->state.update_count;
486  info->update|= HA_STATE_WRITTEN; /* Must use file on next */
487  info->data_changed= 1; /* For mi_is_changed */
488  return 1;
489  }
490  return (!(info->update & HA_STATE_AKTIV) ||
491  (info->update & (HA_STATE_WRITTEN | HA_STATE_DELETED |
492  HA_STATE_KEY_CHANGED)));
493 } /* _mi_test_if_changed */
494 
495 
496 /*
497  Put a mark in the .MYI file that someone is updating the table
498 
499 
500  DOCUMENTATION
501 
502  state.open_count in the .MYI file is used the following way:
503  - For the first change of the .MYI file in this process open_count is
504  incremented by mi_mark_file_change(). (We have a write lock on the file
505  when this happens)
506  - In mi_close() it's decremented by _mi_decrement_open_count() if it
507  was incremented in the same process.
508 
509  This mean that if we are the only process using the file, the open_count
510  tells us if the MYISAM file wasn't properly closed. (This is true if
511  my_disable_locking is set).
512 */
513 
514 
515 int _mi_mark_file_changed(MI_INFO *info)
516 {
517  uchar buff[3];
518  register MYISAM_SHARE *share=info->s;
519  DBUG_ENTER("_mi_mark_file_changed");
520 
521  if (!(share->state.changed & STATE_CHANGED) || ! share->global_changed)
522  {
523  share->state.changed|=(STATE_CHANGED | STATE_NOT_ANALYZED |
524  STATE_NOT_OPTIMIZED_KEYS);
525  if (!share->global_changed)
526  {
527  share->global_changed=1;
528  share->state.open_count++;
529  }
530  if (!share->temporary)
531  {
532  mi_int2store(buff,share->state.open_count);
533  buff[2]=1; /* Mark that it's changed */
534  DBUG_RETURN(mysql_file_pwrite(share->kfile, buff, sizeof(buff),
535  sizeof(share->state.header),
536  MYF(MY_NABP)));
537  }
538  }
539  DBUG_RETURN(0);
540 }
541 
542 
543 /*
544  This is only called by close or by extra(HA_FLUSH) if the OS has the pwrite()
545  call. In these context the following code should be safe!
546  */
547 
548 int _mi_decrement_open_count(MI_INFO *info)
549 {
550  uchar buff[2];
551  register MYISAM_SHARE *share=info->s;
552  int lock_error=0,write_error=0;
553  if (share->global_changed)
554  {
555  uint old_lock=info->lock_type;
556  share->global_changed=0;
557  lock_error=mi_lock_database(info,F_WRLCK);
558  /* Its not fatal even if we couldn't get the lock ! */
559  if (share->state.open_count > 0)
560  {
561  share->state.open_count--;
562  mi_int2store(buff,share->state.open_count);
563  write_error= mysql_file_pwrite(share->kfile, buff, sizeof(buff),
564  sizeof(share->state.header),
565  MYF(MY_NABP));
566  }
567  if (!lock_error)
568  lock_error=mi_lock_database(info,old_lock);
569  }
570  return test(lock_error || write_error);
571 }