mariadb/mysys/mf_keycache.c
unknown a8caad316a New SET syntax & system variables.
Made a some new buffers thread specific and changeable.
Resize of key_buffer.
AUTO_COMMIT -> AUTOCOMMIT
Fixed mutex bug in DROP DATABASE
Fixed bug when using auto_increment as second part of a key where first part could include NULL.
Split handler->extra() to extra() and extra_opt() to be able to support thread specific buffers.
Don't write message to error log when slave reconnects becasue of timeout.
Fixed possible update problem when using DELETE/UPDATE on small tables
(In some cases we used index even if table scanning would be better)
A lot of minior code cleanups


BitKeeper/deleted/.del-net.c~ef21d6402bb882f9:
  Delete: libmysql/net.c
BitKeeper/etc/ignore:
  added libmysql/net.c
Docs/manual.texi:
  New SET syntax & system variables.
client/client_priv.h:
  moved order of include files
client/mysql.cc:
  Removed compiler warning
client/mysqladmin.c:
  Use new SHOW GLOBAL syntax (if server supports it)
configure.in:
  version change
include/Makefile.am:
  indentation cleanup
include/my_getopt.h:
  Made some helper functions global
include/my_sys.h:
  Removed not used code
include/myisam.h:
  Added extra argument to ..._extra()
include/myisammrg.h:
  Added extra argument to ..._extra()
include/mysql_com.h:
  changed NET to be able to support changeable system variables
include/mysql_embed.h:
  Added MYSQL_SERVER_SUFFIX
include/mysql_version.h.in:
  Added check of multiple including (needed for embedded library)
include/mysqld_error.h:
  New error messages
innobase/dict/dict0dict.c:
  Remove compiler warnings
innobase/include/ut0mem.h:
  Remove compiler warnings
innobase/include/ut0mem.ic:
  Remove compiler warnings
isam/isamchk.c:
  new init_key_cache() arguments
isam/isamlog.c:
  new init_key_cache() arguments
isam/test2.c:
  new init_key_cache() arguments
isam/test3.c:
  new init_key_cache() arguments
libmysql/Makefile.am:
  Removed net.c (Automaticly make it from net_serv.cc)
libmysql/Makefile.shared:
  Removed net.c (Automaticly make it from net_serv.cc)
libmysql/libmysql.c:
  Changeable system variables
libmysqld/Makefile.am:
  Added set_var.cc file
libmysqld/embedded_priv.h:
  Changed order of include fiels
libmysqld/lib_sql.cc:
  merge with mysqld.cc (for changeable variables)
libmysqld/libmysqld.c:
  New changeable system variables
myisam/mi_check.c:
  Added extra argument to ..._extra()
myisam/mi_extra.c:
  Added extra argument to ..._extra()
myisam/mi_open.c:
  Removed not used variable
myisam/mi_test1.c:
  Changed call to init_key_cache
myisam/mi_test2.c:
  Added extra argument to ..._extra()
myisam/mi_test3.c:
  Added extra argument to ..._extra()
myisam/mi_write.c:
  Add cache size argument to bulk-insert-init
myisam/myisamchk.c:
  Use new key cache
myisam/myisamdef.h:
  new mi_init_bulk_insert() arguments
myisam/myisamlog.c:
  Added extra argument to ..._extra()
myisam/myisampack.c:
  Added extra argument to ..._extra()
myisammrg/myrg_extra.c:
  Added extra argument to ..._extra()
myisammrg/myrg_rrnd.c:
  Added extra argument to ..._extra()
mysql-test/r/insert_select.result:
  New changeable system variables
mysql-test/r/key.result:
  Test of bug in auto_increment
mysql-test/r/query_cache.result:
  New changeable system variables
mysql-test/r/rpl000001.result:
  New changeable system variables
mysql-test/r/rpl000016.result:
  New changeable system variables
mysql-test/r/union.result:
  New changeable system variables
mysql-test/r/user_var.result:
  New changeable system variables
mysql-test/r/variables.result:
  New changeable system variables
mysql-test/t/key.test:
  Test of bug in auto_increment
mysql-test/t/query_cache.test:
  New changeable system variables
mysql-test/t/rpl000001.test:
  New changeable system variables
mysql-test/t/rpl000009.test:
  New changeable system variables
mysql-test/t/rpl000016.test:
  New changeable system variables
mysql-test/t/rpl_compat.test:
  New changeable system variables
mysql-test/t/union.test:
  New changeable system variables
mysql-test/t/user_var.test:
  New changeable system variables
mysql-test/t/variables.test:
  New changeable system variables
mysys/default.c:
  Bigger default memory allocation
mysys/mf_iocache.c:
  Removed compiler warning
mysys/mf_keycache.c:
  Made key cache resizable on the fly
  Removed not needed extra argument to init_key_cache()
mysys/my_getopt.c:
  Made some helper functions global
sql/Makefile.am:
  Aded set_var.cc
sql/convert.cc:
  Comment cleanup
sql/field.cc:
  new changeable system variables
sql/filesort.cc:
  new changeable system variables
sql/ha_berkeley.cc:
  AUTO_COMMIT -> AUTOCOMMIT
sql/ha_innodb.cc:
  new changeable system variables
sql/ha_myisam.cc:
  Added extra argument to ..._extra()
sql/ha_myisam.h:
  Added extra argument to ..._extra()
sql/ha_myisammrg.cc:
  Added extra argument to ..._extra()
sql/ha_myisammrg.h:
  Added extra argument to ..._extra()
sql/handler.cc:
  Added extra argument to ..._extra()
  Added resize of key cache
  Change ha_table_typelib for use with new system variables
sql/handler.h:
  Added extra argument to ..._extra()
sql/item.cc:
  new changeable system variables
sql/item.h:
  Added better support of Item_uint
sql/item_func.cc:
  Added support for SET @@[global | session] system_variable
sql/item_strfunc.cc:
  new changeable system variables
sql/key.cc:
  Fixed bug in auto_increment on second part keys
sql/lex.h:
  Removed not needed keywords
sql/log.cc:
  new changeable system variables
sql/log_event.cc:
  new changeable system variables
sql/log_event.h:
  Removed not needed var reference
sql/mini_client.cc:
  new changeable system variables
  code cleanup
sql/mini_client.h:
  Indentation cleanup
sql/mysql_priv.h:
  Changed order of include files & variables to make file more readable
sql/mysqld.cc:
  Changed order of variables to make file more readable.
  Support for changeable variables
  Rename of system variables
  Moved init_vars to set_var.cc
  Changed output of --help
sql/net_pkg.cc:
  Added my_net_local_init() to make it possible to set different defaults for network connection depending if you are a client, embedded library or server.
sql/net_serv.cc:
  new changeable system variables
  To support this, some global variables had to be move to the NET structure.
sql/records.cc:
  new changeable system variables
  use extra_opt()
sql/repl_failsafe.cc:
  new changeable system variables
  minior code cleanups
sql/repl_failsafe.h:
  removed not needed external var reference
sql/share/czech/errmsg.txt:
  new changeable system variables
sql/share/danish/errmsg.txt:
  new changeable system variables
sql/share/dutch/errmsg.txt:
  new changeable system variables
sql/share/english/errmsg.txt:
  new changeable system variables
sql/share/estonian/errmsg.txt:
  new changeable system variables
sql/share/french/errmsg.txt:
  new changeable system variables
sql/share/german/errmsg.txt:
  new changeable system variables
sql/share/greek/errmsg.txt:
  new changeable system variables
sql/share/hungarian/errmsg.txt:
  new changeable system variables
sql/share/italian/errmsg.txt:
  new changeable system variables
sql/share/japanese/errmsg.txt:
  new changeable system variables
sql/share/korean/errmsg.txt:
  new changeable system variables
sql/share/norwegian-ny/errmsg.txt:
  new changeable system variables
sql/share/norwegian/errmsg.txt:
  new changeable system variables
sql/share/polish/errmsg.txt:
  new changeable system variables
sql/share/portuguese/errmsg.txt:
  new changeable system variables
sql/share/romanian/errmsg.txt:
  new changeable system variables
sql/share/russian/errmsg.txt:
  new changeable system variables
sql/share/slovak/errmsg.txt:
  new changeable system variables
sql/share/spanish/errmsg.txt:
  new changeable system variables
sql/share/swedish/errmsg.txt:
  new changeable system variables
sql/share/ukrainian/errmsg.txt:
  new changeable system variables
sql/slave.cc:
  new changeable system variables
  Added some suppression of error messages
  Initialize current_thd for all slave threads.
sql/sql_acl.cc:
  Added checking of arguments for SET PASSWORD (for new SET defintion)
sql/sql_acl.h:
  new prototypes
sql/sql_base.cc:
  new changeable system variables
sql/sql_cache.cc:
  new changeable system variables
sql/sql_cache.h:
  Renamed some arguments to make code more readable
sql/sql_class.cc:
  new changeable system variables
sql/sql_class.h:
  New changeable system variables
  Code cleanup
sql/sql_db.cc:
  Fixed bug in DROP DATABASE
sql/sql_delete.cc:
  Usage of wrong define in test (possible speed problem)
sql/sql_insert.cc:
  use extra_opt()
  Code cleanup
sql/sql_lex.cc:
  Added support for SET @@[global | session] system_variable
sql/sql_lex.h:
  Added support for SET @@[global | session] system_variable
sql/sql_load.cc:
  Cleanup for embedded library
  Use extra_opt()
sql/sql_parse.cc:
  Cleanup for embedded library
  New changeable system variables
sql/sql_repl.cc:
  new changeable system variables
sql/sql_repl.h:
  Fixed variable definitions
sql/sql_select.cc:
  new changeable system variables
sql/sql_show.cc:
  New changeable system variables
sql/sql_table.cc:
  Fixed bug in DROP DATABASE
sql/sql_union.cc:
  New changeable system variables
sql/sql_update.cc:
  Usage of wrong define in test (possible speed problem)
sql/sql_yacc.yy:
  New changeable system variables
sql/structs.h:
  Added typedef for SHOW_VAR
sql/table.cc:
  Fixed bug in auto_increment on second part keys
sql/uniques.cc:
  Comment fix
sql/unireg.h:
  A
2002-07-23 18:31:22 +03:00

868 lines
23 KiB
C

/* 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; 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 */
/*
This functions is handle keyblock cacheing for NISAM, MISAM and PISAM
databases.
One cache can handle many files. Every different blocksize has it owns
set of buffers that are allocated from block_mem.
init_key_cache() should be used to init cache handler.
*/
#include "mysys_priv.h"
#include "my_static.h"
#include <m_string.h>
#include <errno.h>
#if defined(MSDOS) && !defined(M_IC80386)
/* We nead much memory */
#undef my_malloc_lock
#undef my_free_lock
#define my_malloc_lock(A,B) halloc((long) (A/IO_SIZE),IO_SIZE)
#define my_free_lock(A,B) hfree(A)
#endif
/* size of map to be used to find changed files */
#define CHANGED_BLOCKS_HASH 128 /* Must be power of 2 */
#define CHANGED_BLOCKS_MASK (CHANGED_BLOCKS_HASH-1)
#define FLUSH_CACHE 2000 /* Sort this many blocks at once */
typedef struct sec_link {
struct sec_link *next_hash,**prev_hash;/* Blocks linked acc. to hash-value */
struct sec_link *next_used,*prev_used;
struct sec_link *next_changed,**prev_changed;
File file;
my_off_t diskpos;
byte *buffer;
my_bool changed;
} SEC_LINK;
static SEC_LINK *find_key_block(int file,my_off_t filepos,int *error);
static int flush_all_key_blocks();
/* static variables in this file */
static SEC_LINK *_my_block_root,**_my_hash_root,
*_my_used_first,*_my_used_last;
static int _my_disk_blocks;
static uint _my_disk_blocks_used, _my_hash_blocks;
static uint key_cache_shift;
ulong _my_blocks_used,_my_blocks_changed;
ulong _my_cache_w_requests,_my_cache_write,_my_cache_r_requests,
_my_cache_read;
uint key_cache_block_size=DEFAULT_KEYCACHE_BLOCK_SIZE;
static byte HUGE_PTR *_my_block_mem;
static SEC_LINK *changed_blocks[CHANGED_BLOCKS_HASH];
static SEC_LINK *file_blocks[CHANGED_BLOCKS_HASH];
#ifndef DBUG_OFF
static my_bool _my_printed;
#endif
/* Init of disk_buffert */
/* Returns blocks in use */
/* ARGSUSED */
int init_key_cache(ulong use_mem)
{
uint blocks,length;
DBUG_ENTER("init_key_cache");
if (key_cache_inited && _my_disk_blocks > 0)
{
DBUG_PRINT("warning",("key cache already in use")); /* purecov: inspected */
DBUG_RETURN(0); /* purecov: inspected */
}
if (! key_cache_inited)
{
key_cache_inited=TRUE;
_my_disk_blocks= -1;
key_cache_shift=my_bit_log2(key_cache_block_size);
DBUG_PRINT("info",("key_cache_block_size: %u key_cache_shift: %u",
key_cache_block_size, key_cache_shift));
#ifndef DBUG_OFF
_my_printed=0;
#endif
}
blocks= (uint) (use_mem/(sizeof(SEC_LINK)+sizeof(SEC_LINK*)*5/4+
key_cache_block_size));
/* No use to have very few blocks */
if (blocks >= 8 && _my_disk_blocks < 0)
{
for (;;)
{
/* Set my_hash_blocks to the next bigger 2 power */
_my_hash_blocks=(uint) 1 << (my_bit_log2(blocks*5/4)+1);
while ((length=(uint) blocks*sizeof(SEC_LINK)+
sizeof(SEC_LINK*)*_my_hash_blocks)+
((ulong) blocks << key_cache_shift) >
use_mem)
blocks--;
if ((_my_block_mem=my_malloc_lock((ulong) blocks << key_cache_shift,
MYF(0))))
{
if ((_my_block_root=(SEC_LINK*) my_malloc((uint) length,MYF(0))) != 0)
break;
my_free_lock(_my_block_mem,MYF(0));
}
if (blocks < 8)
goto err;
blocks=blocks/4*3;
}
_my_disk_blocks=(int) blocks;
_my_hash_root= (SEC_LINK**) (_my_block_root+blocks);
bzero((byte*) _my_hash_root,_my_hash_blocks*sizeof(SEC_LINK*));
_my_used_first=_my_used_last=0;
_my_blocks_used=_my_disk_blocks_used=_my_blocks_changed=0;
_my_cache_w_requests=_my_cache_r_requests=_my_cache_read=_my_cache_write=0;
DBUG_PRINT("exit",("disk_blocks: %d block_root: %lx _my_hash_blocks: %d hash_root: %lx",
_my_disk_blocks,_my_block_root,_my_hash_blocks,
_my_hash_root));
}
bzero((gptr) changed_blocks,sizeof(changed_blocks[0])*CHANGED_BLOCKS_HASH);
bzero((gptr) file_blocks,sizeof(file_blocks[0])*CHANGED_BLOCKS_HASH);
DBUG_RETURN((int) blocks);
err:
my_errno=ENOMEM;
DBUG_RETURN(0);
} /* init_key_cache */
/*
Resize the key cache
SYNOPSIS
resize_key_cache()
use_mem Bytes to use for new key cache
RETURN VALUES
0 Error
# number of blocks in key cache
*/
int resize_key_cache(ulong use_mem)
{
int block;
pthread_mutex_lock(&THR_LOCK_keycache);
if (flush_all_key_blocks())
{
/* TODO: If this happens, we should write a warning in the log file ! */
pthread_mutex_unlock(&THR_LOCK_keycache);
return 0;
}
end_key_cache();
/* The following will work even if memory is 0 */
block=init_key_cache(use_mem);
pthread_mutex_unlock(&THR_LOCK_keycache);
return block;
}
/* Remove key_cache from memory */
void end_key_cache(void)
{
DBUG_ENTER("end_key_cache");
if (! _my_blocks_changed)
{
if (_my_disk_blocks > 0)
{
my_free_lock((gptr) _my_block_mem,MYF(0));
my_free((gptr) _my_block_root,MYF(0));
_my_disk_blocks= -1;
}
}
key_cache_inited=0;
_my_hash_blocks=_my_blocks_used=0;
DBUG_PRINT("status",
("used: %d changed: %d w_requests: %ld writes: %ld r_requests: %ld reads: %ld",
_my_blocks_used,_my_blocks_changed,_my_cache_w_requests,
_my_cache_write,_my_cache_r_requests,_my_cache_read));
DBUG_VOID_RETURN;
} /* end_key_cache */
static inline void link_into_file_blocks(SEC_LINK *next, int file)
{
reg1 SEC_LINK **ptr= &file_blocks[(uint) file & CHANGED_BLOCKS_MASK];
next->prev_changed= ptr;
if ((next->next_changed= *ptr))
(*ptr)->prev_changed= &next->next_changed;
*ptr=next;
}
static inline void relink_into_file_blocks(SEC_LINK *next, int file)
{
reg1 SEC_LINK **ptr= &file_blocks[(uint) file & CHANGED_BLOCKS_MASK];
if (next->next_changed)
next->next_changed->prev_changed=next->prev_changed;
*next->prev_changed=next->next_changed;
next->prev_changed= ptr;
if ((next->next_changed= *ptr))
(*ptr)->prev_changed= &next->next_changed;
*ptr=next;
}
static inline void link_changed_to_file(SEC_LINK *next,int file)
{
reg1 SEC_LINK **ptr= &file_blocks[(uint) file & CHANGED_BLOCKS_MASK];
if (next->next_changed)
next->next_changed->prev_changed=next->prev_changed;
*next->prev_changed=next->next_changed;
next->prev_changed= ptr;
if ((next->next_changed= *ptr))
(*ptr)->prev_changed= &next->next_changed;
*ptr=next;
next->changed=0;
_my_blocks_changed--;
}
static inline void link_file_to_changed(SEC_LINK *next)
{
reg1 SEC_LINK **ptr= &changed_blocks[(uint) next->file & CHANGED_BLOCKS_MASK];
if (next->next_changed)
next->next_changed->prev_changed=next->prev_changed;
*next->prev_changed=next->next_changed;
next->prev_changed= ptr;
if ((next->next_changed= *ptr))
(*ptr)->prev_changed= &next->next_changed;
*ptr=next;
next->changed=1;
_my_blocks_changed++;
}
#if !defined(DBUG_OFF) && !defined(EXTRA_DEBUG)
#define DBUG_OFF /* This should work */
#endif
#ifndef DBUG_OFF
static void test_key_cache(const char *where, my_bool lock);
#endif
/*
** read a key_buffer
** filepos must point at a even key_cache_block_size block
** if return_buffer is set then the intern buffer is returned if
** it can be used
** Returns adress to where data is read
*/
byte *key_cache_read(File file, my_off_t filepos, byte *buff, uint length,
uint block_length __attribute__((unused)),
int return_buffer __attribute__((unused)))
{
reg1 SEC_LINK *next;
int error=0;
DBUG_ENTER("key_cache_read");
DBUG_PRINT("enter", ("file %u, filepos %lu, length %u",
(uint) file, (ulong) filepos, length));
#ifndef THREAD
if (block_length > key_cache_block_size)
return_buffer=0;
#endif
if (_my_disk_blocks > 0)
{ /* We have key_cacheing */
byte *start=buff;
uint read_length;
pthread_mutex_lock(&THR_LOCK_keycache);
if (_my_disk_blocks <= 0) /* Resize failed */
{
pthread_mutex_unlock(&THR_LOCK_keycache);
goto no_key_cache;
}
do
{
_my_cache_r_requests++;
read_length= (length > key_cache_block_size ? key_cache_block_size :
length);
if (!(next=find_key_block(file,filepos,&error)))
{
pthread_mutex_unlock(&THR_LOCK_keycache);
DBUG_RETURN ((byte*) 0); /* Got a fatal error */
}
if (error)
{ /* Didn't find it in cache */
if (my_pread(file,next->buffer,read_length,filepos,MYF(MY_NABP)))
{
pthread_mutex_unlock(&THR_LOCK_keycache);
DBUG_RETURN((byte*) 0);
}
_my_cache_read++;
}
#ifndef THREAD /* buffer may be used a long time */
if (return_buffer)
{
pthread_mutex_unlock(&THR_LOCK_keycache);
DBUG_RETURN (next->buffer);
}
#endif
if (! (read_length & 511))
bmove512(buff,next->buffer,read_length);
else
memcpy(buff,next->buffer,(size_t) read_length);
buff+=read_length;
filepos+=read_length;
} while ((length-= read_length));
pthread_mutex_unlock(&THR_LOCK_keycache);
DBUG_RETURN(start);
}
no_key_cache:
_my_cache_r_requests++;
_my_cache_read++;
if (my_pread(file,(byte*) buff,length,filepos,MYF(MY_NABP)))
error=1;
DBUG_RETURN(error ? (byte*) 0 : buff);
} /* key_cache_read */
/* write a key_buffer */
/* We don't have to use pwrite because of write locking */
/* buff must point at a even key_cache_block_size block */
int key_cache_write(File file, my_off_t filepos, byte *buff, uint length,
uint block_length __attribute__((unused)),
int dont_write)
{
reg1 SEC_LINK *next;
int error=0;
DBUG_ENTER("key_cache_write");
DBUG_PRINT("enter", ("file %u, filepos %lu, length %u",
(uint) file, (ulong) filepos, length));
if (!dont_write)
{ /* Forced write of buffer */
_my_cache_write++;
if (my_pwrite(file,buff,length,filepos,MYF(MY_NABP | MY_WAIT_IF_FULL)))
DBUG_RETURN(1);
}
#if !defined(DBUG_OFF) && defined(EXTRA_DEBUG)
DBUG_EXECUTE("check_keycache",test_key_cache("start of key_cache_write",1););
#endif
if (_my_disk_blocks > 0)
{ /* We have key_cacheing */
uint read_length;
pthread_mutex_lock(&THR_LOCK_keycache);
if (_my_disk_blocks <= 0) /* If resize failed */
{
pthread_mutex_unlock(&THR_LOCK_keycache);
goto no_key_cache;
}
_my_cache_w_requests++;
do
{
read_length= length > key_cache_block_size ? key_cache_block_size : length;
if (!(next=find_key_block(file,filepos,&error)))
goto end; /* Fatal error */
if (!dont_write) /* If we wrote buff at start */
{
if (next->changed) /* Unlink from changed list */
link_changed_to_file(next,next->file);
}
else if (!next->changed)
link_file_to_changed(next); /* Add to changed list */
if (!(read_length & 511))
bmove512(next->buffer,buff,read_length);
else
memcpy(next->buffer,buff,(size_t) read_length);
buff+=read_length;
filepos+=read_length;
} while ((length-= read_length));
error=0;
pthread_mutex_unlock(&THR_LOCK_keycache);
goto end;
}
no_key_cache:
if (dont_write)
{ /* We must write, no cache */
_my_cache_w_requests++;
_my_cache_write++;
if (my_pwrite(file,(byte*) buff,length,filepos,
MYF(MY_NABP | MY_WAIT_IF_FULL)))
error=1;
}
end:
#if !defined(DBUG_OFF) && defined(EXTRA_DEBUG)
DBUG_EXECUTE("check_keycache",test_key_cache("end of key_cache_write",1););
#endif
DBUG_RETURN(error);
} /* key_cache_write */
/* Find block in cache */
/* IF found sector and error is set then next->changed is cleared */
static SEC_LINK *find_key_block(int file, my_off_t filepos, int *error)
{
reg1 SEC_LINK *next,**start;
DBUG_ENTER("find_key_block");
DBUG_PRINT("enter", ("file %u, filepos %lu",
(uint) file, (ulong) filepos));
#if !defined(DBUG_OFF) && defined(EXTRA_DEBUG)
DBUG_EXECUTE("check_keycache2",test_key_cache("start of find_key_block",0););
#endif
*error=0;
next= *(start= &_my_hash_root[((ulong) (filepos >> key_cache_shift)+(ulong) file) &
(_my_hash_blocks-1)]);
while (next && (next->diskpos != filepos || next->file != file))
next= next->next_hash;
if (next)
{ /* Found block */
if (next != _my_used_last)
{ /* Relink used-chain */
if (next == _my_used_first)
_my_used_first=next->next_used;
else
{
next->prev_used->next_used = next->next_used;
next->next_used->prev_used = next->prev_used;
}
next->prev_used=_my_used_last;
_my_used_last->next_used=next;
}
}
else
{ /* New block */
if (_my_disk_blocks_used+1 <= (uint) _my_disk_blocks)
{ /* There are unused blocks */
next= &_my_block_root[_my_blocks_used++]; /* Link in hash-chain */
next->buffer=ADD_TO_PTR(_my_block_mem,
((ulong) _my_disk_blocks_used << key_cache_shift),
byte*);
/* link first in file_blocks */
next->changed=0;
link_into_file_blocks(next,file);
_my_disk_blocks_used++;
if (!_my_used_first)
_my_used_first=next;
if (_my_used_last)
_my_used_last->next_used=next; /* Last in used-chain */
}
else
{ /* Reuse old block */
next= _my_used_first;
if (next->changed)
{
if (my_pwrite(next->file,next->buffer,key_cache_block_size,
next->diskpos,
MYF(MY_NABP | MY_WAIT_IF_FULL)))
{
*error=1;
return((SEC_LINK*) 0);
}
_my_cache_write++;
link_changed_to_file(next,file);
}
else
{
if (next->file == -1)
link_into_file_blocks(next,file);
else
relink_into_file_blocks(next,file);
}
if (next->prev_hash) /* If in hash-link */
if ((*next->prev_hash=next->next_hash) != 0) /* Remove from link */
next->next_hash->prev_hash= next->prev_hash;
_my_used_last->next_used=next;
_my_used_first=next->next_used;
}
if (*start) /* Link in first in h.-chain */
(*start)->prev_hash= &next->next_hash;
next->next_hash= *start; next->prev_hash=start; *start=next;
next->prev_used=_my_used_last;
next->file=file;
next->diskpos=filepos;
*error=1; /* Block wasn't in memory */
}
_my_used_last=next;
#if !defined(DBUG_OFF) && defined(EXTRA_DEBUG)
DBUG_EXECUTE("check_keycache2",test_key_cache("end of find_key_block",0););
#endif
DBUG_RETURN(next);
} /* find_key_block */
static void free_block(SEC_LINK *used)
{
used->file= -1;
used->changed=0;
if (used != _my_used_first) /* Relink used-chain */
{
if (used == _my_used_last)
_my_used_last=used->prev_used;
else
{
used->prev_used->next_used = used->next_used;
used->next_used->prev_used = used->prev_used;
}
used->next_used=_my_used_first;
used->next_used->prev_used=used;
_my_used_first=used;
}
if ((*used->prev_hash=used->next_hash)) /* Relink hash-chain */
used->next_hash->prev_hash= used->prev_hash;
if (used->next_changed) /* Relink changed/file list */
used->next_changed->prev_changed=used->prev_changed;
*used->prev_changed=used->next_changed;
used->prev_hash=0; used->next_hash=0; /* Safety */
}
/* Flush all changed blocks to disk. Free used blocks if requested */
static int cmp_sec_link(SEC_LINK **a, SEC_LINK **b)
{
return (((*a)->diskpos < (*b)->diskpos) ? -1 :
((*a)->diskpos > (*b)->diskpos) ? 1 : 0);
}
static int flush_cached_blocks(File file, SEC_LINK **cache, uint count)
{
uint last_errno=0;
qsort((byte*) cache, count, sizeof(*cache), (qsort_cmp) cmp_sec_link);
for ( ; count-- ; cache++)
{
if (my_pwrite(file,(*cache)->buffer,key_cache_block_size,
(*cache)->diskpos,
MYF(MY_NABP | MY_WAIT_IF_FULL)))
{
if (!last_errno)
last_errno=errno ? errno : -1;
}
}
return last_errno;
}
static int flush_key_blocks_int(File file, enum flush_type type)
{
int error=0,last_errno=0;
uint count=0;
SEC_LINK *cache_buff[FLUSH_CACHE],**cache,**pos,**end;
SEC_LINK *used,*next;
DBUG_ENTER("flush_key_blocks_int");
DBUG_PRINT("enter",("file: %d blocks_used: %d blocks_changed: %d",
file,_my_blocks_used,_my_blocks_changed));
cache=cache_buff; /* If no key cache */
if (_my_disk_blocks > 0 &&
(!my_disable_flush_key_blocks || type != FLUSH_KEEP))
{
#if !defined(DBUG_OFF) && defined(EXTRA_DEBUG)
DBUG_EXECUTE("check_keycache",test_key_cache("start of flush_key_blocks",0););
#endif
if (type != FLUSH_IGNORE_CHANGED)
{
/* Count how many key blocks we have to cache to be able to
write everything with so few seeks as possible */
for (used=changed_blocks[(uint) file & CHANGED_BLOCKS_MASK];
used ;
used=used->next_changed)
{
if (used->file == file)
count++;
}
/* Only allocate a new buffer if its bigger than the one we have */
if (count <= FLUSH_CACHE ||
!(cache=(SEC_LINK**) my_malloc(sizeof(SEC_LINK*)*count,MYF(0))))
{
cache=cache_buff; /* Fall back to safe buffer */
count=FLUSH_CACHE;
}
end=cache+count;
}
/* Go through the keys and write them to buffer to be flushed */
end=(pos=cache)+count;
for (used=changed_blocks[(uint) file & CHANGED_BLOCKS_MASK];
used ;
used=next)
{
next=used->next_changed;
if (used->file == file)
{
if (type != FLUSH_IGNORE_CHANGED)
{
if (pos == end)
{
if ((error=flush_cached_blocks(file, cache, count)))
last_errno=error;
pos=cache;
}
*pos++=used;
_my_cache_write++;
}
if (type != FLUSH_KEEP && type != FLUSH_FORCE_WRITE)
{
/* This will not destroy position or data */
_my_blocks_changed--;
free_block(used);
}
else
link_changed_to_file(used,file);
}
}
if (pos != cache)
{
if ((error=flush_cached_blocks(file, cache, (uint) (pos-cache))))
last_errno=error;
}
/* The following happens very seldom */
if (type != FLUSH_KEEP && type != FLUSH_FORCE_WRITE)
{
for (used=file_blocks[(uint) file & CHANGED_BLOCKS_MASK];
used ;
used=next)
{
next=used->next_changed;
if (used->file == file && (!used->changed ||
type == FLUSH_IGNORE_CHANGED))
free_block(used);
}
}
#ifndef DBUG_OFF
DBUG_EXECUTE("check_keycache",test_key_cache("end of flush_key_blocks",0););
#endif
}
if (cache != cache_buff)
my_free((gptr) cache,MYF(0));
if (last_errno)
errno=last_errno; /* Return first error */
DBUG_RETURN(last_errno != 0);
}
/*
Flush all blocks for a specific file to disk
SYNOPSIS
flush_all_key_blocks()
file File descriptor
type Type of flush operation
RETURN VALUES
0 Ok
1 Error
*/
int flush_key_blocks(File file, enum flush_type type)
{
int res;
pthread_mutex_lock(&THR_LOCK_keycache);
res=flush_key_blocks_int(file, type);
pthread_mutex_unlock(&THR_LOCK_keycache);
return res;
}
/*
Flush all blocks in the key cache to disk
SYNOPSIS
flush_all_key_blocks()
NOTE
We must have a lock on THR_LOCK_keycache before calling this function
RETURN VALUES
0 Ok
1 Error
*/
static int flush_all_key_blocks()
{
int error=0;
while (_my_blocks_changed > 0)
if (flush_key_blocks_int(_my_used_first->file, FLUSH_RELEASE))
error=1;
return error;
}
#ifndef DBUG_OFF
/* Test if disk-cache is ok */
static void test_key_cache(const char *where, my_bool lock)
{
reg1 uint i,error;
ulong found,changed;
SEC_LINK *pos,**prev;
if (lock)
{
pthread_mutex_lock(&THR_LOCK_keycache);
if (_my_disk_blocks <= 0) /* No active key cache */
{
pthread_mutex_unlock(&THR_LOCK_keycache);
return;
}
}
found=error=0;
for (i= 0 ; i < _my_hash_blocks ; i++)
{
for (pos= *(prev= &_my_hash_root[i]) ;
pos && found < _my_blocks_used+2 ;
found++, pos= *(prev= &pos->next_hash))
{
if (prev != pos->prev_hash)
{
error=1;
DBUG_PRINT("error",
("hash: %d pos: %lx : prev: %lx != pos->prev: %lx",
i,(ulong) pos,(ulong) prev,(ulong) pos->prev_hash));
}
if (((pos->diskpos >> key_cache_shift)+pos->file) % _my_hash_blocks != i)
{
DBUG_PRINT("error",("hash: %d pos: %lx : Wrong disk_buffer %ld",
i,(ulong) pos,(ulong) pos->diskpos));
error=1;
}
}
}
if (found > _my_blocks_used)
{
DBUG_PRINT("error",("Found too many hash_pointers"));
error=1;
}
if (error && !_my_printed)
{ /* Write all hash-pointers */
_my_printed=1;
for (i=0 ; i < _my_hash_blocks ; i++)
{
DBUG_PRINT("loop",("hash: %d _my_hash_root: %lx",i,&_my_hash_root[i]));
pos= _my_hash_root[i]; found=0;
while (pos && found < 10)
{
DBUG_PRINT("loop",("pos: %lx prev: %lx next: %lx file: %d disk_buffer: %ld", (ulong) pos, (ulong) pos->prev_hash, (ulong) pos->next_hash, (ulong) pos->file, (ulong) pos->diskpos));
found++; pos= pos->next_hash;
}
}
}
found=changed=0;
if ((pos=_my_used_first))
{
while (pos != _my_used_last && found < _my_blocks_used+2)
{
found++;
if (pos->changed)
changed++;
if (pos->next_used->prev_used != pos)
{
DBUG_PRINT("error",("pos: %lx next_used: %lx next_used->prev: %lx",
(ulong) pos,
(ulong) pos->next_used,
(ulong) pos->next_used->prev_hash));
error=1;
}
pos=pos->next_used;
}
found++;
if (pos->changed)
changed++;
}
if (found != _my_blocks_used)
{
DBUG_PRINT("error",("Found %lu of %lu keyblocks",found,_my_blocks_used));
error=1;
}
for (i= 0 ; i < CHANGED_BLOCKS_HASH ; i++)
{
found=0;
prev= &changed_blocks[i];
for (pos= *prev ; pos && found < _my_blocks_used+2; pos=pos->next_changed)
{
found++;
if (pos->prev_changed != prev)
{
DBUG_PRINT("error",("changed_block list %d doesn't point backwards properly",i));
error=1;
}
prev= &pos->next_changed;
if (((uint) pos->file & CHANGED_BLOCKS_MASK) != i)
{
DBUG_PRINT("error",("Wrong file %d in changed blocks: %d",pos->file,i));
error=1;
}
changed--;
}
if (pos)
{
DBUG_PRINT("error",("changed_blocks %d has recursive link",i));
error=1;
}
found=0;
prev= &file_blocks[i];
for (pos= *prev ; pos && found < _my_blocks_used+2; pos=pos->next_changed)
{
found++;
if (pos->prev_changed != prev)
{
DBUG_PRINT("error",("file_block list %d doesn't point backwards properly",i));
error=1;
}
prev= &pos->next_changed;
if (((uint) pos->file & CHANGED_BLOCKS_MASK) != i)
{
DBUG_PRINT("error",("Wrong file %d in file_blocks: %d",pos->file,i));
error=1;
}
}
if (pos)
{
DBUG_PRINT("error",("File_blocks %d has recursive link",i));
error=1;
}
}
if (changed != 0)
{
DBUG_PRINT("error",("Found %lu blocks that wasn't in changed blocks",
changed));
error=1;
}
if (error)
DBUG_PRINT("error",("Found error at %s",where));
if (lock)
pthread_mutex_unlock(&THR_LOCK_keycache);
return;
} /* test_key_cache */
#endif