Bug #32623 | MySQL server crashes on UPDATE with UNIQUE index when built with VC++ 2005 | ||
---|---|---|---|
Submitted: | 22 Nov 2007 14:17 | Modified: | 22 Nov 2007 22:11 |
Reporter: | Steve Hay | Email Updates: | |
Status: | Won't fix | Impact on me: | |
Category: | MySQL Server | Severity: | S2 (Serious) |
Version: | 4.1.22 | OS: | Windows (XP SP2) |
Assigned to: | Heikki Tuuri | CPU Architecture: | Any |
Tags: | Contribution |
[22 Nov 2007 14:17]
Steve Hay
[22 Nov 2007 14:22]
MySQL Verification Team
I got this when using debug build: mysqld-debug.exe!_lseeki64 mysqld-debug.exe!my_seek mysqld-debug.exe!_my_b_read mysqld-debug.exe!rr_from_tempfile mysqld-debug.exe!mysql_update mysqld-debug.exe!mysql_execute_command mysqld-debug.exe!mysql_parse mysqld-debug.exe!dispatch_command mysqld-debug.exe!do_command mysqld-debug.exe!handle_one_connection mysqld-debug.exe!pthread_start mysqld-debug.exe!_callthreadstart mysqld-debug.exe!_threadstart I'm 99% sure this has been fixed in 5.x. Will check.
[22 Nov 2007 17:16]
Heikki Tuuri
5.0 should compile ok on 64-bit Windows.
[22 Nov 2007 22:11]
MySQL Verification Team
Thank you for the bug report. c:\dev\4.1>bin\mysql -uroot test Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 1 to server version: 4.1.24 Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql> drop table if exists relation; Query OK, 0 rows affected (0.35 sec) mysql> create table relation (parentid int, childid int, unique (parentid, -> childid)) engine=innodb; Query OK, 0 rows affected (0.22 sec) mysql> insert into relation values (1, 1); Query OK, 1 row affected (0.05 sec) mysql> update relation set childid=2 where parentid=1; ERROR 2013 (HY000): Lost connection to MySQL server during query CALL STACK mysqld.exe!_crt_debugger_hook(int _Reserved=) Line 65 C mysqld.exe!_invoke_watson(const wchar_t * pszExpression=0x00000000, const wchar_t * pszFunction=0x00000000, const wchar_t * pszFile=0x00000000, unsigned int nLine=0, unsigned int pReserved=0) Line 181 + 0x7 bytes C++ mysqld.exe!_lseeki64(int fh=-1, __int64 pos=6, int mthd=0) Line 78 + 0x2a bytes C mysqld.exe!_my_seek() + 0x6e bytes C mysqld.exe!__my_b_read() + 0x5a bytes C mysqld.exe!ha_innobase::update_row(const unsigned char * old_row=0x04bbef14, unsigned char * new_row=0x993140e3) Line 2947 + 0x7 bytes C++ mysqld.exe!mysql_update(THD * thd=0x76509896, st_table_list * table_list=0x04bbf140, List<Item> & fields={...}, List<Item> & values={...}, Item * conds=0x76501ff0, unsigned int order_num=2130567168, st_order * order=0x76501ffe, unsigned long limit=0, enum_duplicates handle_duplicates=DUP_ERROR, int ignore=0) Line 353 + 0x14 bytes C++ kernel32.dll!765098ba() [Frames below may be incorrect and/or missing, no symbols loaded for kernel32.dll] mysqld.exe!sql_alloc(unsigned int Size=8) Line 42 + 0xd bytes C++ mysqld.exe!List<create_field>::push_back(create_field * a=0x008e9a20) Line 380 + 0xa bytes C++ mysqld.exe!add_field_to_list(THD * thd=0x04bbf520, char * field_name=0x008e0450, enum_field_types type=-1724818713, char * length=0x008e0450, char * decimals=0x008e0490, unsigned int type_modifier=9312313, Item * default_value=0x004e548a, Item * on_update_value=0x00000003, st_lex_string * comment=0x006eda34, char * change=0x008e9868, List<String> * interval_list=0x00000006, charset_info_st * cs=0x00000000, unsigned int uint_geom_type=9345720) Line 4876 C++ mysqld.exe!yyparse(void * yythd=0x00000000) Line 12661 + 0xb bytes C++ mswsock.dll!74882ae0() ntdll.dll!7731a69d() ntdll.dll!7731a6e5() mswsock.dll!74883e9b() mswsock.dll!74883f42() ws2_32.dll!75772ee5() mswsock.dll!74883807() mswsock.dll!74883816() mswsock.dll!74883f42() ws2_32.dll!7577730e() ws2_32.dll!7577358e() ws2_32.dll!75772ee5() mswsock.dll!74883816() ws2_32.dll!75773f03() ws2_32.dll!7577358e() ws2_32.dll!75773f1b() ws2_32.dll!75773f1b() wsock32.dll!7543316a() mysqld.exe!_vio_timeout() + 0x35 bytes C mysqld.exe!do_command(THD * thd=0x0000002f) Line 1348 + 0x10 bytes C++ mysqld.exe!handle_one_connection(void * arg=0x008e0450) Line 1074 + 0xa bytes C++ mysqld.exe!_win_pthread_setspecific() + 0x5b bytes C mysqld.exe!_callthreadstart() Line 293 + 0x6 bytes C mysqld.exe!_threadstart(void * ptd=0x008c5030) Line 275 + 0x5 bytes C kernel32.dll!765719f1() ntdll.dll!7734d109()
[23 Nov 2007 13:31]
Heikki Tuuri
Hmm... maybe the InnoDB part of the stack trace is missing. ha_innobase::update_row( /*====================*/ /* out: error number or 0 */ const mysql_byte* old_row,/* in: old row in MySQL format */ mysql_byte* new_row)/* in: new row in MySQL format */ { row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt; upd_t* uvect; int error = 0; DBUG_ENTER("ha_innobase::update_row"); ut_ad(prebuilt->trx == (trx_t*) current_thd->transaction.all.innobase_tid); if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) table->timestamp_field->set_time(); if (last_query_id != user_thd->query_id) { prebuilt->sql_stat_start = TRUE; last_query_id = user_thd->query_id; innobase_release_stat_resources(prebuilt->trx); } if (prebuilt->upd_node) { uvect = prebuilt->upd_node->update; } else { uvect = row_get_prebuilt_update_vector(prebuilt); } /* Build an update vector from the modified fields in the rows (uses upd_buff of the handle) */ calc_row_difference(uvect, (mysql_byte*) old_row, new_row, table, upd_buff, (ulint)upd_and_key_val_buff_len, prebuilt, user_thd); /* This is not a delete */ prebuilt->upd_node->is_delete = FALSE; assert(prebuilt->template_type == ROW_MYSQL_WHOLE_ROW); innodb_srv_conc_enter_innodb(prebuilt->trx); error = row_update_for_mysql((byte*) old_row, prebuilt); innodb_srv_conc_exit_innodb(prebuilt->trx); error = convert_error_code_to_mysql(error, user_thd); /* Tell InnoDB server that there might be work for utility threads: */ innobase_active_small(); DBUG_RETURN(error); } I do not see how the code above could invoke a file read operation. I have a vague memory that something with InnoDB 64-bit file reads was fixed a few years ago. Please test building MySQL-5.0.
[23 Nov 2007 18:35]
Vladislav Vaintroub
The problem here is actually the invalid parameter handler in der C runtime (new feature since Visual Studio 2005 ,see http://msdn2.microsoft.com/EN-US/library/ksazx244(VS.80).aspx for description of the feature) It does sanity checks for some parameters in a chosen subset of the CRT functions. If parameters aren't sane, the function raises an exception. For lseek() there is a checks that file descriptor is OK (s. description of lseek in MSDN http://msdn2.microsoft.com/En-US/library/1yee101t(VS.80).aspx ). -1 is not ok of course and ,instead of returning -1 and setting errno to EBADF, the lseek() decides to commit suicide and/or inform the developer to check the params he passes to this function. In MySQL 5.x invalid parameter handler has been switched off via _set_invalid_parameter_handler. See my_init.c and the function my_parameter_handler() with the comment suggesting that somebody run onto lseek problem before. my_parameter_handler is one way to workaround the problem. Unfortunately it disabled all checks and these are a handy debugging tool sometimes. Yet another simple workaround is to check fd<0 in my_seek(), set errno=EBADF and return -1 (what a Posix compliant lseek() implementation would do so anyway). Less simple, yet probably proper way to fix is to understand why my_seek() is actually called on a file that has not been opened and to track seek_not_done flag in the IO cache code.
[26 Nov 2007 12:23]
Heikki Tuuri
Miguel or Vladislav, can you find out what file it is trying to open? InnoDB should have checks that file opening succeeds. One possibility is that it is trying to open the temp file that InnoDB uses to buffer error messages. Regards, Heikki
[26 Nov 2007 20:46]
Vladislav Vaintroub
Heikki, the problem is actually that there is no open() at all, but lseek() on a file that is not opened. I also believe that InnoDB is not involved here at all. The whole action happens in sql_update.cpp in function mysql_update() where a temporary IO_CACHE is created (line 241-243) IO_CACHE tempfile; if (open_cached_file(&tempfile, mysql_tmpdir,TEMP_PREFIX, DISK_BUFFER_SIZE, MYF(MY_WME))) (tempfile is not backed up by real file after that, so that tempfile.file is -1 after that) tempfile gets reinited later at mysql_update.cpp,line 301 if (reinit_io_cache(&tempfile,READ_CACHE,0L,0,0)) error=1; /* purecov: inspected */ select->file=tempfile; // Read row ptrs from this file and accessed at line mysql_update.cpp, line 324 while (!(error=info.read_record(&info)) && !thd->killed) info.read_record is a function pointer set to rr_from_tempfile() rr_from_tempfile() calls _my_b_read(), passing tempfile variable from mysql_update() above as the first parameter. tempfile is still not backed by an open file and has filedescriptor set to -1 . _my_b_read() calls my_seek() with filedescriptor -1. mf_iocache.c line 442 if (info->seek_not_done) { /* File touched, do seek */ VOID(my_seek(info->file,pos_in_file,MY_SEEK_SET,MYF(0)));//my_seek(-1,...)will crash info->seek_not_done=0; } my_seek() is a wrapper around C runtime's _lseeki64(), so _lseeki64 gets filedescriptor -1 and CRT parameter validation code forces the process to crash (mysqld-debug.exe started with --console would also bring a C runtime assertion popup)
[27 Nov 2007 18:15]
Heikki Tuuri
Vladislav, how would you fix MySQL? Regards, Heikki
[27 Nov 2007 20:38]
Vladislav Vaintroub
I'd probably add the parameter validation to my_seek() function. It is a quick fix and is guaranteed to work exactly the same as pre-Visual Studio 2005. Proper fix seems to be hard - there is a lot of (void)my_seek(...) or VOID(my_seek(...)) in MySQL. Because the caller does not care about the return code, every such place is actually suspectible for invalid parameters. Here is the diff. --- my_seek.c.old 2006-11-03 03:16:34.000000000 +0100 +++ my_seek.c 2007-11-27 21:13:49.072220200 +0100 @@ -29,6 +29,19 @@ whence, MyFlags)); DBUG_ASSERT(pos != MY_FILEPOS_ERROR); /* safety check */ +#if(defined __WIN__ && defined _MSC_VER && _MSC_VER >=1400) + if(fd < 0) + { + /* + my_seek is sometimes called with -1 and any error is ignored. + C runtime parameter validation logic available since Visual Studio 2005 + will cause the process to crash, would so we better do sanity checks ere. + */ + errno=my_errno=EBADF; + DBUG_PRINT("error",("fd negative")); + DBUG_RETURN(MY_FILEPOS_ERROR); + } +#endif newpos=lseek(fd, pos, whence); if (newpos == (os_off_t) -1) {
[9 Jan 2008 17:22]
Timothy Smith
A workaround which may be acceptable to some people is to upgrade MySQL to 5.0+.