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